도서 요약 / / 2023. 2. 10. 21:01

디자인 패턴 - 추상 팩토리(abstract factory) 패턴

udemy 강좌(Java Design Patterns & SOLID Design Principles)를 정리한 내용이다.

https://www.udemy.com/course/design-patterns-in-java-concepts-hands-on-projects/learn/lecture/9604610?start=0#overview


이제 추상 팩토리 디자인에 대해 알아볼 것이다. 이 패턴이 처음이라면 처음 몇 개의 슬라이드는 좀 혼동스러울 수 있지만 예제가 있고 이 디자인 패턴을 이해하기 위해 사용해볼 것이니 걱정하지 않아도 된다.

추상 팩토리는 무엇인가?

추상 팩토리의 목적은 무엇이며 이 패턴을 어디서 사용하는가?

  • 하나의 세트를 구성하는 두 가지 이상의 객체가 클라이언트 코드에서 만들어 질 수 있는 다양한 조합으로 구성될 수 있을 때 추상 팩토리가 사용된다.
  • 추상 팩토리의 의도는 클라이언트 코드를 구체적인 객체와 객체를 생성하는 코드로부터 분리하는 것이다.

UML

UML

추상 팩토리 구현

  • 애플리케이션에 있는 제품군을 검토하면서 시작
    • 제품군을 알아 냈다면, 추상 팩토리를 추상 클래스나 인터페이스로 생성한다.
    • 추상 팩토리는 product에 대한 추상 메소드를 정의
    • 각 제품군의 팩토리 구현부를 제공
  • 추상 팩토리는 팩토리 메소드 패턴을 사용한다. 추상 팩토리를 여러 개의 팩토리 메소드를 가진 객체로 생각할 수 있다.

예제: UML

여기서 클라우드 리소스를 다른 클라우드 제공자에 배포하는 시스템을 구현해볼 것이다.

AWS 웹 서비스와 구글 클라우드 플랫폼으로 생각해볼 것이다. 물론 매우 단순하게 만들어볼 것이다. 서로 관련되어 있는 객체군을 어떻게 생성할 수 있는지가 여기서 말하는 요점이다. 우리는 실제로 프로비저닝을 하지는 않을 것이다.

인터페이스가 되는 Instance가 있고 이 인스턴스는 컴퓨터 리소스를 나타낸다.

그리고 두 개의 인스턴스 클래스가 있다. 아마존에 프로비저닝되는 EC2 인스턴스와 구글 클라우드 플랫폼에 프로비저닝되는 GoogleComputeEngineInstance가 있다.

같은 방식으로 저장소(Storage)를 나타낸는 인터페이스가 있고 아마존에서 사용가능 한 S3Storage와 구글 클라우드 플랫폼에서 사용 가능한 GoogleCloudStorage의 두 개의 서브 클래스가 있다.

여러분이 아마존 서비스에서 작업한다면 머신을 프로비저닝하고 싶다면 EC2 인스턴스를 만들고 S3Storage를 프로비저닝하고 이 두개의 서로 연결할 것이다.

여러분이 해야할 일은 컴퓨팅과 저장소를 동일 플랫폼에서 제공하여 서로 연결할 수 있게 하는 것이다.

클라이언트에서는 단순한 main 메서드를 있고 프로비저닝 요청을 받고 어떤 클라우드 프로바이더를 사용할 지 결정한다. 프로바이더가 결정이 되고 나면 컴퓨팅과 저장 리소스를 만들 수 있다.

ResourceFactory는 추상 팩토리이고 인스턴스를 생성하는 팩토리 메소드를 제공하는 인터페이스이다. 이 인스턴스의 자식 클래스 중 하나의 객체를 생성할 것이고 저장 메소드를 만든다.

추상 팩토리에 두 개의 구체 구현체가 있다. 하나는 AWS에 컴퓨팅과 스토리지 같은 리소스를 생성하는 AwsResourceFactory이고 AWS 클라우드에 리소를 나타내는 클래스를 만들것이다. 그리고 나서 예상하겠지만 GoogleCloudResourceFactory는 구글 클라우드 플랫폼에 해당되는 것이다.

Java 구현

// 추상 product
public interface Instance {

    void start();

    void addStorage(Storage storage);

    void stop();

    enum Capacity {
        micro,
        small,
        large
    }
}

우선, Instance 인터페이스가 있다. 이 클래스에는 아마존이나 구글 클라우드 플랫폼 같은 클라우드 프로바이더에서 사용 가능한 컴퓨팅 리소스를 나타낸다.

이 Instance 인터페이스는 사용할 구현체가 어떤 것이던 상관없이 적용할 수 있는 몇 개의 공통 메서드를 정의한다. 이 인터페이스를 구현하는 두 개의 클래스가 있다.

public class Ec2Instance implements Instance {

    public Ec2Instance(Capacity capacity) {
        System.out.println("Created Ec2Instance");
    }

    @Override
    public void start() {
        System.out.println("Ec2Instance started");
    }

    @Override
    public void addStorage(Storage storage) {
        System.out.println("Attached " + storage + " to Ec2Instance");
    }

    @Override
    public void stop() {
        System.out.println("Ec2Instance stopped");
    }

    @Override
    public String toString() {
        return "Ec2Instance";
    }
}

아마존 클라우의 컴퓨팅 리소스를 나타내는 EC2 인스턴스가 있다. 물론 실제 AWS를 연결하지는 않는다.

public class GoogleComputeEngineInstance implements Instance {

    public GoogleComputeEngineInstance(Capacity capacity) {
        System.out.println("Created Google Compute Engine instance");
    }

    @Override
    public void start() {
        System.out.println("Compute engine instance started");
    }

    @Override
    public void addStorage(Storage storage) {
        System.out.println("Attached " + storage + " to Compute engine instance");
    }

    @Override
    public void stop() {
        System.out.println("Compute engine instance stopped");
    }
}

유사하게 GoogleComputeEngineResource가 있고 컴퓨팅 리소스와 Instance 인터페이스를 구현한다.

// abstract product
public interface Storage {

    String getId();
}

그리고 Storage 인터페이스가 있고 이 클래스는 여러 클라우드 플랫폼에서 사용 가능한 클라우드 스토리지를 추상화한다.

public class S3Storage implements Storage {

    public S3Storage(int capacityInMib) {
        System.out.println("Allocated " + capacityInMib + " on S3");
    }

    @Override
    public String getId() {
        return "S31";
    }

    @Override
    public String toString() {
        return "S3 Storage";
    }
}

이 인터페이스를 구현하는, 아마존에 있는 S3Storage와 구글 클라우드 플랫폼에서 사용 가능한 GoogleCloudStorage가 있다.

public class GoogleCloudStorage implements Storage {

    public GoogleCloudStorage(int capacityInMib) {
        // gcp api를 사용
        System.out.println("Allocated " + capacityInMib + " on Google Cloud Storage");
    }

    @Override
    public String getId() {
        return "gcpcs1";
    }

    @Override
    public String toString() {
        return "Google cloud storage";
    }
}
// 각 유형에 정의된 메소드의 추상 팩토리
public interface ResourceFactory {

    Instance createInstance(Instance.Capacity capacity);

    Storage createStorage(int capMib);
}
// Google cloud platform 의 팩토리 구현체
public class GoogleResourceFactory implements ResourceFactory {

    @Override
    public Instance createInstance(Instance.Capacity capacity) {
        return new GoogleComputeEngineInstance(capacity);
    }

    @Override
    public Storage createStorage(int capMib) {
        return new GoogleCloudStorage(capMib);
    }
}
public class AwsResourceFactory implements ResourceFactory {

    @Override
    public Instance createInstance(Instance.Capacity capacity) {
        return new Ec2Instance(capacity);
    }

    @Override
    public Storage createStorage(int capMib) {
        return new S3Storage(capMib);
    }
}
public class Client {

    private ResourceFactory factory;

    public Client(ResourceFactory factory) {
        this.factory = factory;
    }

    public Instance createServer(Instance.Capacity capacity, int storageMib) {
        Instance instance = factory.createInstance(capacity);
        Storage storage = factory.createStorage(storageMib);
        instance.attachStorage(storage);

        return instance;
    }

    public static void main(String[] args) {
        Client aws = new Client(new AwsResourceFactory());
        Instance i1 = aws.createServer(Instance.Capacity.micro, 20480);
        i1.start();
        i1.stop();

        System.out.println("********************************");

        Client gcp = new Client(new GoogleResourceFactory());
        i1 = gcp.createServer(Instance.Capacity.micro, 20480);
        i1.start();
        i1.stop();
    }
}

구현 고려사항

  • 팩토리는 싱글톤으로 구현될 수 있다. 일반적으로 하나의 인스턴스만 필요하다.
  • 새 product를을 추가하는 것은 팩토리 구현뿐만 아니라 기본 팩토리를 변경해야 한다.
  • 객체를 생성할 수 있도록 클라이언트 코드에 구체적인(concrete) 팩토리를 제공한다.

디자인 고려사항

  • 객체 생성을 제한하고 한 번에 같이 동작하게 하려면 추상 팩토리 패턴이 좋은 선택이다.
  • 추상 팩토리는 팩토리 메소드 패턴을 사용한다.
  • 객체 생성 비용이 높다면 객체 생성 시 프로토타입 디자인 패턴을 사용하는 팩토리 구현으로 바꿀 수도 있다.

위험요소

  • 팩토리 메소드 보다 구현하기 더 복잡하다.
  • 새 제품을 추가하면 팩토리의 모든 구현부 뿐만 아니라 기본 팩토리에 변경이 필요하다.
  • 개발 시작시점에 시각화하기 어렵기 때문에 일반적으로 팩토리 메소드 부터 시작한다.
  • 추상 팩토리 디자인 패턴은 제품군 문제를 해결하는 데 특화되어 있다.



반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유