

Ship을 생성하는 과정에서 이름 검증, 준비, 커스터마이징, 알림 등 여러 작업을 단일 메서드 orderShip에 포함하고 있다.
public class ShipFactory {
public static Ship orderShip(String name, String email) {
// validate
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("배 이름을 지어주세요.");
}
if (email == null || email.isBlank()) {
throw new IllegalArgumentException("연락처를 남겨주세요.");
}
prepareFor(name);
Ship ship = new Ship();
ship.setName(name);
// Customizing for specific name
if (name.equalsIgnoreCase("whiteship")) {
ship.setLogo("\uD83D\uDEE5️");
} else if (name.equalsIgnoreCase("blackship")) {
ship.setLogo("⚓");
}
// coloring
if (name.equalsIgnoreCase("whiteship")) {
ship.setColor("whiteship");
} else if (name.equalsIgnoreCase("blackship")) {
ship.setColor("black");
}
// notify
sendEmailTo(email, ship);
return ship;
}
private static void prepareFor(String name) {
System.out.println(name + " 만들 준비 중");
}
private static void sendEmailTo(String email, Ship ship) {
System.out.println(ship.getName() + " 다 만들었습니다.");
}
}
1) 단일 책임 원칙 위반:
2) 확장성 부족:
- 새로운 Ship 타입이 추가될 때마다 조건문을 추가해야 하므로 코드가 점점 복잡해짐.
3) 유지보수 어려움:
public class Ship {
private String name;
private String color;
private String logo;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getLogo() {
return logo;
}
public void setLogo(String logo) {
this.logo = logo;
}
@Override
public String toString() {
return "Ship{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", logo='" + logo + '\'' +
'}';
}
}
---
public class Client {
public static void main(String[] args) {
Ship whiteship = ShipFactory.orderShip("Whiteship", "keesun@mail.com");
System.out.println(whiteship);
Ship blackship = ShipFactory.orderShip("Blackship", "keesun@mail.com");
System.out.println(blackship);
}
}
Ship 생성의 공통 로직을 ShipFactory 인터페이스에 정의하고, Ship 생성(createShip)은 하위 클래스가 구현하도록 분리.
public interface ShipFactory {
default Ship orderShip(String name, String email) {
validate(name, email);
prepareFor(name);
Ship ship = createShip(); //하위 클래스에서 구현
sendEmailTo(email, ship);
return ship;
}
void sendEmailTo(String email, Ship ship);
Ship createShip();
private void validate(String name, String email) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("배 이름을 지어주세요.");
}
if (email == null || email.isBlank()) {
throw new IllegalArgumentException("연락처를 남겨주세요.");
}
}
private void prepareFor(String name) {
System.out.println(name + " 만들 준비 중");
}
}
public abstract class DefaultShipFactory implements ShipFactory {
@Override
public void sendEmailTo(String email, Ship ship) {
System.out.println(ship.getName() + " 다 만들었습니다.");
}
}
공통적으로 사용되는 메서드인 sendEmailTo를 기본 구현으로 제공함.
public class BlackshipFactory extends DefaultShipFactory {
@Override
public Ship createShip() {
return new Blackship();
}
}
public class WhiteshipFactory extends DefaultShipFactory {
@Override
public Ship createShip() {
return new Whiteship();
}
}
BlackshipFactory & WhiteshipFactory
각각의 Ship 타입에 따라 구체적인 Ship 생성 로직을 구현.
public class Whiteship extends Ship {
public Whiteship() {
setName("whiteship");
setLogo("\uD83D\uDEE5️");
setColor("white");
}
}
public class Blackship extends Ship {
public Blackship() {
setName("blackship");
setColor("black");
setLogo("⚓");
}
}
Ship 타입 구현
Ship 클래스를 상속받아 각각의 Ship에 맞는 속성을 설정.
public class Client {
public static void main(String[] args) {
Client client = new Client();
client.print(new WhiteshipFactory(), "whiteship", "keesun@mail.com");
client.print(new BlackshipFactory(), "blackship", "keesun@mail.com");
}
private void print(ShipFactory shipFactory, String name, String email) {
System.out.println(shipFactory.orderShip(name, email));
}
}
사용자는 WhiteshipFactory 또는 BlackshipFactory를 통해 원하는 Ship을 생성할 수 있음.
1) 단일 책임 원칙 준수:
2) 확장성 향상:
3) 유지보수 용이:
4) 코드 중복 제거:

확장에 열려있고 변경에 닫혀있는 구조로 수정되었다!
팩토리 메소드 패턴 장단점:
OCP (Open-Closed Principle):
자바 8의 default 메소드:
1) 단순한 팩토리 패턴
2)스프링 BeanFactory
- Object 타입의 Product를 만드는 BeanFacotry라는 Creator!

WhiteshipFactory에서 Ship 생성 시 직접 WhiteAnchor와 WhiteWheel을 생성하여 의존성을 가지고 있음.WhiteshipFactory 코드를 수정해야 하므로 OCP(확장에는 열려 있고, 변경에는 닫혀 있어야 한다) 원칙을 위반.public class WhiteshipFactory extends DefaultShipFactory {
@Override
public Ship createShip() {
Ship ship = new Whiteship();
ship.setAnchor(new WhiteAnchor()); // 의존성 직접 생성
ship.setWheel(new WhiteWheel()); // 의존성 직접 생성
return ship;
}
}
ShipPartsFactory라는 인터페이스로 분리.WhiteshipPartsFactory, WhitePartsProFactory)가 담당.WhiteshipFactory는 ShipPartsFactory를 의존성으로 받아 부품 생성을 위임하여 확장성을 높임.설계 요소
createAnchor, createWheel)를 정의.public interface ShipPartsFactory {
Anchor createAnchor();
Wheel createWheel();
}
public class WhiteshipPartsFactory implements ShipPartsFactory {
@Override
public Anchor createAnchor() {
return new WhiteAnchor();
}
@Override
public Wheel createWheel() {
return new WhiteWheel();
}
}
public class WhitePartsProFactory implements ShipPartsFactory {
@Override
public Anchor createAnchor() {
return new WhiteAnchorPro();
}
@Override
public Wheel createWheel() {
return new WhiteWheelPro();
}
}
ShipPartsFactory로 위임.public class WhiteshipFactory extends DefaultShipFactory {
private ShipPartsFactory shipPartsFactory;
public WhiteshipFactory(ShipPartsFactory shipPartsFactory) {
this.shipPartsFactory = shipPartsFactory; // 의존성 주입
}
@Override
public Ship createShip() {
Ship ship = new Whiteship();
ship.setAnchor(shipPartsFactory.createAnchor()); // 부품 생성 위임
ship.setWheel(shipPartsFactory.createWheel()); // 부품 생성 위임
return ship;
}
}

Pro 버전 추가
public class WhiteAnchorPro implements Anchor {
// Pro 버전 Anchor 구현
}
public class WhiteWheelPro implements Wheel {
// Pro 버전 Wheel 구현
}
WhitePartsProFactory.public class WhitePartsProFactory implements ShipPartsFactory {
@Override
public Anchor createAnchor() {
return new WhiteAnchorPro();
}
@Override
public Wheel createWheel() {
return new WhiteWheelPro();
}
}
ShipInventory (클라이언트 코드)
WhiteshipFactory를 사용하되, 필요한 부품 팩토리(기본 또는 Pro)를 전달받아 Ship을 생성.public class ShipInventory {
public static void main(String[] args) {
// 기본 부품 팩토리 사용
ShipFactory shipFactory = new WhiteshipFactory(new WhiteshipPartsFactory());
Ship ship = shipFactory.createShip();
System.out.println(ship.getAnchor().getClass()); // WhiteAnchor
System.out.println(ship.getWheel().getClass()); // WhiteWheel
// Pro 부품 팩토리 사용
ShipFactory proShipFactory = new WhiteshipFactory(new WhitePartsProFactory());
Ship proShip = proShipFactory.createShip();
System.out.println(proShip.getAnchor().getClass()); // WhiteAnchorPro
System.out.println(proShip.getWheel().getClass()); // WhiteWheelPro
}
}
Abstract Factory 패턴의 장점
확장성:
유지보수 용이성:
ShipPartsFactory에 캡슐화되어 유지보수가 쉬움.의존성 감소:
WhiteshipFactory는 구체적인 부품 클래스(예: WhiteAnchor, WhiteWheel)에 의존하지 않음. ShipPartsFactory)에 의존하므로 의존성이 낮아지고 테스트가 쉬워짐.Abstract Factory 패턴의 단점
복잡성 증가:
범용성이 제한:
구체적인 객체 생성 과정을 추상화:
클라이언트 코드의 의존성 감소:
| 특징 | 팩토리 메소드 패턴 | 추상 팩토리 패턴 |
|---|---|---|
| 중점 | "팩토리를 구현하는 방법 (상속)" | "팩토리를 사용하는 방법 (구성)" |
| 구조 | - 팩토리 메소드를 가진 클래스에서 객체 생성 책임을 하위 클래스로 위임. | - 관련 있는 여러 객체(Product)를 생성하는 팩토리를 정의. |
| 객체 생성 방식 | 단일 객체 생성 (한 번에 하나의 Product 생성). | 계열(related)의 여러 객체를 한꺼번에 생성. |
| 확장성 | 새로운 객체를 추가하려면 하위 클래스를 생성. | 새로운 객체 계열을 추가하려면 팩토리를 구현. |
| 사용 사례 | 구체적인 객체 생성 과정을 하위 클래스에서 오버라이드할 때 사용. | 관련 있는 여러 객체를 함께 생성해야 할 때 사용. |
팩토리 메소드 패턴:
예제:
public abstract class ShapeFactory {
public abstract Shape createShape();
}
public class CircleFactory extends ShapeFactory {
@Override
public Shape createShape() {
return new Circle();
}
}
추상 팩토리 패턴:
예제:
public interface WidgetFactory {
Button createButton();
Checkbox createCheckbox();
}
public class MacOSWidgetFactory implements WidgetFactory {
@Override
public Button createButton() {
return new MacOSButton();
}
@Override
public Checkbox createCheckbox() {
return new MacOSCheckbox();
}
}
javax.xml.xpath.XPathFactory#newInstance()역할: XPath 표현식을 처리하기 위한 팩토리 객체 생성.
사용 예제:
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
public class Main {
public static void main(String[] args) {
XPathFactory factory = XPathFactory.newInstance();
XPath xPath = factory.newXPath();
System.out.println("XPath 객체 생성: " + xPath);
}
}
javax.xml.transform.TransformerFactory#newInstance()javax.xml.parsers.DocumentBuilderFactory#newInstance()FactoryBean 인터페이스FactoryBean 인터페이스를 사용하여 빈 생성 로직을 커스터마이징할 수 있다.예제:
import org.springframework.beans.factory.FactoryBean;
public class MyFactoryBean implements FactoryBean<MyService> {
@Override
public MyService getObject() throws Exception {
// 복잡한 객체 생성 로직
return new MyService();
}
@Override
public Class<?> getObjectType() {
return MyService.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
사용:
FactoryBean을 등록하여 클라이언트가 원하는 빈을 주입받을 수 있음.<bean id="myService" class="com.example.MyFactoryBean" />팩토리 메소드 패턴 vs 추상 팩토리 패턴:
추상 팩토리 패턴의 사용 목적:
실무에서의 사용 사례:
XPathFactory, TransformerFactory, DocumentBuilderFactory.FactoryBean을 통해 복잡한 객체 생성 로직 캡슐화. 추상 팩토리 패턴은 객체 생성의 복잡성을 줄이고, 확장성과 유지보수성을 높이는 데 매우 유용하다.
더 나아가 추상 팩토리 패턴이 SRP를 위반할 수 있다고 생각하여 찾아본 결과,
1) 추상 팩토리 패턴은 SRP를 위반할 수 있음:
2) SRP 준수를 위한 개선:
3) 실무에서의 타협:
4) 최적의 설계는 상황에 따라 다름:
trade off를 잘 따져보고 설계를 해봐야겠다 !