책&강의 정리

[책 정리] 디자인 패턴 - 팩토리 패턴

마볼링 2023. 10. 28. 18:52

시작하기전 질문

✏️ 9. 팩토리 패턴이 무엇인가요?

  • 객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴입니다.
  • 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴입니다.

✏️ 10. 팩토리 패턴은 그럼 왜 사용하나요?

  • 객체 생성 로직을 추상화하여 유지보수성을 향상하고, 클라이언트 코드와 객체 생성을 분리하기 위해 사용합니다.
  • 주로 다양한 객체 유형을 생성해야 하고, 생성 과정이 복잡하거나 변경 가능한 경우에 유용하게 사용됩니다.

✏️ 11. 팩토리 패턴은 어떻게 사용하나요?

  • Java의 경우, 팩토리 메서드나 추상 팩토리 인터페이스를 정의하고, 서브클래스에서 구체적인 객체를 생성하도록 구현합니다.

✏️ 12. 추상 팩토리 패턴이 뭔가요?

  • 추상 팩토리 패턴은 관련되 객체 집합을 생성하기 위한 인터페이스를 제공하고, 서브클래스에서 이 인터페이스를 구현하여 관련된 객체를 생성합니다.

✏️ 13. 그럼 팩토리 메소드 패턴과 추상 팩토리 패턴의 차이점을 설명해주세요.

  • 팩토리 패턴은 한 종류의 객체를 생성하기 위해 사용되지만, 추상 팩토리 패턴은 연관되거나 의존적인 객체로 이루어진 여러 종류의 객체를 생성하기 위해 사용된다.
  • 팩토리 패턴은 팩토리 인터페이스를 구현하여 그 자체가 하나의 객체를 생성하는데 사용되지만,
    추상 팩토리 패턴은 팩토리 객체가 아닌 다른 객체 내부에 구현되어 해당 객체에서 여러 타입의 객체를 생성하기 위해 사용된다.

✏️ 14. 싱글톤 패턴과 팩토리 패턴을 비교하면 어떤 차이가 있나요?

  • 싱글톤 패턴은 단일 인스턴스를 유지하는 데 중점을 두고, 팩토리 패턴은 객체 생성을 추상화하고 다형성을 제공하는 데 중점을 둡니다.

디자인패턴

프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계 등을 이용하여 해결할 수 있도록 하나의 '규약'형태로 만들어 놓은 것

팩토리 패턴

  • 객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴
  • 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴
  • 상위 클래스와 하위 클래스가 분ㄹ되기 때문에 느슨한 결합을 가지며 상위 클래스에서는 인스턴스 생성 방식에 대해 전혀 알 필요가 없기 때문에 더 많은 유연성을 가지게 된다.
  • 객체 생성 로직이 따로 떼어져 있기 때문에 코드를 리팩터링하더라도 한 곳만 고칠 수 있게 되니 유지 보수성이 증가된다.

1. 자바스크립트의 팩토리 패턴

const num = new Object(42)
const str = new Object('abc')
num.constructor.name; //Number
str.constructor.name; //String

숫자를 전달하거나 문자열을 전달함에 따라 다른 타입의 객체 생성
즉, 전달받은 값에 따라 다른 객체를 생성하며 인스턴스의 타입 등을 정한다.

class CoffeeFactory {
    static createCoffee(type) {
        const factory = factoryList[type]
        return factory.createCoffee()
    }
}
class Latte {
    constructor() {
        this.name = "latte"
    }
}
class Espresso {
    constructor() {
        this.name = "Espresso"
    }
}

class LatteFactory extends CoffeeFactory{
    static createCoffee() {
        return new Latte()
    }
}
class EspressoFactory extends CoffeeFactory{
    static createCoffee() {
        return new Espresso()
    }
}
const factoryList = { LatteFactory, EspressoFactory }

const main = () => {
    // 라떼 커피를 주문한다.
    const coffee = CoffeeFactory.createCoffee("LatteFactory")
    // 커피 이름을 부른다.
    console.log(coffee.name) // latte
}
main()

CoffeeFactory라는 상위 클래스가 중요한 뼈대를 결정하고,
하위 클래스인 LatteFactory가 구체적인 내용을 결정한다.

2. 자바의 팩토리 패턴

enum CoffeeType {
    LATTE,
    ESPRESSO
}

abstract class Coffee {
    protected String name;

    public String getName() {
        return name;
    }
}

class Latte extends Coffee {
    public Latte() {
        name = "latte";
    }
}

class Espresso extends Coffee {
    public Espresso() {
        name = "Espresso";
    }
}

class CoffeeFactory {
    public static Coffee createCoffee(CoffeeType type) {
        switch (type) {
            case LATTE:
                return new Latte();
            case ESPRESSO:
                return new Espresso();
            default:
                throw new IllegalArgumentException("Invalid coffee type: " + type);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Coffee coffee = CoffeeFactory.createCoffee(CoffeeType.LATTE);
        System.out.println(coffee.getName()); // latte
    }
}

3. 팩토리 메서드 패턴과 추상 팩토리 패턴

  • 팩토리 패턴은 한 종류의 객체를 생성하기 위해 사용되지만, 추상 팩토리 패턴은 연관되거나 의존적인 객체로 이루어진 여러 종류의 객체를 생성하기 위해 사용된다.
  • 팩토리 패턴은 팩토리 인터페이스를 구현하여 그 자체가 하나의 객체를 생성하는데 사용되지만,
    추상 팩토리 패턴은 팩토리 객체가 아닌 다른 객체 내부에 구현되어 해당 객체에서 여러 타입의 객체를 생성하기 위해 사용된다.

예시

[super class]

public abstract class Computer {

    public abstract String getRAM();
    public abstract String getHDD();
    public abstract String getCPU();

    @Override
    public String toString(){
        return "RAM= "+this.getRAM()+", HDD="+this.getHDD()+", CPU="+this.getCPU();
    }
}

[sub class-1]

public class PC extends Computer {

    private String ram;
    private String hdd;
    private String cpu;

    public PC(String ram, String hdd, String cpu){
        this.ram=ram;
        this.hdd=hdd;
        this.cpu=cpu;
    }
    @Override
    public String getRAM() {
        return this.ram;
    }

    @Override
    public String getHDD() {
        return this.hdd;
    }

    @Override
    public String getCPU() {
        return this.cpu;
    }

}

[sub class - 2]

public class Server extends Computer {

    private String ram;
    private String hdd;
    private String cpu;

    public Server(String ram, String hdd, String cpu){
        this.ram=ram;
        this.hdd=hdd;
        this.cpu=cpu;
    }
    @Override
    public String getRAM() {
        return this.ram;
    }

    @Override
    public String getHDD() {
        return this.hdd;
    }

    @Override
    public String getCPU() {
        return this.cpu;
    }

}

여기까지는 팩토리 패턴과 같다

먼저 추상 팩토리의 역할을 하는 인터페이스 또는 추상 클래스가 필요하다.

public interface ComputerAbstractFactory {

    public Computer createComputer();

}

createComputer() 메소드의 리턴 타입이 super class인 Computer

팩토리 인터페이스를 구현(implements)하는 클래스에서 createComputer() 메소드를 오버라이딩하여 각각의서브 클래스 리턴 - 자바의 다형성

public class PCFactory implements ComputerAbstractFactory {

    private String ram;
    private String hdd;
    private String cpu;

    public PCFactory(String ram, String hdd, String cpu){
        this.ram=ram;
        this.hdd=hdd;
        this.cpu=cpu;
    }
    @Override
    public Computer createComputer() {
        return new PC(ram,hdd,cpu);
    }

}
public class ServerFactory implements ComputerAbstractFactory {

    private String ram;
    private String hdd;
    private String cpu;

    public ServerFactory(String ram, String hdd, String cpu){
        this.ram=ram;
        this.hdd=hdd;
        this.cpu=cpu;
    }

    @Override
    public Computer createComputer() {
        return new Server(ram,hdd,cpu);
    }

마지막으로 이 서브 클래스들을 생성하기 위해 클라이언트 코드에 접점으로 제공되는 컨슈머 클래스

public class ComputerFactory {

    public static Computer getComputer(ComputerAbstractFactory factory){
        return factory.createComputer();
    }
}

이제 클라이언트는 이 ComputerFacotry 클래스의 getComputer()라는 static 메소드에 앞서 구현한 PCFactory나 ServerFactory 인스턴스를 넣어줌으로써 if-else 없이도 각각 원하는 서브 클래스의 인스턴스를 생성할 수 있게 되었다.

실사용

public class AbstractFactoryTest {

    public static void main(String[] args) {
        Computer pc = ComputerFactory.getComputer(new PCFactory("2 GB","500 GB","2.4 GHz"));
        Computer server = ComputerFactory.getComputer(new ServerFactory("16 GB","1 TB","2.9 GHz"));
        System.out.println("AbstractFactory PC Config::"+pc);
        System.out.println("AbstractFactory Server Config::"+server);
    }

}