Abstract Factory 패턴은 Creational pattern이다.
Factory Method Pattern, GOF Factory Method Pattern에 이어서 새로운 팩토리 패턴을 배워보자. Abstract Factory Pattern은 관련성이 있는 여러 종류의 객체들을 일관된 방식으로 생성할 때 유용하다. 어렵게 생각할 것 없다. 패턴 이름에서 알 수 있듯 이번 디자인 패턴에는 abstract class를 활용한 상속 관계가 등장한다.
추상 팩토리 패턴 이해를 위해 엘리베이터를 만들어보자!
기본 구조
우리가 만들 수 있는 엘리베이터는 LG사 엘리베이터와 현대사 엘리베이터 두 종류다. 엘리베이터는 반드시 내부 부품의 제조사와 엘리베이터 제조사가 같아야 한다. LG 엘리베이터는 LG motor, LG door, LG Lamp를 사용하고 Hyundai 엘리베이터는 Hyundai motor, Hyundai door, Hyundai Lamp 사용한다.
public abstract class Motor {
public void move(Direction direction) {
// 1) 이미 이동 중이면 무시한다.
// 2) 만약 문이 열려 있으면 문을 닫는다.
moveMotor(direction);
// 3) 모터의 상태를 이동중으로 설정한다.
}
protected abstract void moveMotor(Direction direction);
}
엘리베이터 모터 이동은 위와 같이 이루어진다. 상태를 보고 문을 닫고 모터를 움직인다. 이때 엘리베이터가 LG라면 LG Motor를 움직이고 Hyundai라면 Hyundai Motor를 움직인다. 다른 부분은 공통되지만 moveMotor 메서드만 코드가 달라지는 것이다.
public class LgMotor extends Motor {
@Override
protected void moveMotor(Direction direction) {
System.out.println("Lg motor is Moving " + direction);
}
}
public class HyundaiMotor extends Motor {
@Override
protected void moveMotor(Direction direction) {
System.out.println("Hyundai motor is Moving " + direction);
}
}
각자 회사의 모터는 Motor를 상속받되 moveMotor만 재정의하여 자신만의 모터를 완성시켰다.
public abstract class Door {
public void open() {
// 1) 이미 문이 열려있으면 무시한다.
protected abstract void doClose();
// 2) 문의 상태를 열림으로 설정한다.
protected abstract void doOpen();
}
}
Door도 비슷하다. 문을 열고 닫는 과정은 같지만 구체적으로 어떤 회사의 Door를 열고 닫을지는 따로 설정해줘야 한다.
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");
}
}
public class HyundaiDoor extends Door {
@Override
protected void doClose() {
System.out.println("Close Lg Door");
}
@Override
public void doOpen() {
System.out.println("Open Lg Door");
}
}
이렇게! 각 회사의 Door는 Door를 상속받아 doClose와 doOpen 메서드를 재정의했다. 램프도 똑같이 구현 방식은 같지만 어떤 램프를 통해 방식을 구현할지는 자식 클래스에서 별도로 설정해줘야 한다.
이제 모든 부품이 회사 별로 존재하게 됐으니 결과물로 엘리베이터를 만들어볼 순서다.
public class ElevatorCreator {
public static Elevator assembleElevator() {
Elevator elevator = new LgElevator();
Motor motor = new LgMotor();
elevator.setMotor(motor);
Door door = new LgDoor();
elevator.setDoor(door);
motor.setDoor(door);
DirectionLamp lamp = new LgLamp();
elevator.setLamp(lamp);
return elevator;
}
public static void main(String[] args) {
Elevator elevator = assembleElevator();
elevator.move(Direction.UP);
}
}
ElevatorCreator 클래서에는 assembleElevator라는 메서드가 존재하고 해당 메서드를 통해 엘리베이터와 부품을 생성 ➡️ 합체한다. 딱 봐도 문제가 보이지 않는가... 건물주의 변심으로 LG 엘리베이터를 현대 엘리베이터로 변경하고 싶어졌다면... assembleElevator 메서드의 New 연산자 뒤 Lg__()를 전부 Hyundai__()로 변경해야 한다. 당연히 OCP를 만족하지 않는다.
Factory Method Pattern을 적용한 수정 코드
⛔️ GoF Factory Method가 아닌 일반 Factory Method Pattern ⛔️
new 연산자 뒤에 구체적인 클래스 이름이 나오는 것이 문제라면 앞서 배운 Factory Method Pattern을 이용하는 게 어떨까?
public class ElevatorFactory {
public static Elevator createElevator(VendorId id) {
Elevator elevator = null;
switch (id) {
case LG: elevator = new LgElevator(); break;
case HYUNDAI:elevator = new HyundaiElevator(); break;
}
return elevator;
}
}
ElevatorFactory라는 클래스를 생성하고 id에 따라 엘리베이터를 생성하도록 했다.
public class MotorFactory {
public static Motor createMotor(VendorId id) {
Motor motor = null;
switch (id) {
case LG: motor = new LgMotor(); break;
case HYUNDAI:motor = new HyundaiMotor(); break;
}
return motor;
}
}
MotorFactory 클래스에서도 id에 따라 엘리베이터에 맞는 부품을 생성하도록 한다. DoorFactory, LampFactory에서도 동일한 작업을 수행한다.
public class ElevatorCreator {
public static Elevator assembleElevator(VendorId id) {
Elevator elevator = ElevatorFactory.createElevator(id);
Motor motor = MotorFactory.createMotor(id);
elevator.setMotor(motor);
Door door = DoorFactory.createDoor(id);
elevator.setDoor(door);
motor.setDoor(door);
DirectionLamp lamp = LampFactory.createLamp(id);
elevator.setLamp(lamp);
return elevator;
}
public static void main(String[] args) {
Elevator elevator = assembleElevator(VendorId.HYUNDAI);
elevator.move(Direction.UP);
}
}
이제 assembleElevator에서는 new 연산자를 사용하지 않고 id 값에 따라 적절한 엘리베이터와 부품이 생성된다.
🚫 Factory Method Pattern의 문제점 🚫
- 새로운 부품이 생기면 부품 별로 팩토리를 구현해야 한다. 엘리베이터에 들어가는 수많은 부품이 각자의 팩토리 클래스를 갖게 된다.
- 새로운 기업이 생기고 그 기업의 부품을 사용해야 한다면? 삼성 사의 엘리베이터를 납품하기로 하였다면 기존의 모든 팩토리 메서드에 case Samsung:motor = new SamsungMotor(); break; 코드를 추가해주어야 한다. OCP 위반
public class ElevatorCreator {
public static Elevator assembleElevator(VendorId id){
Door hyundaiDoor = DoorFactory.createDoor(id);
Motor hyundaiMotor = MotorFactory.createMotor(id);
hyundaiMotor.setDoor(hyundaiDoor);
ArrivalSensor hyundaiArrivalSensor = ArrivalSensorFactory.createArrivalSensor (id);
WeightSensor hyundaiWeightSensor = WeightSensorFactory.createWeightSensor (id);
ElevatorLamp hyundaiElevatorLamp = ElevatorLampFactory.createElevatorLamp (id);
FloorLamp hyundaiFloorLamp = FloorLampFactory.createFloorLamp (id);
DirectionLamp hyundaiDirectionLamp = DirectionLampFactory.createDirectionLamp (id);
Speaker hyundaiSpeaker = SpeakerFactory.createSpeaker (id);
ElevatorButton hyundaiElevatorButton = ElevatorButtonFactory.createElevatorButton (id);
FloorButton hyundaiFloorButton = FloorButtonFactory.createElevatorFloorButton (id);
…
}
}
수많은 부품을 감당하는 assembleElevator의 모습이다... 끔찍하지 않은가...
Abstract Factory의 등장
✅ 부품이 아니라 제조업체 별로 팩토리를 정의한다면 어떨까?
ElevatorFactory를 통해 생성할 부품을 정의해주고 LGElevatorFactory, HyundaiElevatorFactory에서는 상속 ➡️ 재정의를 통해 자신만의 부품을 생성한다.
public abstract class ElevatorAbstractFactory {
public abstract Motor createMotor();
public abstract Door createDoor();
public abstract DirectionLamp createLamp();
public abstract Elevator createElevator();
}
public class LgElevatorFactory extends ElevatorAbstractFactory {
@Override
public Motor createMotor() { return new LgMotor(); }
@Override
public Door createDoor() { return new LgDoor(); }
@Override
public DirectionLamp createLamp() { return new LgLamp(); }
@Override
public Elevator createElevator() { return new LgElevator(); }
}
이렇게 말이다! 삼성 엘리베이터를 새로 제공하기로 했다면 SamsungElevatorFactory를 생성, ElevatorAbstractFactory를 상속 받아 자신만의 부품 (예를 들어 SamsungMotor) 메서드를 생성하게 된다.
public class ElevatorCreator {
public static Elevator assembleElevator(ElevatorAbstractFactory factory) {
Elevator elevator = factory.createElevator();
Motor motor = factory.createMotor();
elevator.setMotor(motor);
Door door = factory.createDoor();
elevator.setDoor(door);
motor.setDoor(door);
DirectionLamp lamp = factory.createLamp();
elevator.setLamp(lamp);
return elevator;
}
public static void main(String[] args) {
ElevatorAbstractFactory factory = null;
if (args[0].equalsIgnoreCase("LG")) factory = new LgElevatorFactory();
else if (args[0].equalsIgnoreCase("Hyundai")) factory = new HyundaiElevatorFactory();
else factory = new SamsungElevatorFactory();
Elevator elevator = assembleElevator(factory);
elevator.move(Direction.UP);
}
}
abstract Factory Pattern은 관련성이 있는 여러 종류의 객체들을 일관된 방식으로 생성할 때 유용하다.
ConcreteFactory는 항상 변할 수 있는 존재다. 벽지 색깔, 문, 모터 등등.
AbstractFactory는 외부의 어떤 공장을 사용할 건지 고르는 것이다. 상황에 따라 어떤 공장을 선택할지는 알 수 없으니 Abstract로 설정된다.
https://refactoring.guru/ko/design-patterns/abstract-factory
추상 팩토리 패턴
/ 디자인 패턴들 / 생성 패턴 추상 팩토리 패턴 다음 이름으로도 불립니다: Abstract Factory 의도 추상 팩토리는 관련 객체들의 구상 클래스들을 지정하지 않고도 관련 객체들의 모음을 생성할 수 있
refactoring.guru
'공부 > 소프트웨어공학' 카테고리의 다른 글
[디자인패턴] 컴포지트, 복합체 (Composite)패턴이란? (0) | 2024.12.08 |
---|---|
[디자인패턴] 팩토리 메서드(Factory Method) 패턴이란? (+ GoF Factory Method Pattern) (0) | 2024.11.17 |
[디자인패턴] 템플릿 메서드(Template Method) 패턴이란? (0) | 2024.11.15 |
[디자인패턴] 데코레이터(Decorator) 패턴이란? (0) | 2024.11.14 |
[디자인패턴] 빌더(Builder) 패턴이란? (1) | 2024.11.12 |