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

Martin Kim·2022년 3월 16일
0

디자인패턴

목록 보기
4/6
post-thumbnail

의도

  • 객체를 생성하기 위해 인터페이스를 정의하지만, 어떤 클래스의 인스턴스를 생성할지의 대한 결정을 서브클래스가 내리도록 하기 위함이다.

참여자

  • Product: 팩토리 메서드가 생성하는 객체의 인터페이스를 정의
  • ConcreteProduct: Product 클래스에 정의된 인터페이스를 실제로 구현한다.
  • Creator: Product 타입의 객체를 반환하는 팩토리 메서드를 선언한다. Creator 클래스는 팩토리 메서드를 기본적으로 구현하는데, 이 구현에서는 ConcreteProduct 객체를 반환한다. 또한 Product 객체의 생성을 위해 팩토리 메서드를 호출한다.
  • ConcreteCreator: 팩토리 메서드를 재정의하여 ConcreteProduct의 인스턴스를 반환한다.

예시

물류 관리 응용 프로그램을 만들고 있다고 가정하자, 프로그램의 첫번째 버전은 트럭에 의한 운송만 처리할 수 있으므로 대부분 많은 양의 코드는 Truck 클래스 안에 존재한다.

잠시 후, 프로그램이 꽤 유명해졌다. 매일 당신은 해운 회사로부터 해상 물류를 프로그램에 통합해 달라는 수십 건의 요청을 받는다.


이미 코드의 나머지 부분들이 기존 클래스에 연결되어 있는 경우, 프로그램에 새로운 클래스를 추가하는 건 쉽지 않다.

좋은 소식이지만, 대부분의 코드가 Truck 클래스에 연결되어 있다. 프로그램에 Ships 클래스를 추가하려면 전체 코드베이스를 변경해야 한다. 또한 이런 방식으로 프로그램에 다른 교통 수단을 추가하기로 했을 때, 모든 변경을 다시 수행해야 할 수 있다.

결과적으로 운송 객체의 클래스에 따라 앱의 동작을 전환하는 조건 분기문으로 가득 찬 불쾌한 코드가 많아질 것이다.

해결방법

팩토리 메서드 패턴은 연산자를 사용해서 직접적인 객체 생성을 특별한 팩토리 메서드에 대한 호출로 대체할 것을 제안한다. 객체는 물론 new연산자를 통해 생성되지만, 팩토리 메서드 내에서 호출된다. 팩토리 메서드에 의해 반환된 객체는 Product 라고 한다.


서브클래스(RoadLogistics, SeaLogistics)는 팩토리 메소드에 의해 반환되는 객체의 클래스를 변경할 수 있다.

언뜻 보기에는 이 변경이 무의미해 보일 수 있다. 하지만 이제 하위 클래스에서 팩토리 메서드를 override 하고 메서드에 의해 생성되는 제품 클래스를 변경할 수 있다.

하지만 약간의 제한이 있다. 서브클래스는 이러한 제품에 공통 기본 클래스 또는 인터페이스가 있는 경우에만 다른 유형의 제품을 반환할 수 있다. 또한 기본 클래스의 팩토리 메서드는 이 인터페이스로 선언된 리턴 유형을 가져야 한다.

모든 제품은 동일한 인터페이스를 따라야 한다.

예를 들어, TruckShip 클래스 모두 deliver라는 메서드를 선언하는 Transport 인터페이스를 구현해야 한다. 각 클래스는 이 방법을 다르게 구현한다. 트럭은 육로로 화물을 배달하고 선박은 해상으로 화물을 배달한다. 클래스의 팩토리 메서드 RoadLogistics는 트럭 객체를 반환하지만, SeaLogistics 클래스의 팩토리 메서드는 선박 객체를 반환한다.

모든 제품 클래스가 공통 인터페이스를 구현하는 해당 객체를 손상시키지 않고 클라이언트 코드에 전달할 수 있다.

팩토리 메서드를 사용하는 코드(클라이언트 코드)는 다양한 서브 클래스에서 반환된 실제 제품간의 차이를 모른다. 클라이언트는 모든 제품을 Transport 라고 추상적으로 취급하기 때문이다. 클라이언트 코드는 모든 Transport 객체에 메서드가 있어야 한다는 것을 알고 deliver 메서드가 있다는 것을 알지만, 정확히 어떻게 작동하는지는 클라이언트에게 중요하지 않다.

객체지향 프로그래밍의 특징 중 다형성(Polymorphism)을 활용하는 패턴이라고 생각된다!

구현방법

  1. Product는 작성자와 해당 서브 클래스가 생성할 수 있는 모든 객체에 공통적인 인터페이스를 선언한다.
  2. Concrete ProductProduct 인터페이스의 다른 구현이다.
  3. Creator 클래스는 새 Product 객체를 반환하는 팩토리 메서드를 선언한다. 이 메서드의 리턴 타입이 Product 인터페이스와 일치해야 한다. 이 팩토리 메서드를 추상으로 선언해서 모든 서브 클래스가 자체 버전의 메서드를 구현하도록 할 수 있다. 대신, 기본 팩토리 메서드가 일부 기본 product 유형을 리턴할 수 있다.
  4. ConcreteCreator는 기본 팩토리 메서드를 재정의하므로 다른 유형의 제품을 반환한다. 팩토리 메서드는 항상 새 인스턴스를 만들 필요가 없다. 또한 캐시, 객체 풀 또는 다른 소스에서 기존 객체를 반환시킬 수 있다.

활용성

  • 어떤 클래스가 자신이 생성해야 하는 객체의 클래스를 예측할 수 없을 때
  • 생성할 객체를 기술하는 책임을 자신의 서브클래스가 지정했으면 할 때
  • 객체 생성의 책임을 몇 개의 보조 서브클래스 가운데 하나에게 위임하고, 어떤 서브클래스가 위임자인지에 대한 정보를 국소화시키고 싶을 때
  • 라이브러리나 프레임워크의 사용자에게 내부 구성 요소를 확장하는 방법을 제공하고자 할 때

장점

  • 생성자와 구체적인 Product의 긴밀한 결합을 피할 수 있다.
  • 단일 책임 원칙(Single Responsibility Principle)을 지킬 수 있다. 제품의 생성 코드를 프로그램의 한 위치로 이동시켜 코드를 더 쉽게 지원할 수 있다.
  • 개방/폐쇄 원칙()을 지킬 수 있다. 기존 클라이언트의 코드는 손상시키지 않으면서, 새로운 유형의 제품을 프로그램에 도입할 수 있다.

단점

  • 패턴을 구현하기 위해 많은 새 하위 클래스를 도입해야 하므로 코드가 복잡해질 수 있다. 가장 좋은 시나리오는 생성자 클래스의 기존 계층 구조에 패턴을 도입하는 경우이다.

In Swift

import XCTest

/// Product 클래스의 새 개체를 반환해야 하는 팩토리 메서드를 선언한다.
protocol Creator {

    // 추상 객체 Product를 반환하는 factory 메서드
    func factoryMethod() -> Product

    // 서브 클래스는 팩토리 메서드를 오버라이딩하고, 다른 유형의 제품을 반환해서 비즈니스 로직을 간접적으로 변경할 수 있다.
    func someOperation() -> String
}

// 이 extension을 통해 Creator의 기본 동작을 구현한다. 서브클래스 에서 오버라이딩 될 수 있다.
extension Creator {

    func someOperation() -> String {
        // Call the factory method to create a Product object.
        let product = factoryMethod()

        // Now, use the product.
        return "Creator: The same creator's code has just worked with " + product.operation()
    }
}

// 제품의 유형을 변경하기 위해 팩토리 메서드를 오버라이딩 한다.
class ConcreteCreator1: Creator {

    // 비록 구체 프로덕트가 실제로 여기서 반환되더라도 시그니처는 항상 추상 프로덕트를 리턴한다.
    // 그래서 Creator는 구체적인 프로덕트 클래스와 독립될 수 있다.
    public func factoryMethod() -> Product {
        return ConcreteProduct1()
    }
}

class ConcreteCreator2: Creator {

    public func factoryMethod() -> Product {
        return ConcreteProduct2()
    }
}

/// Product 프로토콜은 모든 구체적인 제품이 구현해야 하는 작업을 선언한다.
protocol Product {

    func operation() -> String
}

/// Concrete Products는 Product 프로토콜의 다양한 구현을 제공한다.
class ConcreteProduct1: Product {

    func operation() -> String {
        return "{Result of the ConcreteProduct1}"
    }
}

class ConcreteProduct2: Product {

    func operation() -> String {
        return "{Result of the ConcreteProduct2}"
    }
}

/// 클라이언트 코드는 기본 프로토콜을 통해서라도 구체적인 작성자의 인스턴스와 함께 작동한다.
class Client {
    // ...
    static func someClientCode(creator: Creator) {
        print("Client: I'm not aware of the creator's class, but it still works.\n"
            + creator.someOperation())
    }
    // ...
}

/// Let's see how it all works together.
class FactoryMethodConceptual: XCTestCase {

    func testFactoryMethodConceptual() {

        /// The Application picks a creator's type depending on the
        /// configuration or environment.

        print("App: Launched with the ConcreteCreator1.")
        Client.someClientCode(creator: ConcreteCreator1())

        print("\nApp: Launched with the ConcreteCreator2.")
        Client.someClientCode(creator: ConcreteCreator2())
    }
}

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

profile
학생입니다

0개의 댓글