1. Simple Factory 패턴
예를 들어 어떤 객체에 아래와 같이 자동차를 생성하여 출고하는 메서드가 있다고 해보자.
public Car releaseCar(String type) {
Car car;
if ("PBV".equals(type)) {
car = new PBV();
} else if ("EV".equals(type)) {
car = new EV();
} else if ("SDV".equals(type)) {
car = new SDV();
} else {
car = new Normal();
}
// ...
car.prepareComponents();
car.processComponents();
car.assembleComponents();
return car;
}
Java
복사
위와 같은 코드의 문제점은 객체 내부에 구현체가 들어가기 때문에, 개방 폐쇄 원칙과 인터페이스 분리 원칙을 어기게 된다. 코드를 수정할 일이 생기고, 이에 따라 유연한 대처가 점점 어려워질 코드 임이 분명하다.
따라서 두 원칙을 고수하기 위해선 아래와 같은 Factory 클래스를 만들어서 이용하도록 개선해야 한다.
public class SimpleCarFactory {
public Car createCar(String type) {
Car car;
if ("PBV".equals(type)) {
car = new PBV();
} else if ("EV".equals(type)) {
car = new EV();
} else if ("SDV".equals(type)) {
car = new SDV();
} else {
car = new Normal();
}
return car;
}
Java
복사
public class CarStore {
private final SimpleCarFactory factory;
public CarStore(SimpleCarFactory factory) {
this.factory = factory;
}
public Car releaseCar(String type) {
Car car = factory.createCar(type);
// ...
car.prepareComponents();
car.processComponents();
car.assembleComponents();
return car;
}
}
Java
복사
상점 클래스가 구현체를 갖고 있지 않게 되어, 이전 코드보다는 조금 더 유연한 대처가 가능하다. 이름에서 알 수 있듯, 기재된 코드는 Simple Factory 패턴이다. 만일 회사가 성장해서 Factory마다 동일한 종류의 차만 만들게 된다거나, 여러 종류의 차를 옵션 별로 생성한다고 하면, 위 코드는 그리 좋은 유연성을 갖지 못하게 된다. 두 가지 방법을 적용하여, 동일 종류의 차량만 다루는 상점과 옵션 별 차량을 생성할 수 있도록 점진적인 개선을 해보자.
2. Factory Method 패턴
EV, PV, SDV, Normal 등 다양한 종류의 차량을 대응하기 방안으로는 Factory Method를 이용하는 것이다. 이전에 사용했던 상점 클래스를 추상화 하여 차를 생성하는 방법을 직접 구현해서 사용하도록 강제하는 방법이다.
public abstract class CarStore {
// ...
public Car releaseCar(String type) {
Car car = createCar(type);
// ...
car.prepareComponents();
car.processComponents();
car.assembleComponents();
return car;
}
public abstract Car createCar(String type);
}
Java
복사
위 코드의 추상 클래스를 상속하여 새로운 클래스를 만들 때 반드시 createCar라는 메서드를 오버라이드 하여 사용하도록 강제했다.
public EvCarStore extends CarStore {
// ...
@Override
public Car createCar(String type) {
Car car;
if ("IONIQ".equals(type)) {
car = new IONIQ();
} else if ("eGV".equals(type)) {
car = new eGV();
} else if ("EV".equals(type)) {
car = new EV();
}
return car;
}
}
Java
복사
public NormalCarStore extends CarStore {
// ...
@Override
public Car createCar(String type) {
Car car;
if ("SONATA".equals(type)) {
car = new SONATA();
} else if ("GRANDUER".equals(type)) {
car = new GRANDUER();
} else if ("AVANTE".equals(type)) {
car = new AVANTE();
}
return car;
}
}
Java
복사
종류 별 차량을 종류 별 상점에서 생성할 수 있게 하여, 여러 종류의 상점을 단일 객체로 운용할 수 있게 되었다. 위와 같이 하나의 추상 클래스로부터 추상 메서드를 만들고, 추상 클래스를 상속하면서 추상 메서드를 구현을 강제해서 객체를 만드는 것을 Factory Method 패턴이라고 한다.
3. Abstract Factory 패턴
이전 항목에서 종류 별 차량 판매 상점을 두었던 것과 더불어 차량의 세부 옵션을 나누어 만들 수 있도록 기반 작업을 먼저 수행해보자.
public abstract class Car {
private String name;
private Boolean isHighPassEnabled;
private Boolean isMemorySeatEnabled;
private Boolean isSmartCruiseEnabled;
private Boolean isAutoHoldEnabled;
private Boolean isStopAndGoEnabled;
// ...
public abstract void prepareComponents() {
// ...
}
public void processComponents() {
// ...
}
public void assembleComponents() {
// ...
}
}
Java
복사
차량의 옵션은 베이직, 프레스티지, 노블레스로 나뉜다고 했을 때, 해당 옵션을 구성해주는 공통 Factory와 그 구현체를 만들어보자.
public interface CarOptionFactory {
public Boolean setHighPassOption();
public Boolean setMemorySeatOption();
public Boolean setSmartCruiseOption();
public Boolean setAutoHoldOption();
public Boolean setStopAndGoOption();
}
Java
복사
public class BasicCarOptionFactory implements CarOptionFactory {
@Override
public Boolean setHighPassOption() {
return true;
}
@Override
public Boolean setMemorySeatOption() {
return false;
}
@Override
public Boolean setSmartCruiseOption() {
return false;
}
@Override
public Boolean setAutoHoldOption() {
return false;
}
@Override
public Boolean setStopAndGoOption() {
return false;
}
}
Java
복사
public class PrestigeCarOptionFactory implements CarOptionFactory {
@Override
public Boolean setHighPassOption() {
return true;
}
@Override
public Boolean setMemorySeatOption() {
return true;
}
@Override
public Boolean setSmartCruiseOption() {
return false;
}
@Override
public Boolean setAutoHoldOption() {
return false;
}
@Override
public Boolean setStopAndGoOption() {
return true;
}
}
Java
복사
public class NoblessCarOptionFactory implements CarOptionFactory {
@Override
public Boolean setHighPassOption() {
return true;
}
@Override
public Boolean setMemorySeatOption() {
return true;
}
@Override
public Boolean setSmartCruiseOption() {
return true;
}
@Override
public Boolean setAutoHoldOption() {
return true;
}
@Override
public Boolean setStopAndGoOption() {
return true;
}
}
Java
복사
차량과 옵션의 기반 작업을 마쳤다면, 내가 좋아하는 Granduer와 eGV70 구현체를 만들어보자. 두 차는 하이패스, 메모리 시트, 스마트 크루즈를 공통으로 가지고, Granduer는 오토 홀드를, eGV70은 스탑 앤 고를 옵션으로 가질 수 있음을 가정하고 구현했다. (중간에 차량 종류와 차량 옵션 객체를 만들어 보는 것도 좋을 듯 하다.)
public class Granduer extends Car {
private CarOptionFactory factory;
public Granduer(CarOptionFactory factory) {
this.factory = factory;
}
@Override
public abstract void prepareComponents() {
this.isHighPassEnabled = factory.setHighPassOption();
this.isMemorySeatEnabled = factory.setMemorySeatOption();
this.isSmartCruiseEnabled = factory.setSmartCruiseOption();
this.isAutoHoldEnabled = factory.setAutoHoldOption();
// ...
}
}
Java
복사
public class eGV70 extends Car {
private CarOptionFactory factory;
public eGV70(CarOptionFactory factory) {
this.factory = factory;
}
@Override
public abstract void prepareComponents() {
this.isHighPassEnabled = factory.setHighPassOption();
this.isMemorySeatEnabled = factory.setMemorySeatOption();
this.isSmartCruiseEnabled = factory.setSmartCruiseOption();
this.isStopAndGoEnabled = factory.setStopAndGoOption();
// ...
}
}
Java
복사
이제 두 차량은 옵션을 생성할 수 있는 Factory의 주입에 따라 차량이 보유하고 있는 옵션이 달라진다. 이와 같은 Factory를 상점에서 차량을 생성할 때 주입이 이뤄진다고 해보자.
public abstract class CarStore {
// ...
public Car releaseCar(String type, CarOptionFactory factory) {
Car car = createCar(type, factory);
// ...
car.prepareComponents();
car.processComponents();
car.assembleComponents();
return car;
}
public abstract Car createCar(String type);
}
Java
복사
public NormalCarStore extends CarStore {
// ...
@Override
public Car createCar(String type, CarOptionFactory factory) {
Car car;
if ("SONATA".equals(type)) {
car = new SONATA(factory);
} else if ("GRANDUER".equals(type)) {
car = new GRANDUER(factory);
} else if ("AVANTE".equals(type)) {
car = new AVANTE(factory);
}
return car;
}
}
Java
복사
public EvCarStore extends CarStore {
// ...
@Override
public Car createCar(String type, CarOptionFactory factory) {
Car car;
if ("IONIQ".equals(type)) {
car = new IONIQ(factory);
} else if ("eGV".equals(type)) {
car = new eGV(factory);
} else if ("EV".equals(type)) {
car = new EV(factory);
}
return car;
}
}
Java
복사
차량을 판매할 수 있는 상점들은 종류 별로 분류 되었고 차 종류에 따라 유연하게 대처하면서, 차량을 만들 때 옵션 별로 대처할 수 있도록 개선되었다. 처음에 만들어진 설계보다는 보다 더 유연한 특성을 갖고 있는 것을 볼 수 있다. 이와 같이 인터페이스를 구성하여, 이를 이용하여 객체를 만드는 것을 Abstract Factory 패턴이라고 한다.
4. 이용 방법
1.
차량을 판매할 수 있는 상점을 만든다. 이 때 상점 역시 업 캐스팅된 슈퍼 타입으로 지칭하여 운용하는 것이 가능하다.
2.
차를 상점으로부터 출고하는 releaseCar를 호출하여 차량을 받도록 만든다. 이 때 생성하려는 차량의 종류와 옵션을 함께 입력 받는다.
3.
출고하는 과정에서 차량을 생성하게 된다. 차량은 옵션 생성을 위한 Factory를 통해 만들어지게 된다.
4.
차량이 만들어지는 과정에서는 인자로 받은 Factory로부터 옵션을 위한 필드가 설정된다.
5.
출고된 차량은 슈퍼 타입으로 지칭하여 이용되고, 클래스 내에서 유연한 코드를 작성할 수 있게 된다.