객체 생성 : 추상 팩토리 패턴 (Abstract Factory Pattern)

    들어가기 전

    이 글은 인프런 백기선님의 강의를 복습하며 작성한 글입니다. 

     


    추상 팩토리 패턴 

    • GOF : 관련 있는 여러 객체의 패밀리를 생성하기 위해 인터페이스를 제공함. 
      • 객체 생성 인터페이스는 AbstractFactory를 통해서 제공됨.
      • ConcreteFactory에서는 관련있는 여러 객체를 제공하는 인터페이스를 구현함.
      • 클라이언트는 ConcreteFactory를 DI 받아서 사용함.
    • 추상 팩토리 패턴 vs 팩토리 메서드 패턴
      • 팩토리 메서드 패턴구체적인 객체 생성을 Factory의 하위 클래스로 옮김. 구체적인 객체 추가 시, 확장에 유리함. (단일 객체 생성)
      • 추상 팩토리 패턴관련 있는 여러 객체의 패밀리를 생성. 다양한 객체를 동시에 생성해야할 때 유리함.  (여러 객체 생성)
    • 추상 팩토리 패턴에서는 Client에서 추상 팩토리 인스턴스를 이용해 직접 객체를 조립(?) 한다.

     


    디자인 패턴이 필요한 코드

    public class WhiteshipFactory extends DefaultShipFactory {
    
        @Override
        public Ship createShip() {
            Ship ship = new Whiteship();
            ship.setAnchor(new WhiteAnchor());
            ship.setWheel(new WhiteWheel());
            return ship;
        }
    }

    위 코드에서 Whiteship 인스턴스를 만들려고 한다. 이 코드를 자세히 살펴보면 클라이언트(WhiteshipFactory)는 createShip() 메서드 내에서 Ship 인스턴스를 생성한 후 setter를 이용해 Whiteship을 조립해서 만든다. 이 코드의 문제점은 무엇일까?

    • 현재는 Ship의 제품군이 Whiteship 밖에 없다. 이후에 Blueship, Redship이 추가되는 경우라면, 이쪽 코드가 계속 변경되어야 함.

    예를 들어 Blueship이 추가된 경우 createBlueShip() 같은 메서드가 추가되거나, createShip()안에 If ~ Else문을 추가해서 Blueship 만드는 방법을 추가해야한다. 이 때 Whiteship를 생성하는 것을 Factory Method 패턴으로 빼보려고 생각할 수도 있지만, 현재 Wheel, Anchor 2개를 생성해야하기 때문에 Factory Method 패턴을 사용하기에는 쉽지 않다.

    만약 Factory Method 패턴을 사용하고 싶다면 Whiteship이라는 클래스를 하나 만들고, Whiteship 클래스를 생성해서 반환하도록 수정해야 할 것이다. 그러나 Ship이 여러 종류의 조합으로 만들어 질 가능성이 있다면 Factory 메서드 패턴을 사용할 수 없다. 각 경우에 대한 Ship 클래스를 생성해야 할 것이고, 이렇게 되면 클래스 폭발로 넘어가게 된다. 예를 들어 WhiteWheelAndBlackAnchorShip.. 같은 형태로 문제가 발생한다. 

    또한 현재 클라이언트 코드에서는 WhiteProWheel() 같은 것들이 추가되는 경우에 역시 클라이언트의 setWheel() 쪽 코드가 바뀌어야 할 가능성도 커진다. 이렇게 여러 개의 제품군을 생성하는 방법을 클라이언트로부터 추상화 하기 위해서 추상 팩토리 메서드 패턴을 이용할 수 있다.

     


    디자인 패턴 적용해보기

    배를 만들기 위해서는 Wheel, Anchor가 필요하고 추상 팩토리 패턴에서 추상 팩토리는 2개 이상의 인스턴스를 생성해 줄 수 있다. 이를 이용해서 Wheel, Anchor를 PartsFactory로 묶어서 '그룹핑' 해볼 수 있다.

    public interface PartsFactory {
        Anchor getAnchor();
        Wheel getWheel();
    }

    추상 팩토리로 사용되는 PartsFactory에서는 getAnchor(), getWheel() 인터페이스를 제공한다. 그리고 PartsFactory 인터페이스를 구현하는 클래스는 구체적으로 사용할 Wheel, Anchor를 제공한다. 

    public class WhitePartsProFactory implements ShipPartsFactory {
        @Override
        public Anchor createAnchor() {
            return new WhiteAnchorPro();
        }
    
        @Override
        public Wheel createWheel() {
            return new WhiteWheelPro();
        }
    }

    하나의 예시로 WhitePartsProFactory를 AbstractFactory의 구현체로 만들었다. 

    public class WhiteshipFactory extends DefaultShipFactory {
    
        private ShipPartsFactory shipPartsFactory;
    
        public WhiteshipFactory(ShipPartsFactory shipPartsFactory) {
            this.shipPartsFactory = shipPartsFactory;
        }
    
        @Override
        public Ship createShip() {
            Ship ship = new Whiteship();
            ship.setAnchor(shipPartsFactory.createAnchor());
            ship.setWheel(shipPartsFactory.createWheel());
            return ship;
        }
    }

    이렇게 수정하면 이전의 클라이언트(WhiteshipFactory)의 createShip()에서는 제품군이 새롭게 추가되어도 코드가 변경되지 않게 된다. 왜냐하면 구체적인 제품군은 DI된 shipPartFactory에 의해서 추상화 되기 때문이다.

    public class WhitePartsProFactory implements ShipPartsFactory {
        @Override
        public Anchor createAnchor() {
            return new WhiteAnchorPro();
        }
    
        @Override
        public Wheel createWheel() {
            return new WhiteWheelPro();
        }
    }
    

    예를 들어 WhiteWheelPro, WhiteshipAnchor라는 제품군이 추가되었다고 가정해보자. 그럼에도 불구하고 createShip()의 메서드는 바뀌지 않아도 된다. WhiteWheelPro + WhiteshipAnchor가 추가되면 그룹핑된 두 객체를 생성해주는 새로운 Concrete Factory인 WhitePartsProFactory를 추가하고, WhitePartsProFactory를 클라이언트에게 주입시켜주면 된다. 따라서 새로운 제품군이 추가되더라도 (생성되어야 할 객체가 추가되어도) 기존 코드에는 영향을 주지 않는다. 

     


    Abstract Factory와 Factory Method의 차이점

    • Factory Method 패턴은 구체적인 타입의 인스턴스를 만드는 과정을 ConcreteFactory(하위 클래스)로 숨김.
    • Abstract Factory 패턴은 팩토리를 사용하는 쪽의 관점에서 보는 것임. 
      • 그룹핑 된 여러 객체의 생성을  제공할 수 있음. 
      • 팩토리를 통해서 추상화 된 인터페이스만 클라이언트가 쓸 수 있게 해줌. (클라이언트 입장에서는 Concrete 클래스를 직접 참조해서 쓸 필요가 없어짐) 
    • 모양과 효과
      • 둘다 비슷함.
      • 두 패턴 모두 구체적인 객체 생성 과정을 추상화한 인터페이스를 제공함.
    • 목적이 다름
      • Factory Method 패턴은 구체적인 객체 생성 과정을 하위 클래스 / 구체적인 클래스로 옮기는 것이 목적
      • 추상 팩토리 패턴은 관련있는 여러 객체를 구체적인 클래스에 의존하지 않고 만들 수 있게 해주는 것이 목적. 

    이전에 클라이언트가 객체를 생성하기 위해서 필요한 의존성을 넣어줄 때, 구체적인 객체를 생성하는데 의존하는 부분이 있다면 이것을 추상 팩토리 패턴을 통해서 완화시켜 줄 수 있다. 구체적인 예시로는 위에서 보여주었던 ship.setWheel(new WhiteWheel()); 같은 부분이다.

    댓글

    Designed by JB FACTORY