추상 팩토리 패턴(Abstract Factory Pattern)

Minjae An·2023년 11월 19일
0

설계패턴

목록 보기
2/7

🛗 엘리베이터 부품 업체 변경하기

엘리베이터만 생각하면 여러 제조 업체의 부품을 사용하더라도 같은 동작을 지원하게
하는 것이 바람직하다. 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);
    }
}

❔ 문제점

  • 현재 코드는 LG 부품을 사용하는데 만약 다른 제조 업체 부품을 사용한다면?
  • 새로운 제조 업체 부품을 지원해야 한다면?

다른 제조 업체 부품을 사용해야 하는 경우

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();
    }
}

MotorFactoryDoorFactory 클래스 등과 같이 부품별 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개만 있으면 되기 때문에 싱글턴 패턴을 적용할 수
있다.

ElevatorFactoryFactorygetFactory가 팩토리 메서드 역할을 한다.

🧑‍🏭 추상 팩토리 패턴

추상 팩토리 패턴은 관련성 있는 여러 종류의 객체를 일관된 방식으로 생성하는 경우에
유용하다. 앞선 경우에 부품별로 Factory 메서드를 정의하는 대신 관련 객체들을
일관성 있게 생성할 수 있도록 Factory 메서드를 정의하는 것이 효과적이다.

위 구조를 통해 ClientConcreteFactory의 변화에 의해 영향을 받지
않을 수 있다.

profile
도전을 성과로

0개의 댓글