[Design Pattern] 팩토리 메소드 패턴(Factory Pattern)

뚜비·2023년 3월 14일
0

Design Pattern

목록 보기
3/9

💻 팩토리 메소드 패턴 구현 코드


팩토리 메소드 패턴

"Define an interface for creating an object, but let subclasses decide which class to instantiate. The Factory method lets a class defer instantiation it uses to subclasses."
"객체를 생성하기 위해 인터페이스를 정의하지만, 어떤 클래스의 인스턴스를 생성할지에 대한 결정은 서브클래스가 내리도록 합니다."
-Gang Of Four-

  • 객체 생성 부분을 추상화한 패턴. 상위 클래스에 알려지지 않은 클래스에 대해 하위 클래스에서 어떤 객체를 생성할 지 결정하도록 하는 패턴 즉 , 공장처럼 특정 객체를 만들어서 반환!
  • 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정
  • 상위 클래스는 추상메소드 create를 정의하고, 하위 클래스가 이를 재정의하여 객체를 생성한다. 이 때의 create메소드를 팩토리 메소드
  • 추가로, 하위 클래스는 상위 클래스를 상속하지만 확장하지는 않는다는 점에 유의해야 한다.
  • GoF 디자인 패턴 중 생성 패턴에 해당

🤔언제 사용하나요?
1. 어떤 클래스가 자신이 생성해야 하는 객체의 클래스를 알지 못할 때
2. 객체의 생성을 서브클래스에게 위임하고 싶을 때


구조

  • Creator
    : 상위 클래스를 정의, Product를 생성하는 인터페이스(API)를 가짐
  • ConcreteCreator
    : Creator를 상속받은 하위 클래스
    : factoryMethod()를 override하여 정의하고 ConcreteProduct의 인스턴스를 반환, 즉 어떤 타입의 객체를 생성해야 하는지를 결정
  • Product
    : 어떤 타입의 객체, Product가 가져할 인터페이스(API)를 가짐
  • ConcreteProduct
    : 서브클래스(ConcreteCreator)에 따라 결정되는 구체적인 타입의 객체


장점

  • 느슨한 결합 : 상위 클래스와 하위 클래스가 분리되기 때문에
  • 유연성 증가 : 상위 클래스에서는 인스턴스 생성 방식에 대해 전혀 알 필요가 없음. 즉, 객체를 생성하는 부분을 분리시켜 클라이언트(객체 생성을 호출하는 부분 ex.Main)와 결합도(의존성)를 낮춘다.
  • 유지 보수성 증가 : 객체 생성 로직이 따로 떼어져 있기 때문에 코드를 리팩터링하더라도 한 곳만 고칠 수 있음 (예를 들어 객체를 추가/수정을 한다면 클라이언트 부분을 수정하지 않고 객체를 생성하는 부분만 건들면 된다.)

▶ 객체지향 디자인패턴 원칙 중 Open - Close Principle
즉, 변화가 일어날 수 있는 객체 생성 부분을 한 곳에서 관리(==변화에 대해서 닫힘)하여 수정하는데 편함(== 확정에 대해 열림)



구현

  • Room (추상 클래스, Product에 해당)
    ⌞ MagicRoom (구체적인 product)
    ⌞ OrdinaryRoom (구체적인 product)
  • MazeGame (추상 클래스, Room을 생성하는 abstract factory method를 가짐)
    ⌞ MagicMazeGame (MagicRoom을 생성하는 factory method를 가짐)
    ⌞ OrdinaryMazeGame (OrdinaryRoom을 생성하는 factory method를 가짐)

🤔 인터페이스 사용 해도 되나요?
사용해도 된다. 다만 인터페이스는 추상 메서드만 작성 가능하기 때문에 일련의 고정적인(공통적인) 과정을 구현한 메서드가 필요하면 추상 클래스를 사용해야 한다.


  1. Product 추상클래스와 ConcreteProcut 클래스
/* Room 관련 클래스*/
public abstract class Room { // 추상클래스 
    abstract void connect(Room room); // 방을 연결해주는 추상 method 
}

public class MagicRoom extends Room { // 구체적인 타입의 클래스 
    public void connect(Room room) {}
}

public class OrdinaryRoom extends Room { // 구체적인 타입의 클래스
    public void connect(Room room) {}
}

  1. Creator 추상클래스와 ConcreteCretor 클래스
  • makeRoom()이 factory Method
/* Factory 관련 클래스 */

public abstract class MazeGame { // 추상 Factory 클래스 
     private final List<Room> rooms = new ArrayList<>();

     public MazeGame() { // 게임 판이 열리면 방이 2개 생기고 서로 연결해줌 
          Room room1 = makeRoom();
          Room room2 = makeRoom();
          room1.connect(room2);
          rooms.add(room1);
          rooms.add(room2);
     }

     abstract protected Room makeRoom(); // abstract factory method
}

public class MagicMazeGame extends MazeGame { // 구체적인 Facotry 클래스 
    @Override
    protected MagicRoom makeRoom() {
        return new MagicRoom();
    }
}

public class OrdinaryMazeGame extends MazeGame { // 구체적인 Facotry 클래스  
    @Override
    protected OrdinaryRoom makeRoom() {
        return new OrdinaryRoom();
    }
}

  1. Demo
// 클라이언트 부분, 객체 생성 호출 부분 
public class Main {
    public static void main(String[] args) {
        MazeGame ordinaryGame = new OrdinaryMazeGame();
		MazeGame magicGame = new MagicMazeGame();
    }
}


🤔 Simple Factory 패턴

즉, 객체 생성 부분을 한 클래스에서 담당하는 것, 즉 한 Factory 클래스에서 각기 다른 Product 클래스를 생성한다.

  1. Product 추상클래스와 ConcreteProcut 클래스
/*Product*/
public interface Shape { // 객체 정의 인터페이스 
   void draw();
}

public class Rectangle implements Shape { // 구현 클래스 

   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}

public class Square implements Shape { // 구현 클래스 

   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}

public class Circle implements Shape { // 구현 클래스 

   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }
}

  1. Creator 클래스, ConcreteProduct 바로 생성
  • facotry method 대신 객체 type으로 Product 생성
/*Factory*/
public class ShapeFactory {
	
   //use getShape method to get object of type shape 
   public Shape getShape(String shapeType){
      if(shapeType == null){
         return null;
      }		
      if(shapeType.equalsIgnoreCase("CIRCLE")){
         return new Circle();
         
      } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
         
      } else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }
      
      return null;
   }
}

  1. Demo
public class FactoryPatternDemo { // 객체 생성 호출 부분

   public static void main(String[] args) {
      ShapeFactory shapeFactory = new ShapeFactory();

      //get an object of Circle and call its draw method.
      Shape shape1 = shapeFactory.getShape("CIRCLE");

      //call draw method of Circle
      shape1.draw();

      //get an object of Rectangle and call its draw method.
      Shape shape2 = shapeFactory.getShape("RECTANGLE");

      //call draw method of Rectangle
      shape2.draw();

      //get an object of Square and call its draw method.
      Shape shape3 = shapeFactory.getShape("SQUARE");

      //call draw method of square
      shape3.draw();
   }
}

// Inside Circle::draw() method.
// Inside Rectangle::draw() method.
// Inside Square::draw() method.

Abstract Factory 패턴

나중에 추가 정리 필요!!



EXAMPLE

JavaScript

  • new Object로 간단하게 구현
const num = new Object(42)
const str = new Object('abc')
num.constructor.name; // 숫자 전달 시 Number 객체 생성 
str.constructor.name; // 문자열 전달 시 String 객체 생성 

즉, 전달받은 값에 따라 다른 객체를 생성하며 인스턴스의 타입 등을 정함.


  • Coffee Facotry에서 Latte 생산
class CoffeeFactory {
    static createCoffee(type) {
        const factory = factoryList[type]
        return factory.createCoffee()
    }
}   
class Latte {
    constructor() {
        this.name = "latte"
    }
}
class Espresso {
    constructor() {
        this.name = "Espresso"
    }
} 

class LatteFactory extends CoffeeFactory{
    static createCoffee() {
        return new Latte()
    }
}
class EspressoFactory extends CoffeeFactory{
    static createCoffee() {
        return new Espresso()
    }
}
const factoryList = { LatteFactory, EspressoFactory } 
 
 
const main = () => {
    // 라떼 커피를 주문한다.  
    const coffee = CoffeeFactory.createCoffee("LatteFactory")  
    // 커피 이름을 부른다.  
    console.log(coffee.name) // latte
}
main()
  • CoffeeFactory라는 상위 클래스가 중요한 뼈대를 결정, 하위 클래스인 LatteFactory가 구체적인 내용을 결정
    의존성 주입, CoffeeFactory에서 LatteFactory의 인스턴스를 생성하는 것이 아닌 LatteFactory에서 생성한 인스턴스를 CoffeeFactory에 주입하고 있기 때문
  • CoffeeFactory 클래스에서 createCoffee() 메서드의 static 키워드
    정적메서드로 정의, 클래스를 기반으로 객체를 만들지 않고 호출이 가능하며 해당 메서드에 대한 메모리 할당을 한번만 할 수 있음

Java 언어로 배우는 디자인 패턴 입문 예제

  • Product 인터페이스
abstract class Product {
    public abstract void use();
}
  • Product
class IDCard extends Product {
    private String owner;

    public IDCard(String owner) {
        System.out.println(owner + "의 카드를 만듭니다.");
        this.owner = owner;
    }

    @Override
    public void use() {
        System.out.println(owner + "의 카드를 사용합니다.");
    }

    public String getOwner() {
        return owner;
    }
}
  • Creator 추상클래스
  • createProduct()는 factory method
abstract class Factory {
    public final Product create(String owner) {
        Product p = createProduct(owner);
        registerProduct(p);
        return p;
    }
    protected abstract Product createProduct(String owner);
    protected abstract void registerProduct(Product p);
}
  • ConcreteCreator 클래스
class IDCardFactory extends Factory {
    private List<String> owners = new ArrayList<>();

    @Override
    protected Product createProduct(String owner) {
        return new IDCard(owner);
    }

    @Override
    protected void registerProduct(Product p) {
        owners.add(((IDCard) p).getOwner());
    }

    public List<String> getOwners() {
        return owners;
    }
}
  • Demo
Factory factory = new IDCardFactory();
Product card1 = factory.create("홍길동");
Product card2 = factory.create("이순신");
Product card3 = factory.create("강감찬");
card1.use();
card2.use();
card3.use();

  • 결과
홍길동의 카드를 만듭니다.
이순신의 카드를 만듭니다.
강감찬의 카드를 만듭니다.
홍길동의 카드를 사용합니다.
이순신의 카드를 사용합니다.
강감찬의 카드를 사용합니다.

헤드 퍼스트 디자인 패턴의 예제

  • Pizza가 Product 인터페이스
interface Pizza {
    public void prepare();
    public void bake();
    public void box();
}
  • PizzaStore라는 Creator 추상 클래스
abstract class PizzaStore {
    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);    // factory method 사용
        pizza.prepare();
        pizza.bake();
        pizza.box();
        return pizza;
    }

    // factory method
    abstract Pizza createPizza(String type);
}
  • ConcreteCreator
class NYPizzaStore extends PizzaStore {
    @Override
    Pizza createPizza(String type) {
        if ("cheese".equals(type)) {
            return new NYStyleCheesePizza();
        } else if ("pepperoni".equals(type)) {
            return new NYStylePepperoniPizza();
        }
        return null;
    }
}

class ChicagoPizzaStore extends PizzaStore {
    @Override
    Pizza createPizza(String type) {
        if ("cheese".equals(type)) {
            return new ChicagoStyleCheesePizza();
        } else if ("pepperoni".equals(type)) {
            return new ChicagoStylePepperoniPizza();
        }
        return null;
    }
}
  • Demo
PizzaStore nyStore = new NYPizzaStore();
PizzaStore chicagoStore = new ChicagoPizzaStore();

Pizza pizza = nyStore.orderPizza("cheese");
Pizza pizza1 = chicagoStore.orderPizza("pepperoni");


😎 참고자료

면접을 위한 CS 전공지식 노트
Java 언어로 배우는 디자인 패턴 입문
GOF Design Pattern
디자인패턴 이해하기
Factory method pattern 위키백과
[Design Pattern] GoF(Gang of Four) 디자인 패턴
Design Pattern - Factory Pattern
팩토리 패턴, 도대체 왜 쓰는거야? - 기본 이론편
팩토리 메소드 패턴 - 기계인간

profile
SW Engineer 꿈나무 / 자의식이 있는 컴퓨터

0개의 댓글