도찐개찐

[코드디자인패턴-JAVA] Factory Method Pattern 본문

프로그래밍/코드디자인패턴

[코드디자인패턴-JAVA] Factory Method Pattern

도개진 2024. 2. 29. 09:33

Factory Method Pattern

사용시점:

  1. 클래스가 자신이 생성할 객체의 클래스를 예측 할 수 없는 경우: 라이브러리나 프레임워크의 사용자가 제공하는 확장 클래스의 인스턴스를 생성해야 할 때 등에 사용됩니다.
  2. 클래스가 하위 클래스에 객체 생성 책임을 위임하고 싶은 경우: 객체 생성 로직을 하위 클래스에게 맡겨 다양한 객체 생성 방식을 구현
  3. 코드 중복 최소화: 공통 생성 로직을 부모 클래스에서 구현하고, 세부 로직은 하위 클래스에서 처리하면 코드 중복을 줄일 수 있습니다.

장점:

  1. 유연성 증가: Factory Method 패턴은 객체 생성과 사용을 분리하므로, 다양한 객체 생성 전략을 쉽게 교체하거나 확장 할 수 있습니다.
  2. 결합도 감소: 클라이언트가 특정 클래스와 직접적으로 결합 되지 않으므로, 코드의 유지보수와 확장성이 향상
  3. 확장성 향상: 새로운 객체 타입 추가 및 변경시 기존 코드를 수정하지 않고 새로운 팩토리 클래스만 추가하거나 변경하면 됩니다.

단점:

  1. 코드 복잡성 증가 다양한 팩토리와 제품 클래스가 필요하므로, 코드의 복잡성이 증가 할 수 있습니다.
  2. 디버깅 어려움: 객체 생성 로직이 분산되어 있을 경우, 디버깅이 어려워질 수 있습니다.

결론:

객체 생성 로직을 추상화하여 유연하고 확장 가능한 코드 작성할 수 있게 합니다. 하지만 복잡한 시스템에서는 코드의 복잡성이 증가 할 수 있으므로, 설계 시 주의가 필요합니다.

1. Referance 객체 생성

public abstract class Car {
    public abstract void drive();
}

public abstract class Truck {
    public abstract void drive();
}

public class Sedan extends Car {
    @Override
    public void drive() {
        System.out.println("Driving a Sedan");
    }
}

public class Suv extends Car {
    @Override
    public void drive() {
        System.out.println("Driving a Suv");
    }
}

public class FordSedan extends Sedan {
    @Override
    public void drive() {
        System.out.println("Driving a Ford Sedan");
    }
}

public class FordTruck extends Truck {
    @Override
    public void drive() {
        System.out.println("Driving a Ford Truck");
    }
}

public class BenzSedan extends Sedan {
    @Override
    public void drive() {
        System.out.println("Driving a Benz Sedan");
    }
}

public class BenzTruck extends Truck {
    @Override
    public void drive() {
        System.out.println("Driving a Benz Truck");
    }
}

2. Simple Factory 패턴

  • 장점:
    • 중앙 집중화: 객체 생성 로직이 하나의 메서드 또는 클래스 내에 집중 되므로, 생성 로직 변경이 필요할 때 한곳에서만 수정하면 됩니다.
    • 재사용성: 클라이언트 코드에서 객체 생성 로직을 반복적으로 작성할 필요가 없으므로, 코드 중복을 줄일 수 있습니다.
    • 간소화: 클라이언트는 팩토리 클래스에 요청만 하면 되므로, 객체 생성과 관련 된 복잡한 로직을 알필요가 없습니다.
  • 단점:
    • 개방/폐쇄 원칙(OCP) 위반: 새로운 객체 타입을 추가하거나 변경할 때마다 팩토리 클래스를 수정해야 하므로 OCP를 위반할 수 있습니다.
    • 낮은 응집도: 팩토리 클래스가 너무 많은 종류의 객체 생성 로직을 담당하게 되면, 관리가 어려워 질 수 있으므로 응집도가 낮아 집니다.
    • 유연성 제한: Simple Factory는 상대적으로 단순하므로, 복잡한 객체 생성 요구사항을 충족시키는데 제한이 있을 수 있습니다.
public class SimpleCarFactory {
    public Car createCar(String type) {
        Car car = null;
        if (type.equals("Sedan")) car = new Sedan();
        if (type.equals("Suv")) car = new Suv();
        if (type.equals("FordSedan")) car = new FordSedan();
        if (type.equals("BenzSedan")) car = new BenzSedan();

        return car;
    }
}

public class SimpleMain {
    public static void main(String[] args) {
        SimpleCarFactory simpleCarFactory = new SimpleCarFactory();
        Car car = simpleCarFactory.createCar("FordSedan");
        car.drive();
    }
}

SimpleMain.main(null);
Driving a Ford Sedan

2. Factory Method 패턴

  • 장점:
    • 확장성: 새로운 클래스를 추가 할 때 기존 코드를 변경 불필요
    • 낮은 결합도: 클라이언트와 구체 클래스간의 직접적인 의존성이 제거
    • 유연성: 제품 클래스가 변경 되더라도 클라이언트 코드에 영향을 미치지 않음
  • 단점:
    • 복잡성 증가: 여러 팩토리 클래스가 필요할 수 있으며, 코드를 이해하고 유지하기 어려울 수 있습니다.
    • 디버깅 난이도: 객체 생성 로직의 분산 되어 있는 경우 디버깅이 어려울 수 있습니다.
public abstract class FactoryCarFactory {
    public abstract Car createCar();
}

public class SUVFactory extends FactoryCarFactory {
    @Override
    public Car createCar() {
        return new Suv();
    }
}

public class SedanFactory extends FactoryCarFactory {
    @Override
    public Car createCar() {
        return new Sedan();
    }
}

public class FactoryMain {
    public static void main(String[] args) {
        FactoryCarFactory carFactory = new SUVFactory();
        Car car = carFactory.createCar();
        car.drive();
    }
}

FactoryMain.main(null);
Driving a Suv

3. Abstract Factory

  • 장점:
    1. 제품 일관성: 관련 된 제품들이 함께 사용 되도록 보장하므로 일관성을 유지합니다.
    2. 확장성: 새로운 제품군을 쉽게 추가 할 수 있으며, 기존 클라이언트 코드를 수정 할 필요가 없습니다.
    3. 낮은 결합도: 구체적인 제품 클래스와 클라이언트 사이의 의존성을 줄이므로 코드의 유연성이 증가 합니다.
    4. 추상화 수준의 통일: 모든 제품군에 대해 동일한 추상 인터페이스를 제공하므로 클라이언트 코드의 복잡성을 줄일 수 있습니다.
  • 단점:
    1. 유지보수 난이도: 너무 많은 클래스와 인터페이스가 있을 경우, 시스템을 이해하고 유지보수하기 어려워 질 수 있습니다.
    2. 복잡도 증가: 각 제품군별로 별도의 인터페이스와 구현 클래스를 제공해야 하므로, 코드 복잡도가 증가 할 수 있습니다.
    3. 런타임 에러 가능성: 제품 생성이 런타임에 이루어지므로, 잘못 된 팩토리를 사용하면 런타임 에러가 발생 될 수 있습니다.

결론:

  • 다양한 제품군 간의 일관성과 확장성을 제공하는데 매우 유용합니다.
  • 여러 제품군을 가지고 있고 이를 유연하게 관리해야 하는 시스템에서는 특히 효과적 입니다.
  • 다만 이 패턴의 복잡성은 설계와 유지보수를 어렵게 만들 수 있으므로 실제 필요한 경우에만 적절히 사용해야 합니다.
public interface AbstractCarFactory {
    Car createCar();
    Truck createTruck();
}

public class FordFactory implements AbstractCarFactory {
    @Override
    public Car createCar() { return new FordSedan(); }

    @Override
    public Truck createTruck() { return new FordTruck(); }
}

public class BenzFactory implements AbstractCarFactory {
    @Override
    public Car createCar() { return new BenzSedan(); }

    @Override
    public Truck createTruck() { return new BenzTruck(); }
}

public class AbstractFactoryMain {
    public static void main(String[] args) {
        AbstractCarFactory fordAbstract = new FordFactory();
        AbstractCarFactory benzAbstract = new BenzFactory();
        Car fordCar = fordAbstract.createCar();
        Truck benzTruck = benzAbstract.createTruck();
        benzTruck.drive();
        System.out.println("=============================");
        fordCar.drive();
    }
}

AbstractFactoryMain.main(null);
Driving a Benz Truck
=============================
Driving a Ford Sedan
728x90
Comments