Priv's Blog
13. 추상 팩토리 패턴 본문
1. 엘리베이터 부품 업체 변경하기
엘리베이터를 구성하는 부품들은 여러 제조 업체들을 가진다.
엘리베이터의 여러 제조 업체 부품들을 사용하더라도 모두 동일한 동작을 지원하게 만드는 것이 바람직하다.
즉, 건물 A에서 LG 부품을 사용하고, 건물 B에서 현대 부품을 사용하더라도 엘리베이터 프로그램의 변경은 최소화되어야 한다는 것이다.
LG와 현대 모터는 구체적인 로직은 달라도 엘리베이터 입장에서 보면 모터를 사용해 엘리베이터를 동작시킨다는 것 자체는 동일하다.
그러므로 추상 클래스로 Motor를 정의하고, LGMotor, HyundaiMotor를 하위 클래스로 정의할 수 있다.
Door 또한 Motor와 마찬가지로 구성할 수 있다.
위의 구조에서 모터를 움직이게 만드는 핵심 기능인 move 메서드는 LG, 현대 모터 모두 동일하다.
즉 일반적인 흐름에서는 동일하지만, 특정 부부만 다른 동작을 하고 있으므로 이는 템플릿 메서드 패턴으로 설계하면 좋다.
Door 클래스의 경우에도 open, close 메서드 각각에 템플릿 메서드 패턴을 적용하면 좋다.
엘리베이터 입장에서 본다면 특정 제조 업체의 모터, 문을 제어하는 클래스가 필요하다.
LGMotor 객체와 LGDoor 객체가 바로 그것이다.
이 경우에는 팩토리 메서드를 적용하여 객체를 생성할 수 있다.
즉, MotorFactory 클래스를 하나 정의하고, LGMotor, HyundaiMotor 중 특정 제조 업체에 따라 해당 Motor 객체를 생성하도록 만드는 것이다.
MotorFactory의 createMotor 메서드는 VendorID 매개변수에 따라 LGMotor, HyundaiMotor 객체를 생성한다.
Door 클래스 역시 DoorFactory 클래스를 사용해 정의할 수 있다.
이제 제조 업체에 따라 모터, 문에 해당하는 구체적인 클래스를 생성해야 한다.
만약 LG 업체의 모터와 문을 사용한다면 LGMotor, LGDoor 객체를 사용하는 식이다.
package P13;
import P11.HyundaiMotor;
public enum VendorID { LG, HYUNDAI }
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;
}
}
package P13;
public class DoorFactory {
public static Door createDoor(VendorID vendorID) {
Door door = null;
switch (vendorID) {
case LG :
door = new LGDoor();
break;
case HYUNDAI :
door = new HyudaiDoor();
break;
}
return door;
}
}
2. 문제점
만약 다른 제조 업체의 부품을 사용해야 한다면 어떻게 될까? (LGMotor + HyundaiDoor)
새로운 제조 업체의 부품을 지원해야 한다면 어떻게 될까? (LGMotor + SamsungDoor)
2.1) 다른 제조 업체의 부품을 사용해야 하는 경우
엘리베이터 프로그램에서 HyundaiMotor, HyundaiDoor 객체를 사용하려면 MotorFactory와 DoorFactory 클래스를 통해 이미 정의되어 있는 객체를 생성하도록 코드를 수정해야 한다.
하지만 이렇게 코드를 수정하면 모터, 문, 램프, 센서, 스피커, 버튼 등 다양한 부품들을 사용해야 할 때마다 각 Factory 클래스를 구현하고 이들의 Factory 객체를 각각 생성해야 한다.
즉, 부품의 수가 많아지면 특정 업체별 부품을 생성하는 코드의 길이가 길고 복잡해진다.
2.2) 새로운 제조 업체의 부품을 지원해야 하는 경우
삼성의 부품을 새로 가져와 사용하게 되었다면, 새로운 Motor, Door 클래스를 지원해야 한다.
이 경우, MotorFactory, DoorFactory 클래스가 SamsungMotor, SamsungDoor 클래스를 지원할 수 있게 수정되어야 한다.
이뿐만 아니라 램프, 버튼, 센서 등 다른 부품들이 새로 추가될 때마다 클래스를 모두 수정해주어야 한다.
여태까지 살펴본 바에 의하면 기존의 팩토리 메서드 패턴을 이용한 객체 생성은 관련 있는 여러 객체를 일관성 있는 방식으로 생성할 경우, 많은 코드 변경이 발생한다는 것이다.
package P13;
public class DoorFactory {
public static Door createDoor(VendorID vendorID) {
Door door = null;
switch (vendorID) {
case LG :
door = new LGDoor();
break;
case HYUNDAI :
door = new HyudaiDoor();
break;
case SAMSUNG :
door = new SamsungDoor();
break;
}
return door;
}
}
3. 해결책
엘리베이터는 특정 모터와 문을 제어해야 한다.
일반적으로 현대의 모터를 쓴다면 현대의 문을 사용할 것이다.
이처럼 여러 종류의 객체를 생성할 때, 서로 연관성이 있는 객체일 경우, 각 종류에 따라 별도의 Factory 클래스를 사용하는 것 대신, 관련 객체들을 일관성 있게 생성하는 Factory 클래스를 사용하는 것이 더 편리하다.
즉, MotorFactory, DoorFactory 클래스와 같이 부품별로 Factory 클래스를 만드는 대신, LGElevatorFactory 클래스처럼 제조사 별로 Factory 클래스를 만드는 것이다.
여기서 LGElevatorFactory는 LGMotor, LGDoor 객체를 생성하는 팩토리 클래스에 해당한다.
2개의 ElevatorFactory 클래스는 모두 createMotor 메서드, createDoor 메서드를 제공한다.
즉, 엘리베이터를 구성하는 각 부품 객체를 생성하는 메서드가 공통으로 있는 것이다.
그러므로 2개의 ElevatorFactory 클래스를 일반화한 상위 클래스를 정의할 수 있다.
여기서 ElevatorFactory는 추상 클래스이며, createMotor, createDoor는 추상 메서드이다.
이제 제조사별로 Factory 클래스를 정의했으므로, 제조사별 부품 객체를 아주 간단하게 생성할 수 있다.
LG 부품을 사용할 때는 LGElevatorFactory 객체를, 현대 부품을 사용할 때는 HyundaiElevatorFactory 객채를 생성하면 된다.
새로운 제조 업체 부품을 지원할 때는 삼성 부품의 객체를 생성하도록 코드를 수정해야 한다.
기존의 MotorFactory, DoorFactory 클래스 등과 같이 부품별 Factory 클래스를 활용하는 경우에는 삼성 부품의 객체를 생성하도록 각 Factory 클래스를 수정해야 했다.
이제는 부품이 아니라 제조사별로 Factory 클래스가 있으므로, 부품의 객체를 생성하는 SamsungFactory 클래스만 새로 만들면 된다.
package P13;
public abstract class ElevatorFactory {
public abstract Motor createMotor();
public abstract Door createDoor();
}
package P13;
public class LGElevatorFactory extends ElevatorFactory {
public Motor createMotor() {
return new LGMotor();
}
public Door createDoor() {
return new LGDoor();
}
}
package P13;
public class HyundaiElevatorFactory extends ElevatorFactory {
public Motor createMotor() {
return new HyudaiDoor();
}
public Door createDoor() {
return new HyudaiDoor();
}
}
package P13;
public class SamsungElevatorFactory extends ElevatorFactory {
public Motor createMotor() {
return new SamsungMotor();
}
public Door createDoor() {
return new SamsungDoor();
}
}
package P13;
public class SamsungDoor {
protected void doClose() {
System.out.println("close Samsung Door");
}
protected void doOpen() {
System.out.println("open Samsung Door");
}
}
package P13;
public class SamsungMotor extends Motor {
protected void moveMotor(Direction direction) {
System.out.println("move Samsung Motor");
}
}
이제 SamsungElevatorFactory 객체를 생성하는 구문만 수정할 뿐, Door, Motor 객체를 생성하고 이용하는 부분은 건드릴 필요가 없다.
즉, 새로운 제조 업체 부품을 지원하고자 한다면 해당 제조 업체의 부품을 생성하는 Factory 클래스를 추가하면 된다.
여기서 구체적인 Factory 클래스를 생성하는 부분은 팩토리 메서드 패턴을 사용함으로써 제조사별 Factory 객체를 생성하는 방식을 캡슐화할 수 있다.
또한 제조사별로 Factory 객체는 1개만 존재하면 된다.
그러므로 LGElevatorFactory, SamsungElevatorFactory, HyundaiElevatorFactory 클래스는 싱글턴 패턴으로 설계해야 한다.
package P13;
public class ElevatorFactoryFactory {
public static ElevatorFactory getFactory(VendorID vendorID) {
ElevatorFactory factory = null;
switch(vendorID) {
case LG :
factory = LGElevatorFactory.getInstance();
break;
case HYUNDAI :
factory = HyundaiElevatorFactory.getInstance();
break;
case SAMSUNG :
factory = SamsungElevatorFactory.getInstance();
break;
}
return factory;
}
}
package P13;
public class LGElevatorFactory extends ElevatorFactory {
private static ElevatorFactory factory;
private LGElevatorFactory() { }
public static ElevatorFactory getInstance() {
if (factory == null) {
factory = new LGElevatorFactory();
}
return factory;
}
public Motor createMotor() {
return new LGMotor();
}
public Door createDoor() {
return new LGDoor();
}
}
4. 추상 팩토리 패턴
추상 팩토리 패턴은 관련성이 있는 여러 종류의 객체를 일관된 방식으로 생성하는 경우에 사용하는 패턴이다.
엘리베이터 예제처럼 부품별로 Factory를 정의하지 않고 관련 객체들을 일관성 있게 생성할 수 있는 제조사별 Factory를 정의하는 방식이다.
추상 팩토리 패턴을 엘리베이터 부품 업체 예제에 적용하면 다음과 같다.
수고하셨습니다!
'Dev. Study Note > Design Pattern' 카테고리의 다른 글
14. 컴퍼지트 패턴 (0) | 2022.08.12 |
---|---|
12. 팩토리 메서드 패턴 (0) | 2022.07.03 |
11. 템플릿 메서드 패턴 (0) | 2022.07.03 |
10. 데커레이터 패턴 (0) | 2022.07.03 |
9. 옵서버 패턴 (0) | 2022.07.03 |