팩토리 메서드 패턴 (Factory Method Pattern)
- 구체적으로 어떤 인스턴스를 만들지는 서브클래스가 정한다.
- 인스턴스를 생성하는 책임을 구체 클래스가 아닌 인터페이스의 메서드로 감싸게 된다.
- 다양한 구현체(Product)가 있고, 그 중에서 특정한 구현체를 만들 수 있는 다양한 팩토리(Creator)를 제공할 수 있다.
장점
- 기존에 인스턴스 생성 코드를 건드리지 않고 비슷한 류의 인스턴스 생성 코드를 만들 수 있어 OCP를 보장한다. (Creator와 Product의 느슨한 결합)
단점
- 클래스가 많아진다.
예시 (as-is)
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 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() + " 다 만들었습니다.");
}
}
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);
}
}
orderShip은 name과 color에 따라 로직이 변경된다.
Ship에 신규 특성이 추가되면, (Yellowship, ...) orderShip 로직이 계속 변경되어 OCP에 어긋나게 된다. 변경에 닫혀있지 않게 된다.
예시 (to-be)
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 Whiteship extends Ship {
public Whiteship() {
setName("whiteship");
setLogo("\uD83D\uDEE5️");
setColor("white");
}
}
public Blackship extends Ship {
public Blackship() {
setName("blackship");
setLogo("⚓");
setColor("black");
}
}
public interface ShipFactory {
default Ship orderShip(String name, String email) {
validate(name, email);
prepareFor(name);
Ship ship = createShip();
sentEmailTo(email, ship);
return ship;
}
Ship createShip();
private void validate(String name, String email) {
// validate
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 + " 만들 준비 중");
}
private void sendEmailTo(String email, Ship ship) {
System.out.println(ship.getName() + " 다 만들었습니다.");
}
}
public class WhiteshipFactory implements ShipFactory {
@Override
public Ship createShip() {
return new Whiteship();
}
}
public class BlackshipFactory implements ShipFactory {
@Override
public Ship createShip() {
return new Blackship();
}
}
public class Client {
public static void main(String[] args) {
Ship whiteship = new WhiteshipFactory.orderShip("Whiteship", "keesun@mail.com");
System.out.println(whiteship);
Ship blackship = new BlackshipFactory.orderShip("Blackship", "keesun@mail.com");
System.out.println(blackship);
}
}
WhiteShip이 있는 와중에, BlackShip을 추가하려고 해도 BlackshipFactory와 Blackship만 생성하면 되기 때문에 WhiteshipFactory와 Whiteship에 코드 변경을 요하지 않는다. OCP를 충족시킨 코드가 되었다.
예시 (to-be) : Client에 DI 적용하기
// ...
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");
}
// shipFactory를 메서드 파라미터로 의존성 주입 (DI)
private void print(ShipFactory shipFactory, String name, String email) {
System.out.println(shipFactory.ordership(name, email));
}
}
활용
- 단순한 팩토리 패턴
- 매개변수의 값에 따라 또는 메서드에 따라 각기 다른 인스턴스를 리턴하는 단순한 버전의 팩토리 패턴
- java.lang.Calendar 또는 java.lang.NumberFormat
- 스프링의 BeanFactory
- Object 타입의 Product를 만드는 BeanFactory라는 Creator이고, 그것에 대한 구현체가 ClassPathXmlApplicationContext, AnnotationConfigApplicationContext이다.
- Product는 Object이고, getBean시 가져오는 값이 ConcreteProduct가 된다.
출처
https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4
'Design Pattern' 카테고리의 다른 글
[Design Pattern] Singleton Pattern (싱글톤 패턴) (0) | 2022.11.12 |
---|---|
[Design Pattern] Composite Pattern (컴포지트 패턴) (0) | 2022.11.07 |
[Design Pattern] 전략 패턴 (Strategy Pattern) (1) | 2022.10.30 |
[Design Pattern] 프록시 (Proxy) (0) | 2022.10.24 |