JUINTINATION
추상 팩토리(Abstract Factory) 패턴 본문
추상 팩토리 패턴이란?
관련성 있는 여러 종류의 객체를 일관된 방식으로 생성하는 경우에 객체를 생성하는 코드를 캡슐화하여 구체적인 클래스에 의존하지 않고 서로 연관되거나 의존적인 객체들의 조합을 만드는 패턴이다.
다음과 같이 엘레베이터 부품 업체에 대한 설계가 있다고 하자.
Motor 클래스의 move 메서드의 구조는 다음과 같다.
public void move(Direction direction) {
// 1. 이미 이동 중이면 무시한다.
// 2. 만약 문이 열려 있으면 문을 닫는다.
// 3. 모터를 구동해서 이동시킨다.
// 4. 모터의 상태를 이동 중으로 설정한다.
}
3번 부분을 제외하면 Hyundai 모터, LG 모터 둘 다 코드가 동일하기 때문에 템플릿 메서드 패턴을 적용할 수 있다.
Door 클래스의 경우에도 open과 close 메서드에 위의 클래스 다이어그램과 같이 각각 템플릿 메서드 패턴을 적용할 수 있다.
또한 특정 제조 업체의 모터와 문을 제어하는 클래스가 필요한데 이 때 위와 같이 팩토리 메서드 패턴을 적용할 수 있다.
enum VendorID {
Hyundai, LG
}
class DoorFactory {
public static Door createDoor(VendorID vendorID) {
Door door = null;
switch (vendorID) {
case Hyundai:
door = new HyundaiDoor();
break;
case LG:
door = new LGDoor();
break;
}
return door;
}
}
전체 코드는 다음과 같다.
enum MotorStatus {
MOVING,
STOPPED
}
enum DoorStatus {
OPENED, CLOSED
}
enum Direction {
UP,
DOWN;
}
enum VendorID {
Hyundai, LG
}
abstract class Door {
private DoorStatus doorStatus;
public Door() {
doorStatus = DoorStatus.CLOSED;
}
public DoorStatus getDoorStatus() {
return doorStatus;
}
public void open() {
if (doorStatus == DoorStatus.OPENED) return;
doOpen();
doorStatus = DoorStatus.OPENED;
}
public void close() {
if (doorStatus == DoorStatus.CLOSED) return;
doClose();
doorStatus = DoorStatus.CLOSED;
}
protected abstract void doOpen();
protected abstract void doClose();
}
class HyundaiDoor extends Door {
@Override
protected void doOpen() {
System.out.println("HyundaiDoor.doOpen");
}
@Override
protected void doClose() {
System.out.println("HyundaiDoor.doClose");
}
}
class LGDoor extends Door {
@Override
protected void doOpen() {
System.out.println("LGDoor.doOpen");
}
@Override
protected void doClose() {
System.out.println("LGDoor.doClose");
}
}
class DoorFactory {
public static Door createDoor(VendorID vendorID) {
Door door = null;
switch (vendorID) {
case Hyundai:
door = new HyundaiDoor();
break;
case LG:
door = new LGDoor();
break;
}
return door;
}
}
abstract class Motor {
protected Door door;
private MotorStatus motorStatus;
public Motor() {
motorStatus = MotorStatus.STOPPED;
}
public MotorStatus getMotorStatus() {
return motorStatus;
}
protected void setMotorStatus(MotorStatus motorStatus) {
this.motorStatus = motorStatus;
}
public void move(Direction direction) {
if (getMotorStatus() == MotorStatus.MOVING) return;
if (door.getDoorStatus() == DoorStatus.OPENED) door.close();
moveMotor(direction);
setMotorStatus(MotorStatus.MOVING);
}
protected abstract void moveMotor(Direction direction);
public void setDoor(Door door) {
this.door = door;
}
}
class HyundaiMotor extends Motor {
@Override
protected void moveMotor(Direction direction) {
System.out.println("HyundaiMotor.moveMotor");
}
}
class LGMotor extends Motor {
@Override
protected void moveMotor(Direction direction) {
System.out.println("LGMotor.moveMotor");
}
}
class MotorFactory {
public static Motor createMotor(VendorID vendorID) {
Motor motor = null;
switch (vendorID) {
case Hyundai:
motor = new HyundaiMotor();
break;
case LG:
motor = new LGMotor();
break;
}
return motor;
}
}
- 위 코드의 문제점
- 삼성과 같이 현대 및 LG가 아닌 다른 회사의 부품을 사용해야 한다면 기존의 DoorFactory 클래스와 MotorFactory 클래스를 수정해야 한다.
- 유지 보수의 어려움
- OCP 위반
- 문과 모터 뿐만 아니라 버튼, 센서, 스피커 등 다른 부품이 추가되어야 한다면 각 부품에 대한 Factory 클래스를 추가적으로 만들어야 하고 특정 업체별 부품을 생성하는 코드의 길이가 길어지고 복잡해질 수 있다.
- 삼성과 같이 현대 및 LG가 아닌 다른 회사의 부품을 사용해야 한다면 기존의 DoorFactory 클래스와 MotorFactory 클래스를 수정해야 한다.
일반적으로 현대 엘레베이터는 현대 모터, 현대 문을 사용할 것이고 LG 엘레베이터는 LG 모터, LG 문을 사용할 것이다. 이와 같이 여러 종류의 객체를 생성할 때 객체들 사이에 관련성이 있다면 각 종류별로 별도의 Factory 클래스를 사용하는 대신 관련 객체들을 일관성 있게 생성하는 Factory 클래스를 사용하는 것이 편리할 수 있다.
위와 같은 문제를 해결하기 위해 HyundaiElevatorFactory, LGElevatorFactory와 같이 제조 업체별로 Factory 클래스를 만들었다.
HyundaiElevatorFactory, LGElevatorFactory 둘 다 createMotor 메서드와 createDoor 메서드를 공통적으로 갖고 있기 때문에 ElevatorFactory라는 추상 클래스를 만들어서 상위 클래스로 지정할 수 있을 것 같다.
abstract class ElevatorFactory {
public abstract Motor createMotor();
public abstract Door createDoor();
}
class HyundaiElevatorFactory extends ElevatorFactory {
@Override
public Motor createMotor() {
return new HyundaiMotor();
}
@Override
public Door createDoor() {
return new HyundaiDoor();
}
}
class LGElevatorFactory extends ElevatorFactory {
@Override
public Motor createMotor() {
return new LGMotor();
}
@Override
public Door createDoor() {
return new LGDoor();
}
}
또한 구체적인 Factory 클래스를 생성하는 팩토리 메서드 패턴을 적용할 수 있을 것이다.
class ElevatorFactoryFactory {
public static ElevatorFactory getFactory(VendorID vendorID) {
ElevatorFactory factory = null;
switch (vendorID) {
case Hyundai:
factory = new HyundaiElevatorFactory();
break;
case LG:
factory = new LGElevatorFactory();
break;
}
return factory;
}
}
각 Factory 객체를 생성할 때 싱글턴 패턴을 적용하면 다음과 같다.
import java.util.Scanner;
enum MotorStatus {
MOVING,
STOPPED
}
enum DoorStatus {
OPENED, CLOSED
}
enum Direction {
UP,
DOWN;
}
enum VendorID {
Hyundai, LG
}
abstract class Door {
private DoorStatus doorStatus;
public Door() {
doorStatus = DoorStatus.CLOSED;
}
public DoorStatus getDoorStatus() {
return doorStatus;
}
public void open() {
if (doorStatus == DoorStatus.OPENED) return;
doOpen();
doorStatus = DoorStatus.OPENED;
}
public void close() {
if (doorStatus == DoorStatus.CLOSED) return;
doClose();
doorStatus = DoorStatus.CLOSED;
}
protected abstract void doOpen();
protected abstract void doClose();
}
class HyundaiDoor extends Door {
@Override
protected void doOpen() {
System.out.println("HyundaiDoor.doOpen");
}
@Override
protected void doClose() {
System.out.println("HyundaiDoor.doClose");
}
}
class LGDoor extends Door {
@Override
protected void doOpen() {
System.out.println("LGDoor.doOpen");
}
@Override
protected void doClose() {
System.out.println("LGDoor.doClose");
}
}
abstract class Motor {
protected Door door;
private MotorStatus motorStatus;
public Motor() {
motorStatus = MotorStatus.STOPPED;
}
public MotorStatus getMotorStatus() {
return motorStatus;
}
protected void setMotorStatus(MotorStatus motorStatus) {
this.motorStatus = motorStatus;
}
public void move(Direction direction) {
if (getMotorStatus() == MotorStatus.MOVING) return;
if (door.getDoorStatus() == DoorStatus.OPENED) door.close();
moveMotor(direction);
setMotorStatus(MotorStatus.MOVING);
}
protected abstract void moveMotor(Direction direction);
public void setDoor(Door door) {
this.door = door;
}
}
class HyundaiMotor extends Motor {
@Override
protected void moveMotor(Direction direction) {
System.out.println("HyundaiMotor.moveMotor");
}
}
class LGMotor extends Motor {
@Override
protected void moveMotor(Direction direction) {
System.out.println("LGMotor.moveMotor");
}
}
class ElevatorFactoryFactory {
public static ElevatorFactory getFactory(VendorID vendorID) {
ElevatorFactory factory = null;
switch (vendorID) {
case Hyundai:
factory = HyundaiElevatorFactory.getInstance();
break;
case LG:
factory = LGElevatorFactory.getInstance();
break;
}
return factory;
}
}
abstract class ElevatorFactory {
public abstract Motor createMotor();
public abstract Door createDoor();
}
class HyundaiElevatorFactory extends ElevatorFactory {
private static ElevatorFactory factory = new HyundaiElevatorFactory();
private HyundaiElevatorFactory() {}
public static ElevatorFactory getInstance() {
return factory;
}
@Override
public Motor createMotor() {
return new HyundaiMotor();
}
@Override
public Door createDoor() {
return new HyundaiDoor();
}
}
class LGElevatorFactory extends ElevatorFactory {
private static ElevatorFactory factory = new LGElevatorFactory();
private LGElevatorFactory() {}
public static ElevatorFactory getInstance() {
return factory;
}
@Override
public Motor createMotor() {
return new LGMotor();
}
@Override
public Door createDoor() {
return new LGDoor();
}
}
public class Client {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String vendorName = sc.nextLine();
VendorID vendorID = vendorName.toUpperCase().equals("HYUNDAI") ? VendorID.Hyundai : VendorID.LG;
ElevatorFactory factory = ElevatorFactoryFactory.getFactory(vendorID);
Door door = factory.createDoor();
door.open();
Motor motor = factory.createMotor();
motor.move(Direction.UP);
}
}
추상 팩토리 패턴 컬레보레이션
AbstractFactory : 실제 팩토리 클래스의 공통 인터페이스, 각 제품의 부품을 생성하는 기능을 추상 메서드로 정의한다.
ConcreteFactory : 구체적인 팩토리 클래스
AbstractProduct : 제품의 공통 인터페이스
ConcreteProduct : 구체적인 팩토리 클래스에서 생성되는 구체적인 제품 클래스
'JAVA객체지향디자인패턴' 카테고리의 다른 글
컴퍼지트(Composite) 패턴 (0) | 2023.07.17 |
---|---|
팩토리 메서드(Factory Method) 패턴 (1) | 2023.07.16 |
템플릿 메서드(Template Method) 패턴 (0) | 2023.07.16 |
데커레이터(Decorator) 패턴 (0) | 2023.07.09 |
옵서버(Observer) 패턴 (0) | 2023.07.06 |