Factory Method[Design Pattern]

SnowCat·2023년 2월 14일
0

Design Pattern

목록 보기
2/23
post-thumbnail

Factory Method : 부모 클래스에서 객체들을 생성할 수 있는 인터페이스를 제공하지만, 자식 클래스들이 생성된 객체들의 유형을 변경할 수 있도록 하는 생성 패턴

동기

  • 물류관리앱을 개발중이라 가정해보자. 앱의 초기버전은 트럭운송만 처리가 가능해 대부분 코드가 Truck 클래스에 있는 상황이다.
  • 이 상황에서 해운 회사들로부터 해상 물류기능 추가, 즉 Ship 클래스의 추가를 요청받았다고 해보자.
  • 앱의 Ship 클래스를 추가하려면 전체 코드베이스를 변경해야 하는 문제가 생기게 된다.
  • 이는 다른 유형의 클래스를 추가해도 마찬가지이며, 코드를 하나로 쓴다면 많은 조건문들에 의해 앱의 행동이 바뀌는 복잡한 구조가 될 것이다.

해결책

  • new 연산자를 사용하는 객체 생성 호출을 팩토리 메서드에 대한 호출로 대채할 것
  • 팩토리 메서드 작성을 통해 자식 클래스에서 팩토리 메서드를 오버라이딩하고 메서드에 의해 생성디는 제품들의 클래스 변경이 가능해짐
  • 클래스의 공통 기초 클래스나 인터페이스가 있어야 하며, 팩토리 메서드의 반환 유형을 알고있어야 함

    다음 예시에서는 Transport 인터페이스의 deliver 메서드를 Truck와 ship이 모두 구현해야하지만, 구현 내용은 서로 다름
  • 팩토리 메서드를 사용하는 코드를 클라이언트 코드라고도 부름
  • 클라이언트 코드는 자식 메서드간의 차이에 대해서 알 수 없음, 즉 예시에서 deliver 메서드를 가지는 것만 알고있지, 이것이 어떻게 작동하는지는 모름

구조

  1. Product 인터페이스들을 선언해 실제 Product 구현
  2. 제품 객체들을 반환하는 Creator 팩토리 메스드 선언, 이 때 createProduct()타입은 Product 인터페이스와 일치해야하며, abstract를 선언해 자식 클레스들이 Creator 클래스대로 구현하도록 강제해주어야 함
  3. 자식으로 들어오는 Creator들은 팩토리 메서드를 오버라이드해서 다른 유형의 product를 반환함
// 팩토리 메서드
abstract class Creator {
    public abstract factoryMethod(): Product;
    public someOperation(): string {
        const product = this.factoryMethod();
        return `Creator: The same creator's code has just worked with ${product.operation()}`;
    }
}

// 자식 클래스들은 팩토리 메서드에 맞추어 구현
class ConcreteCreator1 extends Creator {
    public factoryMethod(): Product {
        return new ConcreteProduct1();
    }
}

class ConcreteCreator2 extends Creator {
    public factoryMethod(): Product {
        return new ConcreteProduct2();
    }
}

// 결과물에 대한 인터페이스
interface Product {
    operation(): string;
}

// 인터페이스에 맞추어 product들 구현
class ConcreteProduct1 implements Product {
    public operation(): string {
        return '{Result of the ConcreteProduct1}';
    }
}

class ConcreteProduct2 implements Product {
    public operation(): string {
        return '{Result of the ConcreteProduct2}';
    }
}

function clientCode(creator: Creator) {
    // ...
    console.log('Client: I\'m not aware of the creator\'s class, but it still works.');
    console.log(creator.someOperation());
    // ...
}

/*
App: Launched with the ConcreteCreator1.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct1}
*/
console.log('App: Launched with the ConcreteCreator1.');
clientCode(new ConcreteCreator1());
console.log('');

/*
App: Launched with the ConcreteCreator2.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct2}
*/
console.log('App: Launched with the ConcreteCreator2.');
clientCode(new ConcreteCreator2());
console.log('');

적용

  • 코드가 함께 작동해야 하는 객체들의 정확한 유형들과 의존관계들을 미리 모르는 경우에 사용
    • 제품 생성 코드를 실제로 사용하는 코드와 분리 -> 생성 코드의 확장이 쉬워짐
    • 앱에 새로운 제품을 추가할 때 새로운 자식 클래스를 생성하고 팩토리 메서드를 오버라이딩 하기만 하면 됨
  • 라이브러리, 프레임워크 사용자들에게 내부 컴포넌트를 확장하는 방법을 제공하고자 할 때 사용
    • 프레임워크 전체에서 컴포넌트들을 생성하는 코드를 단일 팩토리 메서드로 줄이고, 이를 오버라이드 할 수 있도록 해주면 됨
    • 가령 원형 버튼을 생성해야 하는데, 프레임워크에는 사각형 버튼만 있다 생각해보자.
    • 프레임워크 클래스의 자식 클래스로 원형 버튼을 생성하는 클래스를 만들자. 그 다음 버튼을 생성하는 부분(팩토리 메서드)를 동그란 버튼이 생성되게 오버라이딩 하고 버튼 객체를 반환하게 하자.
    • 이제 원래의 프레임워크 대신 미리 만들어둔 클래스를 사용하면 된다.
  • 기존 객체들을 재구축하는 대신 재사용하여 시스템 리소스를 절약하고 싶을 때 사용
    • 기존에 사용한 객체를 재사용하기 위해서는 생성된 객체를 찾고, 객체를 반환하거나 새로운 객체를 생성하는 코드를 작성해야함
    • 이를 객체의 생성자에 배치해두는 것이 가장 적합함 but 생성자는 항상 새로운 객체들을 반환해야 함
    • 따라서 새로운 객체들을 생성하고 객체를 재사용 가능한 일반적인 메스드, 즉 팩토리 메서드가 필요함

구현 방법

  1. 모든 구현되는 객체는 같은 인터페이스를 따르도록 해야 하며, 인터페이스는 모든 객체에서 의미가 있는 메서드를 선언해야 함
  2. 인터페이스를 따르는 객체를 반환하는 클래스 내부에 빈 팩토리 메서드 추가
  3. 2번의 클래스에서 객체 생성자에 대한 모든 참조들을 팩토리 메소드에 집어넣음
  4. 객체 생성자들인 자식 클래스 집합을 생성한 다음 자식 클래스에서 팩토리 메서드를 오버라이딩하고 팩토리 메서드 코드를 정리해 나감
  5. 만약 객체가 너무 많은 경우 원래 사용하던 제어 매개변수를 재사용할 수 있음
    가령 Mail 클래스에 GrounMail이 있고, GroundMail은 Truck과 Train을 모두 사용 가능하다면, TrainMail을 따로 분리할수도 있지만, GroundMail 클래스의 팩토리 메서드에 전달인자를 전달해도 됨
  6. 팩토리 메서드 정리가 끝나고 팩토리 메서드가 비어있으면, 해당 팩토리 메서드는 추상화 가능, 만약 비어있지 않으면, 나머지가 메서드의 기본 행동값이 됨

장단점

  • 객체와 생성자들을 분리시킬 수 있음
  • 생성자를 분리시켜 단일 책임 원칙을 지키게 할 수 있음
  • 객체 코드를 건드리지 않고 새로운 유형의 객체를 도입할 수 있게 되기 때문에 개방/폐쇄 원칙을 지키게 할 수 있음
  • 패턴을 구현하기 위해 많은 새로운 자식 클래스를 도입해야 하기에 코드가 더 복잡해질 수 있음

출처:
https://refactoring.guru/ko/design-patterns/factory-method

profile
냐아아아아아아아아앙

0개의 댓글