아무거나

[Design Pattern] Abstract Factory Pattern 본문

Java & Kotlin/Java

[Design Pattern] Abstract Factory Pattern

전봉근 2019. 12. 10. 00:16
반응형

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

인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성할 수 있다.

제품군을 만들 때를 예로 들어보자.

추상 팩토리 패턴을 사용하면 클라이언트에서 추상 인터페이스를 통하여 일련의 제품들을 공급받을 수 있다. 실제로 어떤 제품이 생산되는지도 전혀 알 필요가 없다. 따라서 클라이언트와 팩토리에서 생산되는 제품을 분리시킬 수 있다.

  • AbstractFactory: 모든 구상 팩토리에서 구현해야 하는 인터페이스이다. 제품을 생산하기 위한 일련의 메소드들이 정의되어 있다.
  • ConcreteFactory1~2: 구상 팩토리에서는 서로 다른 제품군을 구현한다. 클라이언트에서 제품이 필요하면 이 팩토리 가운데 적당한걸 선택하여 쓰면 되기 때문에 제품 객체의 인스턴스를 직접 만들 필요가 없다.
  • Client: 클라이언트를 만들 때는 추상 팩토리를 바탕으로 만든다. 실제 팩토리는 실행시에 결정된다.
  • AbstractProductA~B: 제품군 각 구상 팩토리에서 필요한 제품들을 모두 만들 수 있다.

아마 이렇게 설명을 보면 이해가 안 될 것이다. 실습을 통해 이해하자.

추상 팩토리 패턴 예시 - 1

자전거 부품들을 생산하는 공장(=Factory)을 예시로 만들어보자.

abst 패키지를 생성하고 자전거의 부품들과 팩토리를 생성
[Body.java]

package com.bkjeon.abstract_factory.abst;

public interface Body {}

[Wheel.java]

package com.bkjeon.abstract_factory.abst;

public interface Wheel {}

[BikeFactory.java]

package com.bkjeon.abstract_factory.abst;

public interface BikeFactory {

    public Body createBody();
    public Wheel creatWheel();

}

bong 패키지를 생성(자전거 메이커 관련 패키지) 후 각각의부품 구현
[BongBody.java]

package com.bkjeon.abstract_factory.bong;

import com.bkjeon.abstract_factory.abst.Body;

public class BongBody implements Body {}

[BongWheel.java]

package com.bkjeon.abstract_factory.bong;

import com.bkjeon.abstract_factory.abst.Wheel;

public class BongWheel implements Wheel {}

[BongFactory.java]

package com.bkjeon.abstract_factory.bong;

import com.bkjeon.abstract_factory.abst.BikeFactory;
import com.bkjeon.abstract_factory.abst.Body;
import com.bkjeon.abstract_factory.abst.Wheel;

public class BongFactory implements BikeFactory {

    @Override
    public Body createBody() {
        return new BongBody();
    }

    @Override
    public Wheel creatWheel() {
        return new BongWheel();
    }
}

메인 클래스를 구현하자.
[Main.java]

package com.bkjeon.abstract_factory;

import com.bkjeon.abstract_factory.abst.BikeFactory;
import com.bkjeon.abstract_factory.abst.Body;
import com.bkjeon.abstract_factory.abst.Wheel;
import com.bkjeon.abstract_factory.bong.BongFactory;

public class Main {

    public static void main(String[] args) {
        BikeFactory factory = new BongFactory();

        Body body = factory.createBody();
        Wheel wheel = factory.creatWheel();

        System.out.println(body.getClass());
        System.out.println(wheel.getClass());
    }

}

실행결과

class com.bkjeon.abstract_factory.bong.BongBody
class com.bkjeon.abstract_factory.bong.BongWheel

실행하면 해당 클래스의 구조를 볼 수 있다. 왜 이렇게 하냐면 만약 자전거를 생성하려면 자전거를 만드는 공장이 필요하고 그 공장이 Bong인 것이다.

추가적으로 Keun이라는 하나의 공장을 더 만들어보자. 그러면 keun 패키지를 생성하고 bong패키지와 같은 구조로 생성해주자.
[KeunBody.java]

package com.bkjeon.abstract_factory.keun;

import com.bkjeon.abstract_factory.abst.Body;

public class KeunBody implements Body {}

[KeunWheel.java]

package com.bkjeon.abstract_factory.keun;

import com.bkjeon.abstract_factory.abst.Wheel;

public class KeunWheel implements Wheel {}

[KeunBikeFactory.java]

package com.bkjeon.abstract_factory.keun;

import com.bkjeon.abstract_factory.abst.BikeFactory;
import com.bkjeon.abstract_factory.abst.Body;
import com.bkjeon.abstract_factory.abst.Wheel;

public class KeunBikeFactory implements BikeFactory {
    @Override
    public Body createBody() {
        return new KeunBody();
    }

    @Override
    public Wheel creatWheel() {
        return new KeunWheel();
    }
}

메인 클래스를 수정하자.
[Main.java]

package com.bkjeon.abstract_factory;

import com.bkjeon.abstract_factory.abst.BikeFactory;
import com.bkjeon.abstract_factory.abst.Body;
import com.bkjeon.abstract_factory.abst.Wheel;
import com.bkjeon.abstract_factory.keun.KeunBikeFactory;

public class Main {

    public static void main(String[] args) {
//        BikeFactory factory = new BongFactory();
        BikeFactory factory = new KeunBikeFactory();

        Body body = factory.createBody();
        Wheel wheel = factory.creatWheel();

        System.out.println(body.getClass());
        System.out.println(wheel.getClass());
    }

}

실행결과

class com.bkjeon.abstract_factory.keun.KeunBody
class com.bkjeon.abstract_factory.keun.KeunWheel

이렇게 다양한 물건들을 한 가지의 팩토리로 묶어줘서 동일한 방식으로 생성할 수 있게 해주는것이 추상 팩토리 패턴이다.

추상 팩토리 패턴 예시 - 2

우선 추상적인 클래스가 있는 패키지인 abst와 구체적인 클래스가 모여있는 win이라는 패키지를 생성하자.

그 다음 GUI 관련 인터페이스를 생성하자.
[GuiFac.java]

package com.bkjeon.abstract_factory2.abst;

public interface GuiFac {

    public Button createButton();
    public TextArea createTextArea();

}

[Button.java]

package com.bkjeon.abstract_factory2.abst;

public interface Button {

    public void click();

}

[TextArea.java]

package com.bkjeon.abstract_factory2.abst;

public interface TextArea {

    public String getText();

}

그 다음은 GUI 환경에서 할 수 있는 OS의 네임인 패키지를 생성해보자.

  • linux, mac, win(위에서 이미 생성해놓았다.)

먼저 linux 패키지에서 추상 객체들을 구체적인 클래스 객체로 만들어주자.
[LinuxGuiFac.java]

package com.bkjeon.abstract_factory2.linux;

import com.bkjeon.abstract_factory2.abst.Button;
import com.bkjeon.abstract_factory2.abst.GuiFac;
import com.bkjeon.abstract_factory2.abst.TextArea;

public class LinuxGuiFac implements GuiFac {
    @Override
    public Button createButton() {
        return new LinuxButton();
    }

    @Override
    public TextArea createTextArea() {
        return new LinuxTextArea();
    }
}

[LinuxButton.java]

package com.bkjeon.abstract_factory2.linux;

import com.bkjeon.abstract_factory2.abst.Button;

public class LinuxButton implements Button {
    @Override
    public void click() {
        System.out.println("리눅스 버튼");
    }
}

[LinuxTextArea.java]

package com.bkjeon.abstract_factory2.linux;

import com.bkjeon.abstract_factory2.abst.TextArea;

public class LinuxTextArea implements TextArea {
    @Override
    public String getText() {
        return "리눅스 텍스트 에어리어";
    }
}

메인 클래스를 생성하자.
[Main.java]

package com.bkjeon.abstract_factory2;

import com.bkjeon.abstract_factory2.abst.Button;
import com.bkjeon.abstract_factory2.abst.GuiFac;
import com.bkjeon.abstract_factory2.abst.TextArea;
import com.bkjeon.abstract_factory2.linux.LinuxGuiFac;

public class Main {

    public static void main(String[] args) {
        GuiFac fac = new LinuxGuiFac();
        Button button = fac.createButton();
        TextArea area = fac.createTextArea();

        button.click();
        System.out.println(area.getText());
    }

}

실행결과

리눅스 버튼
리눅스 텍스트 에어리어

그 다음 mac도 똑같이 만들어보자. 우선 mac 관련 클래스들을 생성하자.
[MacGuiFac.java]

package com.bkjeon.abstract_factory2.mac;

import com.bkjeon.abstract_factory2.abst.Button;
import com.bkjeon.abstract_factory2.abst.GuiFac;
import com.bkjeon.abstract_factory2.abst.TextArea;

public class MacGuiFac implements GuiFac {
    @Override
    public Button createButton() {
        return new MacButton();
    }

    @Override
    public TextArea createTextArea() {
        return new MacTextArea();
    }
}

[MacButton.java]

package com.bkjeon.abstract_factory2.mac;

import com.bkjeon.abstract_factory2.abst.Button;

public class MacButton implements Button {
    @Override
    public void click() {
        System.out.println("맥 버튼");
    }
}

[MacTextArea.java]

package com.bkjeon.abstract_factory2.mac;

import com.bkjeon.abstract_factory2.abst.TextArea;

public class MacTextArea implements TextArea {
    @Override
    public String getText() {
        return "맥 텍스트 에어리어";
    }
}

메인 클래스를 수정하자.
[Main.java]

package com.bkjeon.abstract_factory2;

import com.bkjeon.abstract_factory2.abst.Button;
import com.bkjeon.abstract_factory2.abst.GuiFac;
import com.bkjeon.abstract_factory2.abst.TextArea;
import com.bkjeon.abstract_factory2.mac.MacGuiFac;

public class Main {

    public static void main(String[] args) {
//        GuiFac fac = new LinuxGuiFac();
        GuiFac fac = new MacGuiFac();   // 이 부분만 변경해주면 된다.
        Button button = fac.createButton();
        TextArea area = fac.createTextArea();

        button.click();
        System.out.println(area.getText());
    }

}

실행결과

맥 버튼
맥 텍스트 에어리어

또 똑같은 작업이니 win 패키지도 구현해보자.
[WinGuiFac.java]

package com.bkjeon.abstract_factory2.win;

import com.bkjeon.abstract_factory2.abst.Button;
import com.bkjeon.abstract_factory2.abst.GuiFac;
import com.bkjeon.abstract_factory2.abst.TextArea;

public class WinGuiFac implements GuiFac {
    @Override
    public Button createButton() {
        return new WinButton();
    }

    @Override
    public TextArea createTextArea() {
        return new WinTextArea();
    }
}

[WinButton.java]

package com.bkjeon.abstract_factory2.win;

import com.bkjeon.abstract_factory2.abst.Button;

public class WinButton implements Button {
    @Override
    public void click() {
        System.out.println("윈도우 버튼");
    }
}

[WinTextArea.java]

package com.bkjeon.abstract_factory2.win;

import com.bkjeon.abstract_factory2.abst.TextArea;

public class WinTextArea implements TextArea {
    @Override
    public String getText() {
        return "윈도우 텍스트 에어리어";
    }
}

[Main.java]

package com.bkjeon.abstract_factory2;

import com.bkjeon.abstract_factory2.abst.Button;
import com.bkjeon.abstract_factory2.abst.GuiFac;
import com.bkjeon.abstract_factory2.abst.TextArea;
import com.bkjeon.abstract_factory2.win.WinGuiFac;

public class Main {

    public static void main(String[] args) {
//        GuiFac fac = new LinuxGuiFac();
//        GuiFac fac = new MacGuiFac();
        GuiFac fac = new WinGuiFac();
        Button button = fac.createButton();
        TextArea area = fac.createTextArea();

        button.click();
        System.out.println(area.getText());
    }

}

실행결과

윈도우 버튼
윈도우 텍스트 에어리어

위와 같이 Mac이면 new MacGuiFac(); 를 사용하고 Linux면 new LinuxGuiFac(); 를 사용하는 것은 패턴을 잘 사용한 것이 아니라고 한다 그래서 하나 패키지를 더 만들자.

concrete 패키지를 생성
[FactoryInstance.java]

package com.bkjeon.abstract_factory2.concrete;

import com.bkjeon.abstract_factory2.abst.GuiFac;
import com.bkjeon.abstract_factory2.linux.LinuxGuiFac;
import com.bkjeon.abstract_factory2.mac.MacGuiFac;
import com.bkjeon.abstract_factory2.win.WinGuiFac;

public class FactoryInstance {

    // 시스템 OS 정보에 따라 필요한 팩토리를 리턴
    public static GuiFac getGuiFac(String osName) {
        switch (getOsCode(osName)) {
            case 0: return new MacGuiFac();
            case 1: return new LinuxGuiFac();
            case 2: return new WinGuiFac();
        }

        return null;
    }

    private static int getOsCode(String osName) {
        // 해당 내용은 System.getProperty("os.name") 값에 따라서 정규표현식으로 리턴값을 지정해주면 된다.
        if (osName.equals("Mac OS X")) {
            return 0;
        } else if (osName.equals("Windows 10")) {
            return 2;
        }

        return 1;
    }

}

Main 클래스를 변경한다.
[Main.java]

package com.bkjeon.abstract_factory2;

import com.bkjeon.abstract_factory2.abst.Button;
import com.bkjeon.abstract_factory2.abst.GuiFac;
import com.bkjeon.abstract_factory2.abst.TextArea;
import com.bkjeon.abstract_factory2.concrete.FactoryInstance;

public class Main {

    public static void main(String[] args) {
//        GuiFac fac = new LinuxGuiFac();
//        GuiFac fac = new MacGuiFac();
//        GuiFac fac = new WinGuiFac();
        // 만약 여기서 FactoryInstance.getGuiFac() 에다 넘겨주는것이 일정하다면
        // Linux면 LinuxGuiFac를 넘겨주고, Mac에서는 MacGuiFac를 넘겨주게 한다면은 메인클래스의 코드는 건들 필요가 없어진다.
        // abst와 concrete 패키지는 라이브러리 형태로 적용될거고 이 두개에서 인터페이스만 사용해서 우리가 원하는 기능을 구현하면 된다.
        // 즉, 우리가 어떤 환경에서 해당 프로그램을 돌리든지 동일한 동작을 하기위해선 해당 소스가 OS와 관계없이 동일하게 적용되어야 한다.
        GuiFac fac = FactoryInstance.getGuiFac(System.getProperty("os.name"));

        Button button = fac.createButton();
        TextArea area = fac.createTextArea();

        button.click();
        System.out.println(area.getText());
    }

}

 

참고: https://www.inflearn.com/course/%EC%9E%90%EB%B0%94-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4/dashboard

반응형

'Java & Kotlin > Java' 카테고리의 다른 글

[Design Pattern] Composite Pattern  (0) 2019.12.21
[Design Pattern] Bridge Pattern  (0) 2019.12.12
[Design Pattern] Builder Pattern  (0) 2019.12.08
[Design Pattern] Prototype Pattern  (0) 2019.12.08
[Design Pattern] Singleton Pattern  (0) 2019.12.07
Comments