설계패턴(Design Pattern)

[디자인 패턴] 4.(1) 팩토리 메서드 패턴 (Factory Method Pattern)

긍.응.성 2024. 6. 16. 14:33
반응형

이 글은 헤드퍼스트 디자인패턴과 GoF 디자인패턴을 읽고 정리한 글입니다.

1. 팩토리 메서드 패턴(Factory Method Pattern)

객체를 생성할 때 필요한 인터페이스를 생성하고, 어떤 클래스의 인스턴스를 만들지 서브클래스에서 결정합니다. 팩토리 메소드 패턴은 클래스 인스턴스를 만드는 일을 서브클래스에 위임합니다.

1.1. 구성 요소

Product

  • 팩토리 메서드를 통해 생성될 Object의 인터페이스입니다.

ConcreteProduct

  • Product 인터페이스를 구현하는 구현 클래스입니다.
  • 구현된 팩토리 메서드를 통해 반환될 Object 객체입니다.

Creator

  • Product 타입을 반환하는 팩토리 메서드가 정의되어있습니다.
  • 때에 따라 기본 ConcreteProduct를 반환하는 팩토리가 구현될 수 있습니다.
  • 팩토리 메서드를 호출하여 Product Object를 생성합니다.

ConcreteCreator

  • Creator의 팩토리 메서드를 오버라이드하며 ConcreteProduct 인스턴스를 반환합니다.

1.2. 적용 방법

  1. 생성될 수 있는 모든 ConcreteProduct 에 대하여 동일한 Product 인터페이스를 따르도록 합니다. 그리고 ConcreteProduct가 Product를 구현하도록 합니다. 이때 Product 인터페이스에는 모든 ConcreteProduct에게 적용될 수 있는 메서드가 선언되어야 합니다.
  2. Creator 클래스를 생성하고 Product 인터페이스 타입을 반환할 팩토리 메서드를 생성합니다.
  3. 우선 생성할 수 있는 ConcreteProduct를 생성하는(new) 부분을 Creator 클래스의 팩토리 메서드에 구현합니다.
  4. Creator를 상속하는 ConcreteCreator를 생성합니다. ConcreteCreator는 팩토리 메서드를 오버라이드하는데, 이때 ConcreteCreator가 제공할 ConcreteProduct를 생성하는 부분을 Creator 클래스의 팩토리 메서드로 부터 가져옵니다. (ConcreteProduct를 생성(new)하는 부분을 Creator에서 ConcreteCreator로 이동)
  5. 모든 ConcreteCreator가 만들어졌다면 Creator 클래스의 팩토리 메서드는 빈 메서드일것입니다. 이제 Creator를 abstract하게 수정합니다.

1.3. 적용 시기

  • 생성될 Product의 정확한 타입이 아직 확정되지 않았으며 Product에 대하여 의존관계가 있는 객체가 있다면 고려할 수 있습니다.
  • 사용자가 Product를 확장하여 사용할 수 있게 하려면 팩토리 메서드 패턴을 적용할 수 있습니다. 팩토리 메서드가 ConcreteProduct가 아닌 Product 타입을 반환하기에 사용자가 구현할 Creator의 서브 클래스는 Product을 상속한 객체라면 어떤 타입이든 반환 가능합니다.
  • Product의 생성한다는 면에서 재사용을 통해 시스템 자원을 아끼기 위해 팩토리 메서드를 사용할 수 있습니다. 팩토리 메서드에서 Product를 생성하는것 뿐만이 아니라 생성된 Product에 대한 참조를 갖고 이를 재사용한다면 효율적으로 자원을 사용할 수 있습니다.

1.4. 정리

팩토리 메서드 패턴은 생성자(Creator)와 생성될 클래스(ConcreteProduct) 간의 커플링을 줄이기 위해 사용됩니다. Creator는 팩토리 메서드에서 ConcreteProduct 대신 Product 인터페이스 타입을 반환하도록 하여 느슨하게 결합을 가지며, 하위 클래스(ConcreteCreator)에서 ConcreteProduct 에 대한 생성을 위임합니다. 이 구조를 통해 Creator와 Product는 서로 동일한 레벨의 컴포넌트간에서만 의존을 갖도록 합니다.

팩토리 메서드는 직접 new를 통한 생성과 달리 하위 클래스의 메서드에서 Product를 생성하고 반환하게되는데, 이 시점에 대한 훅을 추가할 수 있습니다.

2. 팩토리 메서드 구현 시 이슈 및 대응 방법

2.1. Creator 생성의 두가지 방법

팩토리 메서드에서 Creator는 두가지 버전으로 만들 수 있습니다. 먼저 첫번째는 추상 클래스로, 두번째는 구상 클래스로 만드는 경우입니다.

추상 클래스로 Creator를 만드는 경우 팩토리 메서드는 추상 메서드로 선언되며 모든 ConcreteCreator는 반드시 팩토리 메서드를 구현해야만 합니다. 이 경우의 단점은 디폴트 구현이 없다는 점입니다. 그렇기에 추상 클래스로 어떤 타입의 ConcreteProduct를 생성해야할 지 결정하지 못한다면 문제가 될 수 있습니다.

반대로 구상 클래스로 Creator를 만드는 경우 Creator의 팩토리 메서드는 디폴트로 어떤 ConcreteProduct를 반환할지를 지정할 수 있습니다. 또한 ConcreteCreator에게 반드시 팩토리 메서드를 오버라이드 하여야 한다는 강제성을 부여하지 않으며, 만약 구현하더라도 어떠한 ConcreteProduct를 반환할 지 모른다면 상위 클래스(Creator)의 팩토리 메서드를 호출하여 디폴트 ConcreteProduct를 반환하게 하도록 구현할 수 있어 더욱 유연한 구현이 가능하다는 장점이 있습니다.

2.2. 파라미터를 받는 팩토리 메서드

위의 다이어그램에서는 팩토리 메서드가 아무런 파라미터를 받지 않는 모습인데요, 헤드퍼스트 책의 예시나 일반적으로는 Creator의 팩토리 메서드는 파라미터를 전달받습니다. 이때 전달되는 파라미터는 생성될 Product의 종류를 결정짓는데 사용될 수 있는데요, 즉 하나의 ConcreteCreator에서 여러 종류의 ConcreteProduct 생성을 지원할 수 있습니다.

2.3. Language-specific variants and issues

사용언어에 따라 팩토리 메서드 패턴에 다양한 변형과 이슈가 있습니다.

현재 필자가 사용하는 자바 기준으로는 생성 시 lazy initialization 을 적용할 수 있을것 같네요.

또한 파라미터를 받는 경우 동일한 파라미터가 들어오면 동일 Product가 반환된다면, 생성 시 반환 될 Product를 캐시하는 식으로 개선할 수 있습니다.

2.4. 서브 클래스 선언을 피하는 방법으로 템플릿을 사용하라.

이 내용은 C++에서 지원되는 템플릿을 사용하여 ConcreteCreator를 생성하지 않도록 하는 방법에 대하여 설명하는것으로 보입니다. 저는 C++을 잘 모르기 때문에 자바에서 템플릿과 비슷하게 서브 클래스 생성을 하지않고 처리 가능한 방법이 어떤것이 있나 조사해보았는데요, 자바에서는 람다를 이용하여 이를 해결할 수 있습니다.

팩토리 메서드에서 파라미터로 Supplier<Product>를 받도록 하면 ConcreteCreator를 선언하지 않아도 람다에서 이 역할을 대신할 수 있습니다.

2.5. 네이밍 컨벤션

팩토리 메서드에 대한 컨벤션을 두도록 권장합니다. 이 컨벤션을 통해 개발자는 팩토리 메서드 패턴이 적용되어 있음을 쉽게 판단할 수 있습니다.

3. 평행 상속 구조(Parallel Inheritance Hierarchies) 에서의 팩토리 메서드 패턴

평행 상속 구조는 하나 이상의 계층 구조를 갖는 객체 집합에서 동일한 단계에 존재하여 또 다른 계층에서도 동일한 단계에 있는 Product 의 관계가 있는 구조를 이야기 합니다. 평행 상속 구조에서 하나의 계층에 새로운 Product가 추가되고 이와 관련된 여러 계층에서 추가로 Product가 생성되어야 한다면 팩토리 메서드 패턴을 적용할 수 있습니다.

팩토리 메서드는 자신과 평행 상속 구조를 갖는 다른 계층의 Product를 생성하도록 합니다. 위의 다이어그램에서 Figure와 Manipulator가 평행 상속 구조를 갖는데요, Figure에 자신과 동일한 계층으로 생성될 Manipulator를 생성하는 팩토리 메서드를 선언합니다. 팩토리 메서드로 Figure와 자신과 관계를 갖는 다른 계층의 Product를 생성할 수 있다는 것은, 코드 레벨에서 관계를 갖는 객체를 한곳으로 모아준다는 장점이 있습니다. 계층의 Product간 관계를 클라이언트가 알 필요없이 Figure 기준으로 관계를 갖는 다른 계층의 Product를 쉽게 파악할 수 있습니다.

4. 참고 링크

반응형