엘리베이터만 생각하면 여러 제조 업체의 부품을 사용하더라도 같은 동작을 지원하게
하는 것이 바람직하다. LG의 모터와 현대의 모터는 구체적인 제어 방식은 다르지만
엘리베이터 입장에서는 모터를 구동해 엘리베이터를 이동시킨다는 면에서는 동일하다.
Motor
의 핵심 기능은 move
메서드의 기본 기능은 다음과 같다.
public void move(Direction direction) {
// 1) 이미 이동 중이면 무시
// 2) 문이 열려 있다면 문을 닫음
// 3) 모터를 구동해서 이동시킨다.
// 4) 모터의 상태를 이동 중으로 설정한다.
}
위 기능에서 3번 부분이 부품에 따라 달라질 수 있다. 일반적인 흐름에서 동일하지만
특정 부분만 다른 동작을 하는 경우에는 일반적인 기능을 상위 클래스에 템플릿
메서드로서 설계할 수가 있다. 이는 Door
의 경우도 마찬가지다.
Door
package abstract_factory;
public abstract class Door {
private DoorStatus doorStatus;
public Door() {
doorStatus = DoorStatus.CLOSED;
}
public DoorStatus getDoorStatus() {
return doorStatus;
}
public void close() { // template method
if (doorStatus == DoorStatus.CLOSED) {
return;
}
doClose();
doorStatus = DoorStatus.CLOSED;
}
protected abstract void doClose();
public void open() {
if (doorStatus == DoorStatus.OPEN) {
return;
}
doOpen();
doorStatus = DoorStatus.OPEN;
}
protected abstract void doOpen();
}
LGDoor
package abstract_factory;
public class LGDoor extends Door {
@Override
protected void doClose() {
System.out.println("close LG Door");
}
@Override
protected void doOpen() {
System.out.println("open LG Door");
}
}
HyundaiDoor
package abstract_factory;
public class HyundaiDoor extends Door {
@Override
protected void doClose() {
System.out.println("close Hyundai Door");
}
@Override
protected void doOpen() {
System.out.println("open Hyundai Door");
}
}
또한 엘리베이터 입장에서는 특정 제조 업체의 모터와 문을 제어하는 클래스가 필요하다.
이 경우에도 팩토리 메서드 패턴을 사용해 MotorFactory
클래스를 설계할 수 있다.
VendorID
package abstract_factory;
public enum VendorID {
LG, HYUNDAI
}
MotorFactory
package abstract_factory;
public class MotorFactory {
public static Motor createMotor(VendorID vendorID) {
Motor motor = null;
switch (vendorID) {
case LG:
motor = new LGMotor();
break;
case HYUNDAI:
motor = new HyundaiMotor();
break;
}
return motor;
}
}
마찬가지 방식으로 DoorFactory
클래스를 만들 수 있다.
DoorFactory
package abstract_factory;
public class DoorFactory {
public static Door createDoor(VendorID vendorID) {
Door door = null;
switch (vendorID) {
case LG:
door = new LGDoor();
break;
case HYUNDAI:
door = new HyundaiDoor();
break;
}
return door;
}
}
이제 Client
코드를 구성해보자.
Client
package abstract_factory;
public class Client {
public static void main(String[] args) {
Door lgDoor = DoorFactory.createDoor(VendorID.LG);
Motor lgMotor = MotorFactory.createMotor(VendorID.LG);
lgMotor.setDoor(door);
lgDoor.open();
lgMotor.move(Direction.UP);
}
}
Client
의 코드를 수정하여 다른 업체 부품을 사용하게할 수 있다.
하지만 이런 구조라면 부품에 따라 각 Factory
클래스를 구현하고 이들의
Factory
객체를 각각 생성해야 한다.
새로 Samsung
의 부품을 지원하다면 기존의 Factory
코드를 수정해야 한다.
package abstract_factory;
public class DoorFactory {
public static Door createDoor(VendorID vendorID) {
Door door = null;
switch (vendorID) {
case LG:
door = new LGDoor();
break;
case HYUNDAI:
door = new HyundaiDoor();
break;
case SAMSUNG:
door = new SamsungDoor();
break;
}
return door;
}
}
결론적으로 기존의 팩토리 메서드 패턴을 이용한 객체 생성은 관련 있는 여러 개의
객체를 일관성 있는 방식으로 생성하는 경우에 많은 코드 변경이 발생하게 된다는
것이다.
여러 종류의 객체를 생성할 때 객체들 사이의 관련성이 있는 경우(같은 제조업체)
관련 객체들을 일관성 있게 생성하는 Factory
클래스를 사용하는 것이 편리할
수가 있다.
ElevatorFactory
package abstract_factory;
public abstract class ElevatorFactory {
public abstract Motor createMotor();
public abstract Door createDoor();
}
LGElevatorFactory
package abstract_factory;
public class LGElevatorFactory extends ElevatorFactory {
@Override
public Motor createMotor() {
return new LGMotor();
}
@Override
public Door createDoor() {
return new LGDoor();
}
}
MotorFactory
나 DoorFactory
클래스 등과 같이 부품별 Factory
클래스를
활용하는 경우에는 삼성 부품의 객체를 생성하도록 각 Factory
클래스를 수정해야
했다. 그러나 이제는 부품이 아니라 제조 업체별로 Factory
클래스를 설계했으므로
삼성 부품의 객체를 생성하는 SamsungFactory
클래스만 새롭게 만들면 된다.
(OCP 준수)
SamsungFactory
package abstract_factory;
public class SamsungFactory extends ElevatorFactory{
@Override
public Motor createMotor() {
return new SamsungMotor();
}
@Override
public Door createDoor() {
return new SamsungDoor();
}
}
제조 업체별로 Factory
클래스를 생성하는 부분은 팩토리 메서드 패턴을 적용해
설계한 것이다. 즉, 구체적인 Factory
클래스를 생성하는 팩토리 메서드를
사용함으로써 제조 업체별 Factory
객체를 생성하는 방식을 캡슐화할 수 있다.
제조 업체별로 Factory
객체가 1개만 있으면 되기 때문에 싱글턴 패턴을 적용할 수
있다.
ElevatorFactoryFactory
의 getFactory
가 팩토리 메서드 역할을 한다.
추상 팩토리 패턴은 관련성 있는 여러 종류의 객체를 일관된 방식으로 생성하는 경우에
유용하다. 앞선 경우에 부품별로 Factory
메서드를 정의하는 대신 관련 객체들을
일관성 있게 생성할 수 있도록 Factory
메서드를 정의하는 것이 효과적이다.
위 구조를 통해 Client
는 ConcreteFactory
의 변화에 의해 영향을 받지
않을 수 있다.