-
팩토리 메소드 패턴 (Factory Method Pattern)디자인 패턴 2022. 6. 14. 11:08
팩토리 메소드 패턴?
- 조건에 따른 객체 생성을 팩토리 클래스로 위임하여, 팩토르 클래스에서 객체를 생성하는 패턴
- 구체적으로 어떤 인스턴스를 만들지는 서브 클래스가 정한다.
- 다양한 구현체가 있고, 그 중에서 특정한 구현체를 만들 수 있는 다양한 팩토리 제공할 수 있다.
- 객체의 생성 코드를 별도의 클래스/메서드로 분리함으로써 객체 생성의 변화에 대비하는 데 유용하다.
먼저, 팩토리 메소드 패턴이 적용되지 않은 코드를 살펴보자.
public class Client { public static void main(String[] args) { // white_ship 생성 Ship white_ship = ShipFactory.orderShip("white_ship", "hyoklee@naver.com"); System.out.println(white_ship); System.out.println(" ---------------- " ); // black_ship 생성 Ship black_ship = ShipFactory.orderShip("black_ship", "hyoklee@naver.com"); System.out.println(black_ship); } }
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("white_ship")) { ship.setLogo("\uD83D\uDEE5️"); } else if (name.equalsIgnoreCase("black_ship")) { ship.setLogo("⚓"); } // coloring if (name.equalsIgnoreCase("white_ship")) { //대소문자 구분 x ship.setColor("white"); } else if (name.equalsIgnoreCase("black_ship")) { ship.setColor("black"); } 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() + " 다 만들었습니다."); } }
하나(Ship, ShipFactory)에 모든 기능을 다 담기에는 확장에 열려있고 변경에 닫혀야 한다(OCP)는 객체지향 설계 원칙을 위배하게 된다.
→ 새로운 객체가 추가될때마다(혹은 새로운 기능이 추가될때마다) 기존코드에 변경이 일어나게 됨.
그럼 어떻게해야 기존 코드를 건들지 않고 새로운 인스턴스가 생성되게끔 할 수 있을까?
아래 코드를 확인하자.
팩토리 메서드 적용
Creator
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 class BlackshipFactory implements ShipFactory { @Override public void sendEmailTo(String email, Ship ship) { System.out.println(email + " 님, 주문하신 " + ship.getName() + " 이 완성되었습니다."); } @Override public Ship createShip() { return new Blackship(); } }
public class WhiteshipFactory implements ShipFactory { @Override public void sendEmailTo(String email, Ship ship) { System.out.println(email + " 님, 주문하신 " + ship.getName() + " 이 완성되었습니다."); } @Override public Ship createShip() { return new Whiteship(); } }
Product
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 Blackship extends Ship { public Blackship() { setName("blackship"); setColor("black"); setLogo("⚓"); } }
public class Whiteship extends Ship { public Whiteship() { setName("whiteship"); setLogo("\uD83D\uDEE5️"); setColor("white"); } }
App
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)); } }
중요한건, Product 군 (Ship)에도 계층구조가 있고 Creator 군(ShipFactory)에도 계층구조가 있어서 팩토리 안에서 구체적인 제품을 만들어 내는것이 중요한 것이다.
이와 같이 팩토리 메소드 패턴을 적용하여 코드를 작성하면 OCP 원칙을 지킬 수 있게된다.
팩토리 메서드 패턴 실무에선 어떻게 쓰이나?
- 단순한(simple) 팩토리 패턴 (팩토리 메서드 패턴과 조금 다르다.)
- 지금까지 살펴봤던것 처럼 creator 라는 인터페이스가 있고, 그 인터페이스를 구현한 여러개의 creator 구현체들이 있는게 아니라 단 하나의 구현체를 두고 그 구현체 하나에서 여러 다른 구체적인 product 들을 만들어주는 모양이다.
- 매개변수의 값에 따라 또는 메서드에 따라 각기 다른 인스턴스를 리턴하는 단순한 버전의 팩토리 패턴
- ex) java.lang.Calendar , java.lang.NumberFormat
- 스프링 BeanFactory
- Object 타입의 Product 를 만드는 BeanFactory 라는 Creator
- BeanFactory 가 팩토리 패턴을 구현한 구현체 라고 볼 수 있다.
- BeanFactory 가 Creator 인터페이스이고 이를 구현한것이 ClassPathXmlApplicationContext, AnnotationConfigApplicationContext 인데, 이것이 ConcreteCreator 가 되는것이다.
- 여기서 넘겨주는 Product 는 Object 타입이고 이를 넘겨 받는 인스턴스가 ConcreateProduct 가 된다.
- 컴포넌트 스캔, bean 설정 어노테이션, 파일 등의 bean 들이 결국 ConcreateProduct 에 해당되는 것이다.
- ioc 컨테이너에 해당되는 클래스가 bean 팩토리 인데 이부분에 녹아져 있는 패턴이 팩토리 메서드 패턴이다.
정리
1) 팩토리 메서드 패턴이 뭐에요?
→ 구체적으로 어떤 것을 만들지는 서브 클래스가 정한다!!
특정 클래스를 통해 여러 종류의 구체적인 인스턴스를 만들어낼때 해당 인스턴스가 계속해서 추가가 된다면 해당 클래스의 복잡도는 계속해서 증가할 것이다. 이럴경우 객체지향 설계원칙 중 확장엔 열려있고, 변경엔 닫혀있어야 한다는 원칙을 위배하게 된다.
따라서, 특정 구현체를 만들수 있는 펙토리를 정의하여 인터페이스와 하위 클래스들을 구성하고, 구현체들 또한 인터페이스와 하위 클래스로 구성하여 구체적으로 어떤 클래스를 만들지는 하위 클래스에서 정의되도록 나눠 놓는 패턴이다.
2) 팩토리 메소드 패턴을 적용했을 때의 장점과 단점은?
- 장점
- 확장에 열려있고 변경에 닫혀있는 객체지향 원칙을 적용해서 기존 코드를 건들이지 않고, 같은 류의 새로운 인스턴스를 다른 방법으로 확장이 가능하다.
- product 와 creator 간에 커플링은 느슨하게 가져갔기 때문이다. ⇒ 느슨한 결합 creator(factory = 인스턴스를 만드는 주체) 와 product (인스턴스) 간의 관계를 느슨하게 가져갔기 때문에 확장에 열려있고 변경에 닫혀있는 구조가 가능해진것이다.
- 기존 코드를 계속해서 건들지 않고 확장할 수 있기 때문에 코드가 훨씬 더 간결해지고 복잡해지지 않는다.
- 단점
- 각자의 역할을 나누다보니까 클래스가 늘어나는 단점이 있다.
3) 확장에 열려있고 변경에 닫혀있는 객체 지향 원칙을 설명하세요.
- 기존코드를 변경하지 않으면서 새로운 기능을 확장할수 있는 구조를 만드는것
4) 자바 8에 추가된 default 메소드에 대해 설명하세요.
- 인터페이스에 구현체 메서드를 정의할 수 있게된다.
- 인터페이스에 기본 구현체를 만들 수 있고, 그렇게 구현된 메서드를 구현 클래스나 상속 받는 인터페이스에서도 사용할 수 있다.
- 그렇기 때문에 자바 8 이후에는 추상클래스는 잘사용하지 않고 인터페이스를 주로 많이 사용한다. (추상클래스에서 할 수 있었던것을 인터페이스에서도 할 수 있기 때문이다.)
- 자바9 부터는 private 메서드도 지원하고 있다.
'디자인 패턴' 카테고리의 다른 글
프로토타입 (Prototype) 패턴 (0) 2022.06.29 추상 팩토리 (Abstract factory) 패턴 (0) 2022.06.19 싱글톤 패턴 (Singleton Pattern) (0) 2022.06.04 프록시(proxy) 패턴 (0) 2022.05.03 발행-구독 패턴 (0) 2022.04.30