디자인 패턴(구)/생성 패턴

추상 팩토리 패턴(Abstract Factory Pattern)이란?

공대키메라 2022. 4. 5. 19:25

 

이번 시간에는 추상 팩토리 패턴에 대해 알아보고자 한다. 

 

무언가 팩토리 메서드 패턴과 굉장히 유사하지만 차이점이 무엇인지를 생각하며 보는것이 포인트다.

1. 추상 팩토리 패턴이란?

의도

상세화된 서브클래스를 정의하지 않고도 서로 관련성이 있거나 독립적인 여러 객체의 군을 생성하기 위한 인터페이스를 제공

 

즉, 서로 관련있는 여러 객체를 만들어주는 인터페이스를 제공하는 것이다. 

 

얼핏 보면 팩토리 메서드 패턴과 매우 유사하지만 팩토리를 사용하는 초점이 클라이언트에 맞추어 져 있다고 생각하며 된다. 

 

팩토리 메서드 패턴이 기억이 안나거나 잘 모르겟으면 전에 정리한 내용이 있으니 참고하면 좋다. 

https://tech-monster.tistory.com/148

 

팩토리 메서드의 구조는 다음과 같다.

 

 

추상 팩토리의 구조는 다음과 같다. 

 

팩토리 메소드의 구조에 client가 추가되엇다.

 

Factory에서 인스턴스를 만들어 쓸 때 인터페이스 기반으로 구현을 할 수 있게끔 하는 패턴이다.

 

즉, client가 어떤 Factory를 넣어주느냐에 따라서  client가 받는 제품이 달라지는 것이다. 

 

  • Abstract Factory : 개념적 제품에 대한 객체를 생성하는 연산으로 인터페이스를 정의
  • ConcreteFactory : 구체적인 제품에 대한 객체를 생성하는 연산을 구현
  • Abstract Product : 개념적 제품 객체에 대한 인터페이스를 정의 (여기서는 ProductA, ProductB)
  • ConcreteProduct : 구체적으로 팩토리가 생성할 객체를 정의, Abstract Product가 정의하는 인터페이스를 구현    (여기서는 ConcreteProductA와 [ConcreteProductB)
  • Client : Abstract Factory와 Abstract Product 클래스에 선언된 인터페이스를 사용

 

사용 시기

  • 객체가 생성되거나 구성, 표현되는 방식과 무관하게 시스템을 독립적으로 만들고자 할 때
  • 여러 제품군 중 하나를 선택해서 시스템을 설정해야 하고 한번 구성한 제품을 다른 것으로 대체할 수 있을 때
  • 관련된 제품 객체들이 함께 사용되도록 설계되었고, 이 부분에 대한 제약이 외부에도 지켜지도록 하고 싶을 때
  • 제품에 대한 클래스 라이브러리를 제공하고, 그들의 구현이 아닌 인터페이스를 노출시키고 싶을 때

 

2. 구현

백문이 불여일타! 한 번 예를 들어 어떻게 구현하는지 들여보도록 하겠다.

 

필자는 저번에 팩토리 메서드 패턴에서 사용한 class들을 활용해서 구현해 볼 것이다.

 

이번에도 자동차를 여러 모델을 양산하는 것을 목표로 한다. 모델1에서 모델 2의 생산을 추가 할 것이다. 

 

최종적으로 구현할 클래스들은 다음과 같다.

 

염두해야할 사항은 추상 메서드 패턴의 의도에 맞게끔 우리가 작성한 코드가 맞는지 생각하는 것이다. 

 

다시 추상 메서드 패턴의 의도를 가져오면 ...

 

상세화된 서브클래스를 정의하지 않고도 서로 관련성이 있거나 독립적인 여러 객체의 군을 생성하기 위한 인터페이스를 제공

 

과연... 의도에 맞을 것인지 나도 그게 궁금하다. 한 번 알아가보자!

 

Car.java

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._my_code;

public class Car {

    private String name;
    private String color;
    private Integer price;

    private Frame frame;

    private Airbag airbag;

   //toString
   //getter & setter 생략
}

 

Frame.java

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._my_code;

public interface Frame {
}

 

Airbag.java

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._my_code;

public interface Airbag {
}

 

ModelOne.java

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._my_code;

public class ModelOne extends Car {

    public ModelOne() {
        setName("model first");
        setColor("white");
        setPrice(10000);
    }

}

 

ModelOneAirbag.java

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._my_code;

public class ModelOneAirbag implements Airbag {}

 

ModelOneFrame.java

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._my_code;

public class ModelOneFrame implements Frame {}

 

CarFactoryInterface.java

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._my_code;


public interface CarFactoryInterface {

    default Car orderCar(String name, String address){
        validate(name, address);
        getOrder(name);
        Car car = makeCar();
        sendCarTo(address ,car);
        return car;
    }

    Car makeCar();

    //private 선언시에는 interface에도 메소드 구현이 가능함.
    private void validate(String name, String address){
        if(name == null || name.isBlank()) {
            throw new IllegalArgumentException("자동차 이름을 입력하세요");
        }
        if(address == null || address.isBlank()) {
            throw new IllegalArgumentException("주소를 입력하세요");
        }
    }

    private void getOrder(String name){
        System.out.println(name + " 주문이 들어왔습니다. 차를 만들자!");
    }

    private static void sendCarTo(String address, Car car){
        System.out.println(car.getName() + " 제작이 완료됬습니다. 원산지 : " + address );
    }
}

 

CarPartsFactory.java

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._my_code;

//추상 팩토리 생성
//이를 바탕으로 구체적인 팩토리를 생성함
public interface CarPartsFactory {

    Frame createFrame();

    Airbag createAirbag();

}

 

ModelOnePartsFactory.java

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._my_code;

//모든 파트들은 일련의 규약을 통해서 생성된다.
public class ModelOnePartsFactory implements CarPartsFactory {
    @Override
    public Frame createFrame() {
        return new ModelOneFrame();
    }

    @Override
    public Airbag createAirbag() {
        return new ModelOneAirbag();
    }
}

 

ModelOneFactroy.java

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._my_code;

public class ModelOneFactory implements CarFactoryInterface {

	//client에서 주입하는 원하는 자동차 부품으로 제품을 생성한다. 
    //아마 이렇기에 추상 메서드 패턴이 client라고 하는 것 같다. 
    private CarPartsFactory carPartsFactory;

    public ModelOneFactory(CarPartsFactory carPartsFactory) {
        this.carPartsFactory = carPartsFactory;
    }

    @Override
    public Car makeCar() {
        Car car = new ModelOne();
        car.setFrame(carPartsFactory.createFrame());
        car.setAirbag(carPartsFactory.createAirbag());
        return car;
    }
}

CarInventory.java

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._my_code;

public class CarInventory {

    public static void main(String[] args) {
        CarFactoryInterface modelOne = new ModelOneFactory(new ModelOnePartsFactory());
        Car one = modelOne.makeCar();
        System.out.println("car = " + one);

        CarFactoryInterface modelTwo = new ModelTwoFactory(new ModelTwoPartsFactory());
        Car two = modelTwo.makeCar();
        System.out.println("car = " + two);
    }
}

 

의도를 다시 보자

 

상세화된 서브클래스를 정의하지 않고도 서로 관련성이 있거나 독립적인 여러 객체의 군을 생성하기 위한 인터페이스를 제공

 

독립적인 여러 객체군을 생성하기 위한 인터페이스를 확인해보면, ModelOne을 위한것이 

 

CarPartsFactory가 있고, 이를 ModelOnePartsFactory에서 상속받는다. 이 인터페이스를 구현하는 곳에서는 ModelOne에 해당하는 ModelOneFrame, ModelOneAirbag를 오버라이드한 메소드에서 반환한다. 

 

이렇게 하면은 일정 모델을 생성하는데 정해진 규약을 통해서 코드가 정의된다. 

 

새로운 모델을 생성시에는 위에서 한 작업을 동일하게 반복하면 된다.

 

필자는 우선 위에서 ModelOne에 해당되는 class들을 구현했다.

 

다른 모델을 생성하고 싶으면 ModelTwo에 해당되는 class를 구현하면 된다.

 

추상 팩토리 vs 팩토리 메서드 패턴

서로 보는 관점에 따라서 미묘한 차이가 있다. 

 

둘 다 객체를 생성하는 과정을 추상화 한것은 맞다. 

 

팩토리 메서드 패턴의 경우 인스턴스를 만드는 과정에 좀 더 집중되어있다. 직접적으로 ConcreteClass를 참조해서 사용하는 경우가 많다. 즉, 구체적인 객체 생성 과정을 하위 또는 구체적인 클래스로 옮기는 것이 목적이다.

 

반면, 추상 팩토리의 경우에는 그 팩토리를 사용하는 쪽에 좀 더 집중되어있다. 팩토리를 통해서 추상화된 인터페이스만 

클라이언트가 사용할 수 있게끔 해준다. 즉, 관련있는 여러 객체를 구체적인 클래스에 의존하지 않고 만들 수 있게 해주는 것이 목적이다. 

 

구조에서 보면 추상 팩토리에 무언가 더 추가가 되어있는게 보인다.

 

Factory내부에는 동일해 보이지만 다른 역할을 하는(modelOne 생성용, modelTwo 생성용) Factory를 생성했다.

 

어떤 팩토리도 하나의 product만 생산하는 것은 아니지만 다수의 프로덕트를 생성할 가능성을 가지고 있다.

 

그래서 구조에서 createMethodA(), createMethodB()를 interface에 정의했다. 

 

이것을 바탕으로 ConcreteFactory에서 각각 구현하는 것이 있다. 

 

솔직히 잘 정리하고 이해하려고 했는데 

 

무언가 너무 그래도 헷갈린다. 

 

더 이해를 돕기 위한 정보를 찾다가 다음 유튜브 영상을 참고했다.

 

참고 : 

https://www.youtube.com/watch?v=v-GiuMmsXj4&t=471s 

 

좋은 강의... 고맙습니다 수염아저쉬...

 

우리가 상품을 가지고 있다고 하자

 

그리고 다양한 구현을 가지고 있다고 하자.

 

 

그 프로덕트를 기준으로 다양한 구현 클래스가 존재한다. 

 

우리 어플리케이션에서, 오직 특정한 프로턱트만 적용 가능하다. 

 

x/y, z/a, b/c 만 해당되는데, 다른 방식으로 x/c를 엮어서 사용하면 우리 프로그램에서 작동하지 않는다. 

 

핵심은 x/y, z/a, b/c의 묶음이 우리 프로그램의 ui 컨트롤 플랫폼을 나타낸다고 하자.

 

(x/y는 apple, z/a는 window 그리고 b/c는 리눅스)

 

x는 apple만을 위한 alert고, y는 alert의 ok버튼이다. (다른 뭉치도 동일하다.)


아마도 실제로는 받아들이기 힘들지만 x와 a를 조합할 수 도 있고, x 와 c를 조합할 수 있다.

 

이러한 것을 그림으로 표현하면 다음과 같다.

 

 

여기서 다시 한번 의도를 확인해보자.

 

상세화된 서브클래스를 정의하지 않고도 서로 관련성이 있거나 독립적인 여러 객체의 군을 생성하기 위한 인터페이스를 제공

 

자. 상세화된 서브클래스를 정의하기 않았기 때문에 여러 객체 군을 인터페이스를 통해서 무한히 확장이 가능하다. 

 

multiple product를 구현체를 통해서 반환이 가능하다는 것이다. 

 

그것을 확인할 수 있는 부분은 우리가 작성한 ModelOneFactory에서 확인할 수 있다. 

 

생성자 주입을 인터페이스를 통해서 여러 객체의 군을 생성할 수 있다. 우리가 상세화된 서브 클래스를 정의하지 않고 인터페이스를 제공한다는 말은 이말인 것 같다. 

 

포인트는 다른 그룹의 군을 생성한다는 것이다. 위의 그림에서 이 말을 확인할 수 있다.

 

위처럼 구성하게 되면 아까 x/y, z/a, b/c가 여러개 섞일수가 있다는 말이 조금 이해가 될 것이다. 

 

이것을 다시 한번 이어보면 다음과 같이 그려질 수 있다.

 

 

이것은 다르게 보면 팩토리 메서드 패턴인데, multiple factory method를 가진 것으로도 볼 수 있다.

 

우리가 처음 본 구조와 크게 다르지 않다. 

잘못된것은 텍스트로 명시함...

client는 참고로 우리가 선언한 Product Interface를 사용하는 코드 부분을 말한다. 

 

CarFactoryInterface가 Product라면 이를 구현하는 곳은 ModelOneFactory이다. ModelOneFactory가 CarFactoryInterface의 Client인 것이다. 

 

client측에서 생성자 주입을 통해 (CarPartsFactory를 주입) 어떤 부품을 사용할지 정해주고 있다.

 

Abstract Fatory에 해당하는 부분은 CarPartsFactory인 것 같다. 이것을 ModelOnePartsFactory에서 받아서 각각 프로덕트에 맞는 반환값(ModelOne에게는 ModelOneParts와 이를 위한 부품인 ModelOneFrame, ModelOneAirbag이 있다.)

을 구체화한 것이다.

 

즉, ConcreteFactory에 해당하는 부분은  ModelOneParsFactory, ModelTwoPartsFactory이다. 

 


오늘은 추상메서드에 대해서 공부를 했는데

 

솔직히 아직도 아리까리하다. 강의도 듣고, 책도 보고, 유튜브도 뒤져서 정리를 햇지만 무언가 부족하고 머리에 박히지가 않는다. 혹시 고수분들이 내 글이 잘못됐고 도움을 주고 싶어 안달이 났으면 아주 좋다. 피드백은 언제든지 환영!