추상 팩토리 패턴은 관련있는 여러 인스턴스를 만들어주는 팩토리를 추상화된 형태로 정의한 패턴입니다. 인터페이스로 정의하거나 추상클래스로 하거나 구체적인 팩토리에서 구체적인 인스턴스를 만드는것 까지는 팩토리메서드 패턴과 굉장히 비슷하지만 추상 팩토리 패턴은 초점이 클라이언트에 있습니다. 추상 팩토리패턴의 목적 자체가 팩토리에서 인스턴스를 만들어서 쓰는 코드를 인터페이스 기반으로 코딩을 할 수 있도록 도와주는 패턴입니다.
위의 그림이 약간 복잡해 보이지만 팩토리 메서드 패턴에 비해 클라이언트만 추가되었습니다.
public class SedanFactory extends DefaultCarFactory {
@Override
public Car createCar() {
Car car = new Sedan();
car.setBody(new FBody());
car.setWheel(new SpokeWheel());
return car;
}
}
위의 SedanFactory클래스에서 createCar메서드를 호출할때 body는 FBody로 wheel은 SpokeWheel로 구체적인 클래스타입으로 설정하고 있습니다. 만약에 비슷한 제품군, 예를 들어 Body는 GBody, Wheel은 PinWheel등이 추가 되었을 때 이것들로 바꿔줘야 할 때 createCar에 setBody, setWheel메서드에 값을 바꿔서 설정해야 합니다. 이는 결국 변경에 닫힌코드가 아니게 됩니다. 이 부분이 바뀌지 않으면서 제품군을 늘려나갈 수 있게 추상 팩토리 패턴을 적용해서 구현해보도록 하겠습니다.
추상 팩토리 패턴을 적용해서 아래와 같이 setBody, setWheel에 구체적인 클래스에 의존하지 않도록 변경해보겠습니다.
public class SedanFactory extends DefaultCarFactory {
@Override
public Car createCar() {
Car car = new Sedan();
car.setBody(new FBody());
car.setWheel(new SpokeWheel());
return car;
}
}
가장먼저 FBody와 SpokeWheel의 제품군을 일반화시킨 인터페이스를 먼저 정의합니다.
public interface Body {
}
public interface Wheel {
}
public interface CarPartsFactory {
Body createBody();
Wheel createWheel();
}
각 제품군의 인터페이스와 그 제품군들을 생산하는 추상 팩토리를 만들었으니 추상팩토리타입의 구체적인 팩토리를 생성합니다.
public class SedanPartsFactory implements CarPartsFactory {
@Override
public Body createBody() {
return new FBody();
}
@Override
public Wheel createWheel() {
return new SpokeWheel();
}
}
Sedan을 생성하는 팩토리는 body는 FBody wheel은 SpokeWheel로 생성하도록 하겠습니다. 그러려면 각 제품군에 해당하는 인터페이스를 구현해야합니다.
public class FBody implements Body {
}
public class SpokeWheel implements Wheel{
}
Sedan을 생성하는 팩토리를 구현했으니 사용해 봅시다.
public class SedanFactory extends DefaultCarFactory {
private CarPartsFactory carPartsFactory;
public SedanFactory(CarPartsFactory carPartsFactory) {
this.carPartsFactory = carPartsFactory;
}
@Override
public Car createCar() {
Car car = new Sedan();
car.setBody(carPartsFactory.createBody());
car.setWheel(carPartsFactory.createWheel());
return car;
}
}
CarPartsFactory추상 팩토리를 주입받아서 추상 팩토리를 통해서 Body와 Wheel을 가져오도록 하면 SedanFactory의 코드는 이제 바뀔일이 없습니다. 만약에 이상태에서 프리미엄 제품군을 만든다고 하면 추상 팩토리를 적용하기전에는 setBody, setWheel에 구체적인 클래스를 추가해서 변경해야 했지만 지금은 추상팩토리를 구현하는 새로운 팩토리를 SedanFactory에 주입하기만하면 됩니다. 한번 적용해 보겠습니다.
public class FBodyPremium implements Body {
}
프리미엄 FBody도 Body제품군 입니다.
public class SpokeWheelPremium implements Wheel{
}
프리미엄 SpokeWheel도 Wheel제품군 입니다.
이제 이 Premium제품군을 생산할 팩토리를 생성하겠습니다.
public class SedanPremiumPartsFactory implements CarPartsFactory {
@Override
public Body createBody() {
return new FBodyPremium();
}
@Override
public Wheel createWheel() {
return new SpokeWheelPremium();
}
}
Premium Body와 Wheel을 생산하는 팩토리를 구현하였습니다. 이 팩토리를 SedanFactory에 주입받기만하면 Premium제품을 생산할 수 있습니다.
public class SedanFactory extends DefaultCarFactory {
private CarPartsFactory carPartsFactory;
public SedanFactory(CarPartsFactory carPartsFactory) { // 주입
this.carPartsFactory = carPartsFactory;
}
@Override
public Car createCar() {
Car car = new Sedan();
car.setBody(carPartsFactory.createBody());
car.setWheel(carPartsFactory.createWheel());
return car;
}
}
실제로 SedanFactory에 SedanPremiumPartsFactory인스턴스를 주입해서 생성한 자동차에 어떤 부품이 들어있는지 확인해 보겠습니다.
public class CarInventory {
public static void main(String[] args) {
SedanFactory sedanFactory = new SedanFactory(new SedanPremiumPartsFactory());
Car car = sedanFactory.createCar();
System.out.println(car.getBody().getClass().getSimpleName());
System.out.println(car.getWheel().getClass().getSimpleName());
}
}
실행 결과 생성한 Car의 부품이 Premium부품이라는 것을 확인할 수 있습니다.
FBodyPremium
SpokeWheelPremium
어떤 제품군을 제공하는 팩토리를 SedanFactory에 주입해주느냐에 따라서 결과가 달라지지 SedanFactory자체의 코드가 변경되지는 않습니다. SedanFactory클래스 입장에서는 코드변경이 없고 주입 받는 팩토리가 다를때마다 다른결과가 보여지기 때문에 확장에 대해서 열려있고 변경에는 닫혀있는 객체지향 원칙이 지켜짐을 알 수 있습니다.
둘 다 구체적인 객체 생성 과정을 추상화한 인터페이스를 제공합니다.
팩토리 메소드 패턴은 "팩토리를 구현하는 방법 (inheritance)"에 초점을 둬서 구체적인 객체 생성 과정을 하위 또는 구체적인 클래스로 옮기는 것이 목적입니다.
추상 팩토리 패턴은 "팩토리를 사용하는 방법 (composition)"에 초점을 둬서 관련있는 여러 객체를 구체적인 클래스에 의존하지 않고 만들 수 있게 해주는 것이 목적입니다.