본문 바로가기
설계/디자인패턴

[디자인패턴] 추상 팩토리(Abstract Factory) 패턴 이해하기

by J4J 2024. 4. 28.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 추상 팩토리 (Abstract Factory) 패턴에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

관련 글

 

[디자인패턴] 팩토리 메서드(Factory Method) 패턴 이해하기

 

 

반응형

 

 

Abstract Factory 패턴이란?

 

abstract factory 패턴은 생성해야 되는 객체들 중 관련된 객체들을 서로 묶어 객체들의 집합 군을 생성할 수 있도록 도와주는 디자인 패턴입니다.

 

 

 

abstract factory 패턴과 유사하게 비교될 수 있는 디자인 패턴으로 factory method 패턴이 있습니다.

 

두 디자인 패턴의 차이점에 대해 간단하게 설명해 보면 다음과 같이 얘기해 볼 수 있습니다.

 

  • abstract factory → 집합 군의 객체들을 생성하기 위해 사용되는 디자인 패턴
  • factory method → 단일 객체 생성을 목적으로 하위 클래스에서 객체 생성을 정의하는 디자인 패턴
 

 

 

두 디자인 패턴 모두 객체를 생성하기 위한 목적으로써 사용될 수 있는 디자인 패턴이라는 공통점이 존재합니다.

 

하지만 abstract facotry 패턴은 객체 생성에 대한 클래스를 다른 여러 클래스들에 의존하지 않고 하나의 집합 군에서 생성될 수 있도록 도와줍니다.

 

그리고 factory method 패턴은 객체 생성에 대한 클래스를 다른 여러 하위 클래스에 정의되도록 합니다.

 

즉, 두 디자인 패턴은 유사하게 보일 수 있지만 각 역할이 분명히 존재하기에 구현되어야 하는 목적이 무엇인지에 따라 선택해서 사용될 수 있으며 상황에 따라 함께 사용될 수도 있다고 생각합니다.

 

 

 

 

abstract factory 패턴의 주요 사용처는 위에서 얘기한 것과 같이 집합 군의 객체 생성을 목적으로 사용됩니다.

 

그중 대표적으로 얘기해 볼 수 있는 것은 UI 컴포넌트들이 있습니다.

 

Button, Text Field, Drop Box, Check Box 등등 사용될 수 있는 UI 컴포넌트들은 정말 다양하게 존재하고, 상황 별 사용하고 싶은 컴포넌트들도 모두 정해져 있을 것입니다.

 

 

 

간단한 예시로 많은 분들이 사용하고 계시는 Chrome, Safari, Edge 등의 브라우저가 존재할 때 해당 브라우저에서는 사용자들에게 보여 줄 다양한 UI 컴포넌트들이 존재하고 있습니다.

 

하지만 각 브라우저마다 가지고 있는 디자인 컨셉과 제공해주고 싶은 기능에 따라 서로 다른 형태의 컴포넌트들을 생성하게 됩니다.

 

물론, 위에서 얘기한 브라우저들은 서로 다른 프로젝트를 통해 개발이 이루어지고 있지만 만약 같은 곳에서 모두 개발해야 된다고 한다면 abstract factory 패턴을 활용해 볼 수 있습니다.

 

각 브라우저들에서 제공해줘야 하는 기능들을 집합 군으로 묶어줄 수 있으며 추상화를 통해 동일한 기능들을 모두 적절하게 가져갈 수 있게 됩니다.

 

 

 

 

abstract facotry 패턴의 구조는 다음과 같습니다.

 

abstract factory 패턴

 

 

 

구조를 기반으로 정의되는 순서에 따라 정리를 해보면 다음과 같습니다.

 

  • 필요한 객체 개수만큼 Abstract Product를 생성합니다.
  • 활용될 Factory 개수 만큼 Abstract Product를 상속받아 Product 구현체를 생성합니다.
  • Abstract Factory를 생성합니다.
  • 필요한 Factory 개수 만큼 Abstract Factory를 상속받아 Factory 구현체를 생성합니다.
  • Factory 구현체를 생성할 때 각 Factory에 사용되어야 하는 Product를 연결해 줍니다.

 

 

 

 

Abstract Factory 패턴 특징

 

abstract factory 패턴의 특징은 factory method 패턴의 특징과 유사하며 다음과 같습니다.

 

 

 

[ 장점 ]

 

  • 서로 연관된 객체들을 집합 군으로 묶기 때문에 단일 객체로 관리될 수 있도록 도와줌
  • 추가 factory가 필요할 때 기존 구현되어 있는 factory를 고려할 필요가 없음
  • 기능 추가에 대한 예상치 못한 side effect가 발생하지 않음
  • 유연하고 확장성 있는 개발을 할 수 있음

 

 

 

[ 단점 ]

 

  • product 및 factory 생성을 위한 객체가 방대해짐
  • abstract factory에 변경 점이 발생하면 하위 factory에 모두 영향을 미칠 수 있음

 

 

 

 

abstract factory 패턴도 factory method에서 준수하고 있는 solid 원칙의 ocp에 대해 얘기해 볼 수 있습니다.

 

추상화를 이용하여 확장에는 열려 있지만 수정에는 닫혀 있는 구조를 가지고 있기 때문에 abstract factory 또한 ocp를 준수하게 됩니다.

 

또한 객체 생성을 위한 각각의 factory들이 존재하기 때문에 solid 원칙 중 srp도 준수하는 것을 확인할 수 있습니다.

 

 

 

 

Abstract Factory 패턴 예시 (1) - Before (디자인 패턴 미 적용)

 

위에서 얘기드렸던 UI 컴포넌트와 관련되어 간단하게 예시를 작성해 보겠습니다.

 

먼저 디자인 패턴을 적용하지 않는다면 다음과 같이 간단하게 구현될 수 있습니다.

 

// browser component
package com.jforj.abstractfactory.beforenodesign;

public class BrowserComponent {

    public void createButton(String type) {
        switch (type) {
            case "chrome": {
                System.out.println("chrome button create");
                break;
            }

            case "edge": {
                System.out.println("edge button create");
                break;
            }

            default: {
                System.out.println("do not create button");
            }
        }
    }

    public void createTextField(String type) {
        switch (type) {
            case "chrome": {
                System.out.println("chrome text field create");
                break;
            }

            case "edge": {
                System.out.println("edge text field create");
                break;
            }

            default: {
                System.out.println("do not create text field");
            }
        }
    }
}


// main
package com.jforj.abstractfactory.beforenodesign;

public class Main {
    public static void main(String[] args) {
        BrowserComponent browserComponent = new BrowserComponent();

        // chrome
        String chromeType = "chrome";
        browserComponent.createButton(chromeType);
        browserComponent.createTextField(chromeType);

        // edge
        String edgeType = "edge";
        browserComponent.createButton(edgeType);
        browserComponent.createTextField(edgeType);
    }
}

 

 

 

 

여기서 만약 safari에 대해서도 고려를 한다고 가정해 보겠습니다.

 

그러면 chrome 및 edge에서도 사용되고 있는 기능들에 대해서도 수정 사항이 반영되는 결과를 만듭니다.

 

// browser component
package com.jforj.abstractfactory.beforenodesign;

public class BrowserComponent {

    public void createButton(String type) {
        switch (type) {
            case "chrome": {
                System.out.println("chrome button create");
                break;
            }

            case "edge": {
                System.out.println("edge button create");
                break;
            }

            case "safari": {
                System.out.println("safari button create");
                break;
            }

            default: {
                System.out.println("do not create button");
            }
        }
    }

    public void createTextField(String type) {
        switch (type) {
            case "chrome": {
                System.out.println("chrome text field create");
                break;
            }

            case "edge": {
                System.out.println("edge text field create");
                break;
            }

            case "safari": {
                System.out.println("safari text field create");
                break;
            }

            default: {
                System.out.println("do not create text field");
            }
        }
    }
}


// main
package com.jforj.abstractfactory.beforenodesign;

public class Main {
    public static void main(String[] args) {
        BrowserComponent browserComponent = new BrowserComponent();

        // chrome
        String chromeType = "chrome";
        browserComponent.createButton(chromeType);
        browserComponent.createTextField(chromeType);

        // edge
        String edgeType = "edge";
        browserComponent.createButton(edgeType);
        browserComponent.createTextField(edgeType);

        // safari
        String safariType = "safari";
        browserComponent.createButton(safariType);
        browserComponent.createTextField(safariType);
    }
}

 

 

 

 

Abstract Factory 패턴 예시 (2) - Before (Factory Method 패턴 적용)

 

이번엔 factory method 패턴을 적용해 보는 코드를 작성해 보겠습니다.

 

factory method 패턴에 맞게 각 객체를 생성한 factory를 각각 구성할 수 있고 결과적으로 다음과 같이 코드가 변경됩니다.

 

// browser button
package com.jforj.abstractfactory.beforefactorymethod.button;

public interface BrowserButton {

    void print();
}


// chrome button
package com.jforj.abstractfactory.beforefactorymethod.button;

public class ChromeButton implements BrowserButton {

    @Override
    public void print() {
        System.out.println("chrome button create");
    }
}


// edge button
package com.jforj.abstractfactory.beforefactorymethod.button;

public class EdgeButton implements BrowserButton {

    @Override
    public void print() {
        System.out.println("edge button create");
    }
}


// browser button factory
package com.jforj.abstractfactory.beforefactorymethod.button;

public interface BrowserButtonFactory {

    default BrowserButton getInstance() {
        return createButton();
    }

    BrowserButton createButton();
}


// chrome button factory
package com.jforj.abstractfactory.beforefactorymethod.button;

public class ChromeButtonFacotry implements BrowserButtonFactory {

    @Override
    public BrowserButton createButton() {
        return new ChromeButton();
    }
}


// edge button factory
package com.jforj.abstractfactory.beforefactorymethod.button;

public class EdgeButtonFactory implements BrowserButtonFactory {

    @Override
    public BrowserButton createButton() {
        return new EdgeButton();
    }
}


// browser text field
package com.jforj.abstractfactory.beforefactorymethod.textfield;

public interface BrowserTextField {

    void print();
}


// chrome text field
package com.jforj.abstractfactory.beforefactorymethod.textfield;

public class ChromeTextField implements BrowserTextField {

    @Override
    public void print() {
        System.out.println("chrome text field create");
    }
}


// edge text field
package com.jforj.abstractfactory.beforefactorymethod.textfield;

public class EdgeTextField implements BrowserTextField {

    @Override
    public void print() {
        System.out.println("edge text field create");
    }
}


// browser text field factory
package com.jforj.abstractfactory.beforefactorymethod.textfield;

public interface BrowserTextFieldFactory {

    default BrowserTextField getInstance() {
        return createTextField();
    }

    BrowserTextField createTextField();
}


// chrome text field factory
package com.jforj.abstractfactory.beforefactorymethod.textfield;

public class ChromeTextFieldFacotry implements BrowserTextFieldFactory {

    @Override
    public BrowserTextField createTextField() {
        return new ChromeTextField();
    }
}


// edge text field factory
package com.jforj.abstractfactory.beforefactorymethod.textfield;

public class EdgeTextFieldFactory implements BrowserTextFieldFactory {

    @Override
    public BrowserTextField createTextField() {
        return new EdgeTextField();
    }
}


// main
package com.jforj.abstractfactory.beforefactorymethod;

import com.jforj.abstractfactory.beforefactorymethod.button.BrowserButton;
import com.jforj.abstractfactory.beforefactorymethod.button.ChromeButtonFacotry;
import com.jforj.abstractfactory.beforefactorymethod.button.EdgeButtonFactory;
import com.jforj.abstractfactory.beforefactorymethod.textfield.BrowserTextField;
import com.jforj.abstractfactory.beforefactorymethod.textfield.ChromeTextFieldFacotry;
import com.jforj.abstractfactory.beforefactorymethod.textfield.EdgeTextFieldFactory;

public class Main {
    public static void main(String[] args) {
        // chrome
        BrowserButton chromeButton = new ChromeButtonFacotry().getInstance();
        BrowserTextField chromeTextField = new ChromeTextFieldFacotry().getInstance();
        chromeButton.print();
        chromeTextField.print();

        // edge
        BrowserButton edgeButton = new EdgeButtonFactory().getInstance();
        BrowserTextField edgeTextField = new EdgeTextFieldFactory().getInstance();
        edgeButton.print();
        edgeTextField.print();
    }
}

 

 

 

 

factory method 패턴의 단점에 맞게 객체 생성이 많아질수록 클래스 파일의 개수가 많아지는 것을 경험할 수 있습니다.

 

하지만 유연한 확장성이 좋다는 장점을 볼 수 있도록 이곳에도 safari를 추가해 보겠습니다.

 

// browser button (이전과 동일)
// chrome button (이전과 동일)
// edge button (이전과 동일)
// safari button
package com.jforj.abstractfactory.beforefactorymethod.button;

public class SafariButton implements BrowserButton {

    @Override
    public void print() {
        System.out.println("safari button create");
    }
}


// browser button factory (이전과 동일)
// chrome button factory (이전과 동일)
// edge button factory (이전과 동일)
// safari button factory
package com.jforj.abstractfactory.beforefactorymethod.button;

public class SafariButtonFactory implements BrowserButtonFactory {

    @Override
    public BrowserButton createButton() {
        return new SafariButton();
    }
}


// browser text field (이전과 동일)
// chrome text field (이전과 동일)
// edge text field (이전과 동일)
// safari text field
package com.jforj.abstractfactory.beforefactorymethod.textfield;

public class SafariTextField implements BrowserTextField {

    @Override
    public void print() {
        System.out.println("safari text field create");
    }
}


// browser text field factory (이전과 동일)
// chrome text field factory (이전과 동일)
// edge text field factory (이전과 동일)
// safari text field factory
package com.jforj.abstractfactory.beforefactorymethod.textfield;

public class SafariTextFieldFactory implements BrowserTextFieldFactory {

    @Override
    public BrowserTextField createTextField() {
        return new SafariTextField();
    }
}


// main
package com.jforj.abstractfactory.beforefactorymethod;

import com.jforj.abstractfactory.beforefactorymethod.button.BrowserButton;
import com.jforj.abstractfactory.beforefactorymethod.button.ChromeButtonFacotry;
import com.jforj.abstractfactory.beforefactorymethod.button.EdgeButtonFactory;
import com.jforj.abstractfactory.beforefactorymethod.button.SafariButtonFactory;
import com.jforj.abstractfactory.beforefactorymethod.textfield.BrowserTextField;
import com.jforj.abstractfactory.beforefactorymethod.textfield.ChromeTextFieldFacotry;
import com.jforj.abstractfactory.beforefactorymethod.textfield.EdgeTextFieldFactory;
import com.jforj.abstractfactory.beforefactorymethod.textfield.SafariTextFieldFactory;

public class Main {
    public static void main(String[] args) {
        // chrome
        BrowserButton chromeButton = new ChromeButtonFacotry().getInstance();
        BrowserTextField chromeTextField = new ChromeTextFieldFacotry().getInstance();
        chromeButton.print();
        chromeTextField.print();

        // edge
        BrowserButton edgeButton = new EdgeButtonFactory().getInstance();
        BrowserTextField edgeTextField = new EdgeTextFieldFactory().getInstance();
        edgeButton.print();
        edgeTextField.print();

        // safari
        BrowserButton safariButton = new SafariButtonFactory().getInstance();
        BrowserTextField safariTextField = new SafariTextFieldFactory().getInstance();
        safariButton.print();
        safariTextField.print();
    }
}

 

 

 

 

Abstract Factory 패턴 예시 (3) - After

 

이제는 abstract factory 패턴을 적용한 예시를 작성해 보겠습니다.

 

위의 예시들과 동일한 결과를 구성한다고 했을 때 다음과 같이 코드가 변경될 수 있습니다.

 

// browser button
package com.jforj.abstractfactory.after.button;

public interface BrowserButton {

    void print();
}


// chrome button
package com.jforj.abstractfactory.after.button;

public class ChromeButton implements BrowserButton {

    @Override
    public void print() {
        System.out.println("chrome button create");
    }
}


// edge button
package com.jforj.abstractfactory.after.button;

public class EdgeButton implements BrowserButton {

    @Override
    public void print() {
        System.out.println("edge button create");
    }
}


// browser text field
package com.jforj.abstractfactory.after.textfield;

public interface BrowserTextField {

    void print();
}


// chrome text field
package com.jforj.abstractfactory.after.textfield;

public class ChromeTextField implements BrowserTextField {

    @Override
    public void print() {
        System.out.println("chrome text field create");
    }
}


// edge text field
package com.jforj.abstractfactory.after.textfield;

public class EdgeTextField implements BrowserTextField {

    @Override
    public void print() {
        System.out.println("edge text field create");
    }
}


// browser factory
package com.jforj.abstractfactory.after;

import com.jforj.abstractfactory.after.button.BrowserButton;
import com.jforj.abstractfactory.after.textfield.BrowserTextField;

public interface BrowserFactory {

    BrowserButton createButton();

    BrowserTextField createTextField();
}


// chrome factory
package com.jforj.abstractfactory.after;

import com.jforj.abstractfactory.after.button.BrowserButton;
import com.jforj.abstractfactory.after.button.ChromeButton;
import com.jforj.abstractfactory.after.textfield.BrowserTextField;
import com.jforj.abstractfactory.after.textfield.ChromeTextField;

public class ChromeFactory implements BrowserFactory {

    @Override
    public BrowserButton createButton() {
        return new ChromeButton();
    }

    @Override
    public BrowserTextField createTextField() {
        return new ChromeTextField();
    }
}


// edge factory
package com.jforj.abstractfactory.after;

import com.jforj.abstractfactory.after.button.BrowserButton;
import com.jforj.abstractfactory.after.button.EdgeButton;
import com.jforj.abstractfactory.after.textfield.BrowserTextField;
import com.jforj.abstractfactory.after.textfield.EdgeTextField;

public class EdgeFactory implements BrowserFactory {

    @Override
    public BrowserButton createButton() {
        return new EdgeButton();
    }

    @Override
    public BrowserTextField createTextField() {
        return new EdgeTextField();
    }
}


// main
package com.jforj.abstractfactory.after;

import com.jforj.abstractfactory.after.button.BrowserButton;
import com.jforj.abstractfactory.after.textfield.BrowserTextField;

public class Main {
    public static void main(String[] args) {
        // chrome
        renderBrowserComponent(new ChromeFactory());

        // edge
        renderBrowserComponent(new EdgeFactory());
    }

    public static void renderBrowserComponent(BrowserFactory browserFactory) {
        BrowserButton edgeButton = browserFactory.createButton();
        BrowserTextField edgeTextField = browserFactory.createTextField();
        edgeButton.print();
        edgeTextField.print();
    }
}

 

 

 

 

코드를 보시는 것과 같이 집합 군을 묶어 하나의 단일 객체에서 관련된 모든 객체들이 관리될 수 있습니다.

 

또한 factory method 패턴처럼 유연한 확장이 가능하기에 safari를 동일하게 추가하면 다음과 같이 코드가 작성됩니다.

 

// browser button (이전과 동일)
// chrome button (이전과 동일)
// edge button (이전과 동일)
// safari button
package com.jforj.abstractfactory.after.button;

public class SafariButton implements BrowserButton {

    @Override
    public void print() {
        System.out.println("safari button create");
    }
}


// browser text field (이전과 동일)
// chrome text field (이전과 동일)
// edge text field (이전과 동일)
// safari text field
package com.jforj.abstractfactory.after.textfield;

public class SafariTextField implements BrowserTextField {

    @Override
    public void print() {
        System.out.println("safari text field create");
    }
}


// browser factory (이전과 동일)
// chrome factory (이전과 동일)
// edge factory (이전과 동일)
// safari factory
package com.jforj.abstractfactory.after;

import com.jforj.abstractfactory.after.button.BrowserButton;
import com.jforj.abstractfactory.after.button.SafariButton;
import com.jforj.abstractfactory.after.textfield.BrowserTextField;
import com.jforj.abstractfactory.after.textfield.SafariTextField;

public class SafariFactory implements BrowserFactory {

    @Override
    public BrowserButton createButton() {
        return new SafariButton();
    }

    @Override
    public BrowserTextField createTextField() {
        return new SafariTextField();
    }
}


// main
package com.jforj.abstractfactory.after;

import com.jforj.abstractfactory.after.button.BrowserButton;
import com.jforj.abstractfactory.after.textfield.BrowserTextField;

public class Main {
    public static void main(String[] args) {
        // chrome
        renderBrowserComponent(new ChromeFactory());

        // edge
        renderBrowserComponent(new EdgeFactory());

        // safari
        renderBrowserComponent(new SafariFactory());
    }

    public static void renderBrowserComponent(BrowserFactory browserFactory) {
        BrowserButton edgeButton = browserFactory.createButton();
        BrowserTextField edgeTextField = browserFactory.createTextField();
        edgeButton.print();
        edgeTextField.print();
    }
}

 

 

 

 

 

 

 

 

이상으로 추상 팩토리 (Abstract Factory) 패턴에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글