디자인패턴

[헤드퍼스트 디자인패턴] 팩토리 패턴

hongyb 2024. 8. 12. 13:33

팩토리 패턴과 새로운 디자인 원칙을 알아보겠다.

팩토리 패턴

객체지향 원칙

바뀌는 부분은 캡슐화한다.

상속보다는 구성을 활용한다.

구현보다는 인터페이스에 맞춰서 프로그래밍한다.

상호작용하는 객체 사이에서는 가능하면 느슨한 결합을 사용해야 한다.

클래스는 확장에는 열려있어야 하지만 변경에는 닫혀 있어야 한다.

추상화된 것에 의존하게 만들고 구상 클래스에 의존하지 않게 만든다.  

 

팩토리 메서드 패턴 

객체를 생성할 때 필요한 인터페이스를 만든다. 어떤 클래스의 인터페이스를 만들지 서브 클래스에서 결정한다. 팩토리 메서드를 사용하면 인터페이스 만드는 일을 서브클래스에 맡길 수 있다.

 

추상 팩토리패턴

구상 클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생성하는 인터페이스를 제공한다. 구상클래스는 서브 클래스에서 만든다.          

 

구상 클래스

구상 클래스란 모든 메서드가 구현된 클래스로, 객체를 생성할 수 있는 클래스이다. 이를 통해 특정 기능을 직접 사용할 수 있다. Java에서 'new' 연산자가 눈에 띈다면 '구상'이라는 용어를 떠올리면 된다. new를 사용하면 구상클래스의 인스턴스가 만들어진다.

 

다음과 같이 인터페이스에 맞춰 코딩했다고 하더라도 구상클래스의 인스턴스를 만들어야 한다.

InterfaceProduct = new ConcreteProduct()

 

'new'에 어떤 문제가 있는 걸까? 구상 클래스를 많이 사용하면 새로운 구상 클래스가 추가될 때마다 코드를 고쳐야 하므로 변경에 닫혀있는 코드가 된다. 

 

이 문제를 해결하기 위해 팩토리 패턴을 활용하면 된다. 팩토리 패턴은 구상 클래스를 생성하는 역할을 한다. 구상 클래스는 실제 기능을 제공하는 클래스이며, 팩토리 패턴을 통해 이러한 클래스의 인스턴스를 생성할 수 있다. 이를 통해 객체 생성 로직이 클라이언트 코드와 분리되어, 객체 생성의 변경이 필요할 때 클라이언트 코드를 수정하지 않고 팩토리 클래스만 수정하면 된다.

 

총 3가지 종류의 팩토리 패턴을 알아보도록 하겠다. 

 

간단한 팩토리

간단한 팩토리 (Simple Factory) 패턴은 객체 생성 로직을 하나의 팩토리 클래스에 캡슐화하는 디자인 패턴이다. 따라서 클라이언트가 객체 생성 방식을 알 필요 없이 객체를 생성할 수 있다.

 

클래스 구조는 다음과 같다. 

 

위 구조에 맞춰 코드로 구현해 보도록 하겠다. 아래는 자바로 단순 팩토리 패턴을 구현한 예제 코드이다.

 

Product 인터페이스와 구현 클래스를 생성하겠다. 

//추상 Product 클래스
abstract class Product {
    public abstract void productMethod();
}
// 구상 클래스
class ConcreteProduct1 extends Product {
    @Override
    public void productMethod() {
        System.out.println("product 1");
    }
}
// 구상 클래스
class ConcreteProduct2 extends Product {
    @Override
    public void productMethod() {
        System.out.println("product 2");
    }
}

 

다음으로 Factory와 Client 코드를 생성하겠다.

// 간단한 팩토리 클래스
class ProductFactory {
    public static Product createProduct(String type) {
        switch (type.toLowerCase()) {
            case "1":
                return new ConcreteProduct1();
            case "2":
                return new ConcreteProduct2();
            default:
                throw new IllegalArgumentException("Unknown type: " + type);
        }
    }
}

 

// 클라이언트 코드
public class Client {
    public static void main(String[] args) {
        // 팩토리 메서드를 통해 객체 생성
        Product a = ProductFactory.createProduct("1");
        Product b = ProductFactory.createProduct("2");
        
        // 메서드 호출
        a.productMethod();  // 출력: product 1
        b.productMethod();  // 출력: product 2
    }
}

 

객체 생성을 간단한 팩토리 형태로 캡슐화하면 무슨 장점이 있는 걸까? 구현을 변경할 때 여기저기 클래스를 고칠 필요 없이 팩토리 클래스 하나만 고치면 된다는 장점이 있다. 간단한 팩토리를 정적메서드로 정의하는 기법도 존재한다. 이를 static factory라고 정의한다. 정적 메서드를 쓰면 서브 클래스를 만들어 객체 생성 메서드의 행동을 변경할 수 없다는 단 점이 있다.

 

하지만 여전히 새로운 구상 클래스를 추가하려면, 팩토리 클래스의 코드를 수정해야 한다. 이는 개방-폐쇄 원칙(Open/Closed Principle)에 위배된다. 즉, 코드는 확장에는 열려 있고 수정에는 닫혀 있어야 하지만, 단순 팩토리 패턴에서는 새로운 타입을 추가할 때마다 팩토리 클래스를 수정해야 한다.

 

팩토리 메서드 패턴

팩토리 메서드 패턴이란 객체를 생성할 때 필요한 인터페이스를 만든다. 어떤 클래스의 인터페이스를 만들지 서브 클래스에서 결정한다. 팩토리 메서드를 사용하면 인터페이스 만드는 일을 서브클래스에 맡길 수 있다.

 

구조는 다음과 같다.  

구조를 자세히 보면 팩토리 메서드용 인터페이스를 제공하는 것을 알 수 있다. 실제 팩토리 메서드를 구현하고 Product를 만드는 일은 서브 클래스(ConcreteFactoryA, ConcreteFactoryB)에서 처리한다는 사실을 알 수 있다. 따라서 팩토리 메서드 패턴에서 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정한다. 생산자 클래스(인터페이스)는 실제 생상될 제품을 전혀 모르는 사태로 만들어진다. 

 

코드는 다음과 같다.

 

우선 Product 인터페이스와 구현 클래스를 만들겠다. 

package factory.methodV2;

public abstract class Product {
    public abstract void setting();
}
package factory.methodV2;

public class ConcreteProductA extends Product {
    @Override
    public void setting() {
        System.out.println("product a");
    }
}
package factory.methodV2;

public class ConcreteProductB extends Product {
    @Override
    public void setting() {
        System.out.println("product b");
    }
}

 

다음으로 팩토리 인터페이스와 구현 클래스를 만들겠다.

package factory.methodV2;

public abstract class AbstractFactory {
    public Product anOperation() {
        Product product = create();
        product.setting();

        return product;
    };

    protected abstract Product create();
}
package factory.methodV2;

public class ConcreteFactoryA extends AbstractFactory {
    @Override
    protected Product create() {
        return new ConcreteProductA();
    }
}
package factory.methodV2;

public class ConcreteFactoryB extends AbstractFactory {
    @Override
    protected Product create() {
        return new ConcreteProductB();
    }
}

 

팩토리 메서드 패턴에서 사용된 객체지향 원칙은 의존성 뒤집기 원칙(Dependency Inversion Principle)이다. 이 원칙은 '추상화된 것에 의존하게 만들고, 구상 클래스에 의존하지 않게 만든다.'로 정의할 수 있다.

 

AbstractFactory에서 Product에 의존하고 있지 ConcreteProductA나 ConcreteProductB와 같은 구상 클래스에 의존하지 않고 있다. Product가 추상 클래스이기 때문에 AbstractFactory는 추상화된 것에 의존하고 있다.

   

추상 팩토리 패턴

추상 팩토리 패턴은 구상 클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생성하는 인터페이스를 제공한다. 구상클래스는 서브 클래스에서 만든다.

 

클래스 다이어그램은 다음과 같다.

추상 팩토리 패턴을 사용하면 클라이언트에서 추상 인터페이스로 제품군을 공급받을 수 있다. 어떤 제품이 생상 되는지 알 필요가 없기 때문에 클라이언트와 팩토리에서 생상 되는 제품을 분리할 수 있다. 

 

코드는 다음과 같다.

 

AbstrationProductA 클래스와 구현 클래스는 다음과 같다.

public abstract class AbstractProductA {
    public abstract void abstractProductAMethod();
}

public class ConcreteProductA1 extends AbstractProductA {
    @Override
    public void abstractProductAMethod() {
        System.out.println("product a - 1");
    }
}

public class ConcreteProductA2 extends AbstractProductA {
    @Override
    public void abstractProductAMethod() {
        System.out.println("concrete a - 2");

    }
}

     

다음은 AbstrationProductB 클래스와 구현 클래스이다.

public abstract class AbstractProductB {
    public abstract void abstractProductBMethod();
}

public class ConcreteProductB1 extends AbstractProductB {
    @Override
    public void abstractProductBMethod() {
        System.out.println("product b - 1");
    }
}

public class ConcreteProductB2 extends AbstractProductB {
    @Override
    public void abstractProductBMethod() {
        System.out.println("product b - 2");
    }
}

 

팩토리 인터페이스를 선언하도록 하겠다.

public abstract class AbstractFactory {
    public abstract AbstractProductA createProductA();
    public abstract AbstractProductB createProductB();
}

 

다음은 구현 클래스이다.

public class ConcreteFactory1 extends AbstractFactory{
    @Override
    public AbstractProductA createProductA() {
        return new ConcreteProductA1();
    }

    @Override
    public AbstractProductB createProductB() {
        return new ConcreteProductB1();
    }
}

public class ConcreteFactory2 extends AbstractFactory {
    @Override
    public AbstractProductA createProductA() {
        return new ConcreteProductA2();
    }

    @Override
    public AbstractProductB createProductB() {
        return new ConcreteProductB2();
    }
}

 

마지막으로 팩토리를 사용하는 클라이언트 코드이다.

public class Client {
    public static void main(String[] args) {
        Client client = new Client(new ConcreteFactory1());
        client.abstractProductA.abstractProductAMethod();
        client.abstractProductB.abstractProductBMethod();

        System.out.println("-----------------------------");
        client = new Client(new ConcreteFactory2());
        client.abstractProductA.abstractProductAMethod();
        client.abstractProductB.abstractProductBMethod();
    }


    public AbstractProductA abstractProductA;
    public AbstractProductB abstractProductB;

    public Client(AbstractFactory abstractFactory) {
        abstractProductA = abstractFactory.createProductA();
        abstractProductB = abstractFactory.createProductB();
    }
}

 

추상 팩토리 패턴과 팩토리 머서드 패턴 비교 분

추상 팩토리 패턴(Abstract Factory Pattern)팩토리 메서드 패턴(Factory Method Pattern)은 둘 다 객체 생성과 관련된 디자인 패턴이지만, 서로 다른 상황에서 사용되며 구조와 목적에 차이가 있다.

 

팩토리 메서드 패턴은 객체 생성 코드를 서브클래스에 위임하여 객체 생성의 책임을 분산하는 디자인 패턴이다. 팩토리 메서드를 정의한 인터페이스를 통해 객체를 생성하고, 실제 객체 생성은 서브클래스에서 결정된다. 새로운 타입의 객체를 추가할 때 기존 코드를 수정할 필요 없이 새로운 서브클래스만 추가하면 되고, 단일 책임 원칙 준수하여 객체 생성 로직이 별도의 팩토리 클래스에 캡슐화된다.

 

추상 팩토리 패턴은 관련된 객체군을 생성하기 위한 인터페이스를 제공하는 패턴이다. 구체적인 클래스는 서브클래스에서 결정되며, 클라이언트는 구체적인 클래스의 이름을 지정하지 않고도 객체들을 생성할 수 있다. 

 

특징 팩토리 메서드 패턴 추상 팩토리 패턴
주요 목적 서브클래스에서 객체 생성 책임 분산 관련 객체군 생성
구조 하나의 팩토리 메서드 여러 팩토리 메서드를 가진 인터페이스
확장성 새로운 타입 추가 시 서브 클래스 추가  새로운 객체군 추가 시 팩토리 클래스 추가 및 인터페이스 수정
클래스 각 제품마다 팩토리 클래스 각제품군마다 팩토리 클래스 
일관성 유지 단일 객체 생성 객체군 간의 일관성 유

 

 

 

즉 팩토리 메서드 패턴은 단일 객체를 생성하는 경우에, 추상 팩토리 패턴은 관련된 객체군을 생성하는 경우에 적합하다.