Factory Method

chris·2021년 1월 1일
0

design pattern

목록 보기
1/11

Intent

Factory Method는 슈퍼 클래스에서 개체를 생성하기위한 인터페이스를 제공하지만 하위 클래스가 생성 될 개체의 유형을 변경할 수 있도록하는 생성 디자인 패턴입니다.

Problem

물류 관리 애플리케이션을 만들고 있다고 상상해보십시오. 앱의 첫 번째 버전은 trucks으로 만 운송을 처리 할 수 있으므로 코드의 대부분은 Truck 클래스 내에 있습니다.

잠시 후 앱이 꽤 유명해집니다. 매일 해상 운송 회사로부터 해상 물류를 앱에 통합하라는 수십 건의 요청을 받습니다.

좋은 소식 이지요? 하지만 코드는 어떻습니까? 현재 대부분의 코드는Truck 클래스에 연결되어 있습니다. 앱에 Ships를 추가하려면 전체 코드베이스를 변경해야합니다. 또한 나중에 앱에 다른 유형의 교통 수단을 추가하기로 결정한 경우 이러한 모든 변경을 다시 수행해야 할 수 있습니다.

결과적으로 교통 개체의 클래스에 따라 앱의 동작을 전환하는 조건으로 가득 찬 매우 불쾌한 코드가 생성됩니다.

Solution

Factory Method pattern은 직접 개체 생성 호출 (new 연산자 사용)을 특수 팩토리 메서드에 대한 호출로 대체 할 것을 제안합니다. Don’t worry: 객체는 여전히 new 연산자를 통해 생성되지만 팩토리 메서드 내에서 호출됩니다. 팩토리 메소드에서 반환 된 객체를 종종 제품이라고합니다.

언뜻보기에이 변경 사항은 무의미 해 보일 수 있습니다. 프로그램의 한 부분에서 다른 부분으로 생성자 호출을 이동했습니다. 그러나 이것을 고려하십시오. 이제 하위 클래스에서 팩토리 메서드를 재정의하고 메서드에 의해 생성되는 제품의 클래스를 변경할 수 있습니다.

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

For example, TruckShip 클래스 모두deliver라는 메소드를 선언하는Transport 인터페이스를 구현해야합니다. 각 클래스는이 방법을 다르게 구현합니다. 트럭은 육지로 화물을 배달하고 선박은 바다로 화물을 배달합니다. RoadLogistics 클래스의 Factory Method는 트럭 객체를 반환하는 반면SeaLogistics 클래스의 Factory Method는 배송을 반환합니다.

Factory Method(종종 클라이언트 코드라고 함)를 사용하는 코드는 다양한 하위 클래스에서 반환되는 실제 제품 간의 차이를 보지 못합니다. 클라이언트는 모든 제품을 추상 전송으로 취급합니다. 클라이언트는 모든transport 객체에deliver 메소드가 있어야한다는 것을 알고 있지만 정확히 어떻게 작동하는지는 클라이언트에게 중요하지 않습니다.

Structure


1. Product는 작성자와 해당 하위 클래스가 생성할 수 있는 모든 개체에 공통적인 인터페이스를 선언합니다.

2. Concrete Products는 제품 인터페이스의 다른 구현입니다.

3. Creator 클래스는 새 제품 개체를 반환하는 팩토리 메서드를 선언합니다. 이 메서드의 반환 유형이 제품 인터페이스와 일치하는 것이 중요합니다.

Factory Method를 추상으로 선언하여 모든 하위 클래스가 자체 버전의 메서드를 구현하도록 할 수 있습니다. 대안으로, 기본 팩토리 메소드는 일부 기본 제품 유형을 리턴할 수 있습니다.

이름에도 불구하고 제품 제작은 제작자의 주요 책임이 아닙니다. 일반적으로 Creator 클래스에는 제품과 관련된 핵심 비즈니스 로직이 이미 있습니다. 팩토리 메소드는 이 로직을 구체적인 제품 클래스에서 분리하는 데 도움이 됩니다. 다음은 유추입니다. 대규모 소프트웨어 개발 회사에는 프로그래머를 위한 교육 부서가 있을 수 있습니다. 그러나 회사 전체의 주요 기능은 프로그래머를 생산하는 것이 아니라 여전히 코드를 작성하는 것입니다.

4. Concrete Creators는 기본 팩토리 메서드를 재정의하므로 다른 유형의 제품을 반환합니다.

팩토리 메서드는 항상 새 인스턴스를 만들 필요가 없습니다. 또한 캐시, 개체 풀 또는 다른 소스에서 기존 개체를 반환할 수도 있습니다.

Pseudocode

이 예는 클라이언트 코드를 구체적인 UI 클래스에 연결하지 않고 크로스 플랫폼 UI 요소를 만드는 데 Factory Method를 사용하는 방법을 보여줍니다.

기본 대화 상자 클래스는 다른 UI 요소를 사용하여 창을 렌더링합니다. 다양한 운영 체제에서 이러한 요소는 약간 다르게 보일 수 있지만 여전히 일관되게 작동해야합니다. Windows의 버튼은 여전히 Linux의 버튼입니다.

Factory Method가 작동 할 때 각 운영 체제에 대한 대화 상자의 논리를 다시 작성할 필요가 없습니다. 기본 대화 상자 클래스 내부에 단추를 생성하는 Factory Method를 선언하면 나중에 Factory Method에서 Windows 스타일 단추를 반환하는 하위 클래스를 만들 수 있습니다. 그런 다음 하위 클래스는 기본 클래스에서 대화 상자의 코드 대부분을 상속하지만 Factory Method 덕분에 화면에 Windows 모양의 버튼을 렌더링 할 수 있습니다.

이 패턴이 작동하려면 기본 대화 상자 클래스가 추상 버튼 (기본 클래스 또는 모든 구체적인 버튼이 따르는 인터페이스)과 함께 작동해야합니다. 이렇게하면 대화 상자의 코드가 작동하는 버튼 유형에 관계없이 계속 작동합니다.

물론 이 접근 방식을 다른 UI 요소에도 적용 할 수 있습니다. 그러나 대화 상자에 추가하는 각각의 새로운 팩토리 방법을 사용하면 Abstract Factory 패턴에 더 가까워집니다. 걱정하지 마세요. 나중에이 패턴에 대해 이야기하겠습니다.

// The creator class declares the factory method that must
// return an object of a product class. The creator's subclasses
// usually provide the implementation of this method.
class Dialog is
    // The creator may also provide some default implementation
    // of the factory method.
    abstract method createButton():Button

    // Note that, despite its name, the creator's primary
    // responsibility isn't creating products. It usually
    // contains some core business logic that relies on product
    // objects returned by the factory method. Subclasses can
    // indirectly change that business logic by overriding the
    // factory method and returning a different type of product
    // from it.
    method render() is
        // Call the factory method to create a product object.
        Button okButton = createButton()
        // Now use the product.
        okButton.onClick(closeDialog)
        okButton.render()


// Concrete creators override the factory method to change the
// resulting product's type.
class WindowsDialog extends Dialog is
    method createButton():Button is
        return new WindowsButton()

class WebDialog extends Dialog is
    method createButton():Button is
        return new HTMLButton()


// The product interface declares the operations that all
// concrete products must implement.
interface Button is
    method render()
    method onClick(f)

// Concrete products provide various implementations of the
// product interface.
class WindowsButton implements Button is
    method render(a, b) is
        // Render a button in Windows style.
    method onClick(f) is
        // Bind a native OS click event.

class HTMLButton implements Button is
    method render(a, b) is
        // Return an HTML representation of a button.
    method onClick(f) is
        // Bind a web browser click event.


class Application is
    field dialog: Dialog

    // The application picks a creator's type depending on the
    // current configuration or environment settings.
    method initialize() is
        config = readApplicationConfigFile()

        if (config.OS == "Windows") then
            dialog = new WindowsDialog()
        else if (config.OS == "Web") then
            dialog = new WebDialog()
        else
            throw new Exception("Error! Unknown operating system.")

    // The client code works with an instance of a concrete
    // creator, albeit through its base interface. As long as
    // the client keeps working with the creator via the base
    // interface, you can pass it any creator's subclass.
    method main() is
        this.initialize()
        dialog.render()

Applicability

Use the Factory Method when you don’t know beforehand the exact types and dependencies of the objects your code should work with.
Factory Method는 실제로 제품을 사용하는 코드에서 제품 구성 코드를 분리합니다. 따라서 나머지 코드와 별도로 제품 구성 코드를 확장하는 것이 더 쉽습니다.

예를 들어 앱에 새 제품 유형을 추가하려면 새 작성자 하위 클래스를 만들고 여기에있는 팩토리 메서드를 재정의하기 만하면됩니다.

Use the Factory Method when you want to provide users of your library or framework with a way to extend its internal components.

상속은 아마도 라이브러리 나 프레임 워크의 기본 동작을 확장하는 가장 쉬운 방법 일 것입니다. 그러나 프레임 워크는 표준 구성 요소 대신 하위 클래스를 사용해야한다는 것을 어떻게 인식할까요?

해결책은 프레임 워크 전체에서 구성 요소를 구성하는 코드를 단일 팩토리 메서드로 줄이고 구성 요소 자체를 확장하는 것 외에도 누구나이 메서드를 재정의 할 수 있도록하는 것입니다.

어떻게 작동하는지 보겠습니다. 오픈 소스 UI 프레임 워크를 사용하여 앱을 작성한다고 가정 해보십시오. 앱에는 둥근 버튼이 있어야하지만 프레임 워크는 사각형 버튼 만 제공합니다. 멋진 RoundButton하위 클래스로 표준 Button클래스를 확장합니다. 하지만 이제 기본UIFramework 클래스에 기본 버튼 대신 새 버튼 하위 클래스를 사용하도록 지시해야합니다.

이를 위해 기본 프레임 워크 클래스에서 하위 클래스UIWithRoundButtons를 만들고createButton 메서드를 재정의합니다. 이 메서드는 기본 클래스에서Button 객체를 반환하지만 하위 클래스가RoundButton 객체를 반환하도록합니다. 이제UIFramework 대신UIWithRoundButtons 클래스를 사용하십시오. 그게 다예요!


Use the Factory Method when you want to save system resources by reusing existing objects instead of rebuilding them each time.

데이터베이스 연결, 파일 시스템 및 네트워크 리소스와 같이 리소스를 많이 사용하는 대규모 개체를 처리 할 때 이러한 요구가 자주 발생합니다.

기존 객체를 재사용하기 위해 해야 할 일에 대해 생각해 봅시다.:

  1. 먼저 생성 된 모든 개체를 추적하기 위해 일부 저장소를 만들어야합니다.
  2. 누군가 객체를 요청하면 프로그램은 해당 풀에서 사용 가능한 객체를 찾아야합니다.
  3. … 그런 다음 클라이언트 코드로 반환합니다.
  4. 사용 가능한 개체가 없는 경우 프로그램은 새 개체를 만들고 풀에 추가해야합니다.

That’s a lot of code! 그리고 중복 코드로 프로그램을 오염시키지 않도록 모두 한곳에 보관해야합니다.

아마도이 코드를 배치 할 수있는 가장 분명하고 편리한 위치는 우리가 재사용하려는 객체가있는 클래스의 생성자 일 것입니다. 그러나 생성자는 항상 정의에 따라 새 개체를 반환해야합니다. 기존 인스턴스를 반환 할 수 없습니다.

따라서 기존 개체를 재사용 할뿐만 아니라 새 개체를 만들 수있는 일반적인 방법이 필요합니다. 그것은 Factory Method와 매우 흡사합니다.

How to Implement

  1. 모든 제품이 동일한 인터페이스를 따르도록합니다. 이 인터페이스는 모든 제품에서 의미있는 메서드를 선언해야합니다.

  2. 생성자 클래스 내부에 빈 Factory Method를 추가합니다. 메서드의 반환 유형은 공통 제품 인터페이스와 일치해야합니다.

  3. 작성자의 코드에서 제품 생성자에 대한 모든 참조를 찾습니다. 하나씩 Factory Method에 대한 호출로 대체하고 제품 생성 코드를 Factory Method로 추출합니다.

    Returned product의 유형을 제어하기 위해 Factory Method에 임시 매개 변수를 추가해야 할 수 있습니다.

    이 시점에서 Factory Method의 코드는 매우 추하게 보일 수 있습니다. 인스턴스화 할 제품 클래스를 선택하는 큰 스위치 연산자가 있을 수 있습니다. 하지만 걱정하지 마세요. 곧 수정하겠습니다.

  4. 이제 Factory Method에 나열된 각 제품 유형에 대한 작성자 하위 클래스 세트를 작성하십시오. 하위 클래스에서 Factory Method를 재정의하고 기본 메서드에서 적절한 구성 코드 비트를 추출합니다.

  5. 제품 유형이 너무 많고 모든 유형에 대해 하위 클래스를 만드는 것이 타당하지 않은 경우 하위 클래스의 기본 클래스에서 제어 매개 변수를 재사용 할 수 있습니다.

    예를 들어, 다음과 같은 클래스 계층이 있다고 가정 해 봅시다. 두 개의 하위 클래스가있는 기본Mail 클래스 :AirMailGroundMail; Transport 클래스는Plane,TruckTrain입니다. AirMail 클래스는Plane 객체 만 사용하지만GroundMailTruckTrain 객체 모두에서 작동 할 수 있습니다. 두 경우를 모두 처리하기 위해 새 하위 클래스 (예 : 'TrainMail')를 만들 수 있지만 다른 옵션이 있습니다. 클라이언트 코드는GroundMail 클래스의 Factory Method에 인수를 전달하여 수신하려는 제품을 제어 할 수 있습니다.

  6. 모든 추출 후 기본 Factory Method가 비어있는 경우 추상화로 만들 수 있습니다. 남은 것이 있으면 이를 메서드의 기본 동작으로 만들 수 있습니다.

Pros and Cons

O. Creator와 concrete products간의 coupling을 피합니다.
O. 단일 책임 원칙. 제품 생성 코드를 프로그램의 한 위치로 이동하여 코드를 더 쉽게 지원할 수 있습니다.
O. Open/Closed Principle. 기존 클라이언트 코드를 손상시키지 않고 새로운 유형의 제품을 프로그램에 도입 할 수 있습니다.
X. 패턴을 구현하기 위해 많은 새로운 서브 클래스를 도입해야하므로 코드가 더 복잡해질 수 있습니다. 가장 좋은 시나리오는 creator classes의 기존 계층 구조에 패턴을 도입하는 경우입니다.

Relations with Other Patterns

  • 많은 설계는 Factory Method (less complicated and more customizable via subclasses)를 사용하여 시작하고 Abstract Factory, Prototype 또는 Builder (more flexible, but more complicated)로 발전합니다.

  • Abstract Factory클래스는 종종 Factory Methods 집합을 기반으로 하지만, Prototype을 사용하여 이러한 클래스에 대한 메서드를 구성 할 수도 있습니다.

  • Iterator와 함께 Factory Method를 사용하여 컬렉션 하위 클래스가 컬렉션과 호환되는 여러 유형의 iterators를 반환하도록 할 수 있습니다.

  • Prototype은 상속을 기반으로 하지 않으므로 단점이 없습니다. 반면에 Prototype은 복제 된 개체의 복잡한 초기화가 필요합니다. Factory Method는 상속을 기반으로 하지만 초기화 단계가 필요하지 않습니다.

  • Factory MethodTemplate Method의 전문화입니다. 동시에 Factory Method는 대형 Template Method의 단계 역할을 할 수 있습니다.

profile
software engineer

0개의 댓글