Design Pattern

Abstract Factory

Jungsoomin :) 2020. 11. 30. 23:02
  • 구체적 클래스에 의존하는 것이 아닌 서로 연관되거나 의존된 객체들의 조합을 만드는 인터페이스를 제공한다.
  • 관련있는 객체들을 일관된 방식으로 생성하는 경우 유용
  • 싱글톤, 팩토리 메서드 패턴을 사용한다.

일관된 방식으로 객체를 생성할 경우 요구사항이 늘어날 수록 팩토리 클래스가 늘어나는 것을 방지해준다.

 


Product의 추상화로부터 생겨나는 Door, Motor 상속클래스와 팩토리, Enum 클래스

추상화 된 Door 에서 상속 되어 나오는 벤더 제품을 만들고 있다. 

공통기능은 템플릿 메서드로 획일화 시킨다.

public abstract class Door {
    private DoorStatus doorStatus;

    public Door() {
        doorStatus = DoorStatus.CLOSED;
    }

    public DoorStatus getDoorStatus() {
        return doorStatus;
    }

    public abstract void doClose();

    // 템플릿 메서드
    public void close() {
        if(doorStatus == DoorStatus.CLOSED)
            return;
        doClose();
        doorStatus = DoorStatus.CLOSED;
    }

    public abstract void doOpen();

    //템플릿 메서드
    public void open() {
        if(doorStatus == DoorStatus.OPENED)
            return;
        doOpen();
        doorStatus = DoorStatus.OPENED;
    }
}

//
public class HyundaiDoor extends Door {
    @Override
    public void doClose() {
        System.out.println("Close Hyundai Door");
    }

    @Override
    public void doOpen() {
        System.out.println("Open Hyundai Door");
    }
}
//
public class LGDoor extends Door {
    @Override
    public void doClose() {
        System.out.println("Close LG Door");
    }

    @Override
    public void doOpen() {
        System.out.println("Open LG Door");
    }
}

추상화된 Motor 에서 상속되어 나오는 벤더 제품을 만들고 있다.

공통 기능은 템플릿 메서드로 획일화 한다.

public abstract class Motor {
    private MotorStatus  motorStatus;

    private Door door;

    //의존 주입
    public void setDoor(Door door) {
        this.door = door;
    }

    public Motor() {
        this.motorStatus = MotorStatus.STOP;
    }

    public MotorStatus getMotorStatus() {
        return motorStatus;
    }

    public void setMotorStatus(MotorStatus motorStatus) {
        this.motorStatus = motorStatus;
    }

    // 템플릿 메서드
    public void move(Direction direction) {
        if(motorStatus == MotorStatus.RUN)
            return;
        if(door.getDoorStatus() == DoorStatus.OPENED){
            door.close();
        }
        doRun();
        motorStatus = MotorStatus.RUN;
    }

    protected abstract void doRun();


}

//
public class LGMotor extends Motor {
    @Override
    protected void doRun() {
        System.out.println("Run LG Motor");
    }
}

//
public class HyundaiMotor extends Motor {
    @Override
    protected void doRun() {
        System.out.println("Run Hyundai Motor");
    }
}

해당 객체들을 만들어 낼 Factory 클래스가 필요하다.

public class DoorFactory {
    public static Door createDoor(VenderID venderID){
        Door door = null;
        switch (venderID) {
            case LG:
                door = new LGDoor();
                break;
            case HYUNDAI:
                door = new HyundaiDoor();
                break;
        }
        return door;
    }
}

//
public class MotorFactory {

    public static Motor createMotor(VenderID venderID){
        Motor motor = null;
        switch (venderID) {
            case LG:
                motor = new LGMotor();
                break;
            case HYUNDAI:
                motor = new HyundaiMotor();
                break;
        }
        return motor;
    }
}

메인 메서드에서 호출을 하면, 해당 로그가 찍힌다.

public class Main {
    public static void main(String[] args) {
        Motor lgMotor = MotorFactory.createMotor(VenderID.LG);
        Door lgDoor = DoorFactory.createDoor(VenderID.LG);
        lgMotor.setDoor(lgDoor);

        lgDoor.open();
        lgMotor.move(Direction.UP);
    }
}

//
Open LG Door
Close LG Door
Run LG Motor

추상 팩토리 패턴의 사용 이유

밴더 사의 부품이 늘어날 수록 제공할 Product 구현체도 많아질 것이며, Product에 대한 Factory 클래스에 다른 벤더 사의 부품을 만들도록 변경해야 한다.

 

주목해야 할 점은 코드의 변경 점이 많다는 것이다.

 

획기적으로 코드 내용을 바꿀 수는 없으나, Factory 클래스에 추상화를 시켜서 획일화된 과정에 대해 코드 변경점을 줄이는 것으로 보인다.

 

ElevatorFactory 의 추상화 과정을 거치면 Main 메서드 분기점으로 부품들을 생성할 수 있다.

public abstract class ElevatorFactory {

    public abstract Motor createMotor();

    public abstract Door createDoor();
}

//
public class LGElevatorFactory extends ElevatorFactory {
    @Override
    public Motor createMotor() {
        return new LGMotor();
    }

    @Override
    public Door createDoor() {
        return new LGDoor();
    }
}

//
public class HyundaiElevatorFactory extends ElevatorFactory {
    @Override
    public Motor createMotor() {
        return new HyundaiMotor();
    }

    @Override
    public Door createDoor() {
        return new HyundaiDoor();
    }
}

//
public class Main {
    public static void main(String[] args) {
        ElevatorFactory elevatorFactory = null;

        String venderName = "Hyundai";

        if("LG".equals(venderName)){
            elevatorFactory = new LGElevatorFactory();
        } else {
            elevatorFactory = new HyundaiElevatorFactory();
        }

        Door door = elevatorFactory.createDoor();
        Motor motor = elevatorFactory.createMotor();
        motor.setDoor(door);

        door.open();;
        motor.move(Direction.UP);
    }
}

//
Open Hyundai Door
Close Hyundai Door
Run Hyundai Motor

메인 메서드 호출 지점의 분기 점 코드를 Factory 클래스에 적용 시키고자 한다면 상화했던 Factory 클래스 위에 Factory 클래스를 두자.

ElevatorFactory 추상클래스를 다시 Factory 클래스로 분기 시켜 ElevatorFactoryFactory 클래스에서 ElevatorFactory 들을 생성시켜 사용한다.

public class LGElevatorFactory extends ElevatorFactory {

    private static LGElevatorFactory lgElevatorFactory = new LGElevatorFactory();

    
    // 팩토리 구현 클래스들을 싱글톤으로 처리
    private LGElevatorFactory() {}

    public static LGElevatorFactory getInstance() {
        return lgElevatorFactory;
    }

    @Override
    public Motor createMotor() {
        return new LGMotor();
    }

    @Override
    public Door createDoor() {
        return new LGDoor();
    }
}

// 
public class HyundaiElevatorFactory extends ElevatorFactory {

    private HyundaiElevatorFactory() {}

    private static HyundaiElevatorFactory hyundaiElevatorFactory = new HyundaiElevatorFactory();

    public static HyundaiElevatorFactory getInstance(){
        return hyundaiElevatorFactory;
    }

    @Override
    public Motor createMotor() {
        return new HyundaiMotor();
    }

    @Override
    public Door createDoor() {
        return new HyundaiDoor();
    }
}

// 추상화한 Factory 클래스를 다시 Factory 클래스로 호출한다.
public class ElevatorFactoryFactory {

    public static ElevatorFactory getFactory(VenderID venderID){
        ElevatorFactory elevatorFactory = null;

        switch (venderID) {
            case HYUNDAI:
                elevatorFactory = HyundaiElevatorFactory.getInstance();
                break;
            case LG:
                elevatorFactory = LGElevatorFactory.getInstance();
                break;
        }
        return elevatorFactory;
    }
}

// 메인 메서드
public class Main {
    public static void main(String[] args) {
        ElevatorFactory factory = ElevatorFactoryFactory.getFactory(VenderID.LG);
        Door door = factory.createDoor();
        Motor motor = factory.createMotor();

        motor.setDoor(door);

        door.open();
        motor.move(Direction.UP);
    }
}

//로깅
Open LG Door
Close LG Door
Run LG Motor

 

사실 타이핑하면서, 클래스 다이어그램을 보면서도 이해가 될랑 말랑한다. Product 처럼 Factory 도 추상화 시키고 추상화한 Factory 클래스를 Factory 클래스로 분기시켜 사용한다. 정도로 정리가 된다.

'Design Pattern' 카테고리의 다른 글

Builder  (0) 2020.11.29
Prototype - 깊은 복사와 얕은 복사, Cloneable  (0) 2020.11.29
Prototype  (0) 2020.11.29
SingleTon Pattern  (0) 2020.11.24
Factory Method  (0) 2020.11.24