본문 바로가기
컴퓨터공학

[디자인 패턴] 팩토리 메소드 패턴: 유연한 객체 생성을 위한 패턴

by oobw 2023. 12. 4.

이 글에서는 팩토리 메소드 패턴의 기본 원리, 장점, 단점, 그리고 실제 사용 예시를 살펴보겠습니다. 이 패턴을 이해하고 적절히 활용하는 것은 소프트웨어 개발에서 유연성과 확장성을 크게 향상시킬 수 있습니다.

1. 팩토리 메소드 (Factory Method) 패턴의 정의 및 기본 원리

팩토리 메소드 패턴은 객체 생성을 위한 인터페이스를 제공하면서, 실제 객체의 생성은 서브클래스 또는 구현 클래스에서 수행하는 구조를 가진 디자인 패턴입니다. 이 패턴은 객체를 생성하는 코드와 사용하는 코드를 분리함으로써, 높은 수준의 모듈성과 유연성을 제공합니다.

기본 원리

추상화된 생성 인터페이스: 팩토리 메소드 패턴의 핵심은 객체를 생성하는 메소드(즉, 팩토리 메소드)를 추상화하는 것입니다. 이 추상화된 메소드는 다양한 타입의 객체를 생성할 수 있는 능력을 가지고 있으며, 구체적인 생성 로직은 클라이언트 코드로부터 분리됩니다.

  • 구체적인 객체 생성: 클라이언트는 팩토리 메소드를 통해 객체를 요청하며, 이 메소드는 요청에 맞는 구체적인 객체를 생성하여 반환합니다. 클라이언트는 생성되는 객체의 구체적인 클래스에 대해 알 필요가 없으며, 객체가 제공하는 인터페이스에만 의존합니다.
  • 단일 클래스 구현: 단일 팩토리 클래스에서 구현되는 팩토리 메소드 패턴은 모든 객체 생성 요청을 하나의 팩토리 클래스가 처리합니다. 이 클래스는 다양한 타입의 객체를 생성할 수 있는 로직을 내부적으로 포함하고 있으며, 종종 매개변수를 통해 생성할 객체의 타입을 지정받습니다.

패턴의 핵심 특징

  • 유연성: 새로운 객체 타입이 필요할 때, 기존 코드를 변경하지 않고도 새로운 클래스를 추가하기만 하면 됩니다. 이는 시스템의 확장성과 유연성을 크게 향상시킵니다.
  • 낮은 결합도: 객체 생성과 사용 사이의 의존성이 줄어듭니다. 클라이언트 코드는 생성되는 객체의 구체적인 클래스에 대해 알 필요가 없으며, 이로 인해 시스템의 결합도가 낮아집니다.
  • 재사용성 및 유지보수 용이성: 객체 생성 관련 코드를 중앙화하면 코드의 재사용성이 높아지고, 유지보수가 용이해집니다.

팩토리 메소드 패턴은 특히 객체 생성 로직이 복잡하거나, 시스템이 다양한 타입의 객체를 생성해야 할 때 유용하게 사용될 수 있습니다. 이러한 패턴을 적용함으로써, 개발자는 보다 유연하고 확장 가능한 소프트웨어를 설계할 수 있습니다.

2. 팩토리 메소드 (Factory Method) 패턴의 기본 구현 설명

팩토리 메소드 패턴을 이해하는 데 도움이 되는 구체적인 예제로서, 다양한 도형을 생성하는 ShapeFactory 클래스를 사용하는 방법을 살펴보겠습니다. 이 예제는 팩토리 메소드 패턴의 특징을 잘 드러내며, 각 부분의 역할을 명확히 이해하는 데 도움이 됩니다.

// Product 인터페이스
public interface Shape {
    void draw();
}

// ConcreteProduct 클래스들
public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a circle.");
    }
}

public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a rectangle.");
    }
}

public class Triangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a triangle.");
    }
}

// Factory 클래스
public class ShapeFactory {

    // Factory 메소드
    public Shape getShape(String shapeType) {
        if (shapeType == null) {
            return null;
        }
        if (shapeType.equalsIgnoreCase("CIRCLE")) {
            return new Circle();
        } else if (shapeType.equalsIgnoreCase("RECTANGLE")) {
            return new Rectangle();
        } else if (shapeType.equalsIgnoreCase("TRIANGLE")) {
            return new Triangle();
        }
        return null;
    }
}

// 클라이언트 코드
public class Client {
    public static void main(String[] args) {
        ShapeFactory shapeFactory = new ShapeFactory();

        Shape shape1 = shapeFactory.getShape("CIRCLE");
        shape1.draw();

        Shape shape2 = shapeFactory.getShape("RECTANGLE");
        shape2.draw();

        Shape shape3 = shapeFactory.getShape("TRIANGLE");
        shape3.draw();
    }
}
  • Shape 인터페이스: 이 인터페이스는 draw 메소드를 정의하며, 모든 도형 객체가 이를 구현해야 합니다.
  • ConcreteProduct 클래스들: Circle, Rectangle, TriangleShape 인터페이스의 구현체입니다. 각 클래스는 draw 메소드를 자신의 도형에 맞게 구현합니다.
  • ShapeFactory 클래스: 이 클래스는 팩토리 메소드 getShape를 포함하며, 이 메소드는 문자열 매개변수를 통해 요청받은 도형 타입에 해당하는 Shape 객체를 생성합니다.
  • 클라이언트 코드: 클라이언트는 ShapeFactorygetShape 메소드를 사용하여 필요한 도형 객체를 생성합니다. 이 방식은 클라이언트가 구체적인 도형 클래스를 몰라도 되며, Shape 인터페이스를 통해 도형을 사용할 수 있게 합니다.

이 예제는 팩토리 메소드 패턴이 어떻게 유연한 객체 생성을 제공하는지 보여줍니다. 클라이언트 코드는 생성하려는 객체의 구체적인 클래스를 몰라도 되며, 새로운 도형 타입이 필요할 때 ShapeFactory 클래스에만 변경을 추가하면 됩니다. 이는 시스템의 확장성을 증진시키고, 결합도를 낮추며, 유지보수를 용이하게 합니다.

3. 팩토리 메소드 패턴의 장점

팩토리 메소드 패턴은 객체 생성에 관한 디자인 패턴 중에서 널리 사용되며, 여러 중요한 장점을 제공합니다. 이러한 장점들은 소프트웨어 설계와 개발 과정에서 효율성과 유연성을 증대시키는 데 크게 기여합니다.

객체 생성과 클래스 구현의 분리

팩토리 메소드 패턴은 객체 생성 로직을 클라이언트 코드로부터 분리합니다. 이를 통해 클라이언트 코드는 생성되는 객체의 구체적인 타입을 몰라도 됩니다. 객체 생성의 책임이 전문화된 팩토리 클래스로 이동함으로써, 클라이언트 코드와 객체 생성 코드 간의 의존성이 줄어들고, 코드의 유지보수가 용이해집니다.

코드의 유연성과 확장성 향상

팩토리 메소드 패턴을 사용하면 새로운 타입의 객체를 추가하는 경우에도 기존 코드를 변경할 필요가 없습니다. 새로운 ConcreteProduct 클래스와 해당 클래스를 생성하는 ConcreteCreator 클래스만 추가하면 됩니다. 이는 소프트웨어의 확장성을 증진시키며, 개방-폐쇄 원칙(Open-Closed Principle)을 준수하는 설계를 가능하게 합니다.

테스트와 유지보수의 용이성

객체 생성이 분리되어 있기 때문에, 테스트 중에 필요한 특정 객체 생성 로직을 쉽게 대체하거나 모의할 수 있습니다. 이는 단위 테스트의 용이성을 높이며, 전체 시스템의 안정성을 강화합니다.

단일 책임 원칙(Single Responsibility Principle)의 준수

팩토리 메소드 패턴은 객체 생성에 대한 책임을 담당하는 팩토리 클래스를 별도로 두어, 단일 책임 원칙을 준수합니다. 각 클래스는 자신의 핵심 기능에만 집중하게 되며, 이는 시스템의 복잡성을 줄이고, 코드의 가독성을 향상시킵니다.

클라이언트 코드와 생성되는 객체 간의 결합도 감소

팩토리 메소드 패턴은 생성되는 객체의 구체적인 타입을 클라이언트 코드에서 분리함으로써, 결합도를 낮춥니다. 클라이언트는 인터페이스나 추상 클래스를 통해서만 객체와 상호 작용하므로, 구체적인 객체 타입이 변경되어도 클라이언트 코드는 영향을 받지 않습니다.

이러한 장점들 덕분에 팩토리 메소드 패턴은 다양한 소프트웨어 개발 상황에서 유용하게 활용됩니다. 특히, 시스템의 확장성과 유연성이 중요한 대규모 애플리케이션, 라이브러리 개발, 프레임워크 설계 등에서 그 가치가 높습니다.

4. 팩토리 메소드 패턴의 단점

팩토리 메소드 패턴은 많은 장점을 가지고 있지만, 몇 가지 단점 또한 고려해야 합니다. 이러한 단점들을 인지하는 것은 패턴을 적용할 때 더욱 신중하고 효과적인 결정을 내리는 데 도움이 됩니다.

복잡성 증가

팩토리 메소드 패턴은 단순한 객체 생성보다 복잡한 구조를 가집니다. 각각의 도형(Shpe)에 대해 별도의 생성자(ConcreteCreator)를 구현해야 하기 때문에 클래스의 개수가 증가하고, 시스템의 전체적인 복잡성이 늘어날 수 있습니다. 이는 특히 많은 종류의 객체를 생성해야 하는 경우에 더욱 두드러집니다.

코드 유지 관리의 어려움

팩토리 메소드 패턴을 사용하면, 새로운 ConcreteProduct 타입이 추가될 때마다 ConcreteCreator 클래스도 추가해야 합니다. 이로 인해 유지 관리해야 할 코드가 증가하고, 시스템의 복잡성이 증가합니다. 또한, 팩토리 메소드와 관련된 인터페이스나 추상 클래스의 변경이 필요한 경우, 이러한 변경이 여러 클래스에 영향을 미칠 수 있습니다.

확장성에 대한 제약

팩토리 메소드 패턴은 객체 생성을 추상화하여 확장성을 제공하지만, 이 패턴 자체가 어떤 방식으로 확장될 수 있는지에 대한 제약이 있습니다. 예를 들어, Product 인터페이스에 새로운 메소드를 추가하면, 모든 ConcreteProduct 클래스들이 이 변경을 반영해야 합니다. 이는 대규모 시스템에서 코드의 수정과 테스트에 상당한 노력이 필요하게 만듭니다.

과도한 추상화

일부 경우에는 팩토리 메소드 패턴이 과도한 추상화를 초래할 수 있습니다. 간단한 객체 생성의 경우, 팩토리 메소드 패턴을 적용하는 것이 오히려 과한 설계가 될 수 있으며, 이는 코드를 이해하고 유지하기 어렵게 만들 수 있습니다.

테스트의 복잡성

팩토리 메소드 패턴을 사용하는 경우, 객체 생성 로직이 추상화되고 분리되어 있기 때문에, 이러한 생성 로직을 테스트하는 것이 복잡해질 수 있습니다. 특히, 다양한 ConcreteCreator 클래스들이 제공하는 객체들을 모두 테스트해야 하는 경우, 테스트 작성과 유지 관리가 어려워질 수 있습니다.

 

팩토리 메소드 패턴의 이러한 단점들은 특히 대규모 시스템이나 간단한 객체 생성이 필요한 경우에 더욱 고려되어야 합니다. 따라서, 이 패턴을 적용하기 전에는 프로젝트의 요구 사항과 복잡성, 유지 관리의 용이성 등을 면밀히 검토해야 합니다.