관리 메뉴

bright jazz music

Compound Pattern: 3. Factory 패턴 본문

Design Pattern/Compound Pattern

Compound Pattern: 3. Factory 패턴

bright jazz music 2022. 10. 15. 08:40

DuckSimulator에서 데코레이터로 객체를 감싸야만 오리가 소리 낸 횟수를 셀 수 있다. 그런데 오리의 종류가 늘어나고 DuckSimulator의 코드가 복잡해진다면 실수로 객체를 감싸지 않는 문제가 생길 수 있다. 이런 경우 차라리 오리를 생성하고 감싸는 작업을 분리하여 한 군데서 수행하는 것이 좋다.

 

이 때 팩토리 패턴을 사용해볼 수 있다.

 

이 예제에서는 추상 팩토리 클래스를 만들고 그 팩토리 클래스를 상속하는 방식으로 또다른 팩토리 클래스를 만들어 나가는 방식을 사용한다.

 

아래는 추상 팩토리 클래스인 AbstractDuckFactory이다. 이를 기반으로 기능을 추가한 팩토리 클래스를 생성할 것이다.

//AbstractDuckFactory.java
//새로운 기능을 사용할 때는 객체를 데코레이터로 감싸야만 가능
//이 감싸는 작업을 따로 빼내어 한 군데서 하기 위해 팩토리 사용
package com.example.designpatternstudy.compoundPattern;
//추상 클래스
public abstract class AbstractDuckFactory {

    //추상 메서드 : 선언부만 작성 가능하며 구현부(메서드 바디)는 작성할 수 없다. 이는 구현 클래스에서 작성할 것이다.
    
    public abstract QuackableInterfaceForCompoundPattern createMallardDuck();
    public abstract QuackableInterfaceForCompoundPattern createRedheadDuck();
    public abstract QuackableInterfaceForCompoundPattern createDuckCall();
    public abstract QuackableInterfaceForCompoundPattern createRubberDuck();

}

 

아래는 각 오리 클래스의 객체를 생성하는 기능을 가진 DuckFactory 클래스이다. DuckFactory클래스는 팩토리를 만드는 방식을 보여주기 위해 생성한 클래스이고, 기본 객체를 생성하는 기능 외에 추가적인 기능은 없다. 메서드 반환값을 살펴볼 필요는 있다. 각 메서드의 반환값은 QuackableInterfaceForCompoundPattern이다. 따라서 QuackableInterfaceForCompoundPattern만 반환한다면 실제로 해당 메서드에서 어떤 것이 만들어지는지 외부에서는 알지 못한다.

//DuckFactory.java
//데코레이터가 없는 오리 생성 팩토리
package com.example.designpatternstudy.compoundPattern;

public class DuckFactory extends AbstractDuckFactory{ //추상 팩토리 확장
    //각 메소드는 QuackableInterfaceForCompoundPattern 인터페이스 객체를 만든다.
    //시뮬레이터는 실제 어떤 제품이 만들어지는지 알 수 없다.
    //단순히 QuackableInterfaceForCompoundPattern 리턴된다는 것만 안다.

    @Override
    public QuackableInterfaceForCompoundPattern createMallardDuck() {
        return new MallardDuck();
    }

    @Override
    public QuackableInterfaceForCompoundPattern createRedheadDuck() {
        return new RedheadDuck();
    }

    @Override
    public QuackableInterfaceForCompoundPattern createDuckCall() {
        return new DuckCall();
    }

    @Override
    public QuackableInterfaceForCompoundPattern createRubberDuck() {
        return new RubberDuck();
    }
}

 

아래는 데코레이터로 감싸는 기능을 가진 CountingDuckFactory 클래스이다. 생성한 오리 객체를 데코레이터 클래스인 QuackCountingDecorator로 감싸서 반환한다는 것이 차이점이다. QuackCountingDecorator 클래스 역시 QuackableInterfaceForCompoundPattern 인터페이스를 구현한 구현 클래스이므로 QuackableInterfaceForCompoundPattern을 반환하는 데 문제될 것은 없다.

package com.example.designpatternstudy.compoundPattern;

public class CountingDuckFactory extends AbstractDuckFactory{

    //모든 메서드에서 QuackableInterfaceForCompoundPattern객체를
    //QuackCountingDecorator데코레이터로 감싼다.
    //시뮬레이터는 데코레이터로 감싼 객체가 반환된다는 것을 알 수 없다.
    //이전과 똑같이 QuackableInterfaceForCompoundPattern을 반환받았다고 여긴다.

    @Override
    public QuackableInterfaceForCompoundPattern createMallardDuck() {
        return new QuackCountingDecorator(new MallardDuck());
    }

    @Override
    public QuackableInterfaceForCompoundPattern createRedheadDuck() {
        return new QuackCountingDecorator(new RedheadDuck());
    }

    @Override
    public QuackableInterfaceForCompoundPattern createDuckCall() {
        return new QuackCountingDecorator(new DuckCall());
    }

    @Override
    public QuackableInterfaceForCompoundPattern createRubberDuck() {
        return new QuackCountingDecorator(new RubberDuck());
    }
}

 

 

DuckSimulator를 이용해서 테스트 해보자. 

 

  • initDuckSimul() 메서드의 바디에 CountingDuckFactory 객체를 생성하여 업캐스팅 하여 AbstractDuckFactory 타입의 변수  duckFactory에 대입하는 것을 확인할 수 있다. CountingDuckFactory 역시 AbstractDuckFactory를 상속하므로 문제될 것이 없다. 
  • 이렇게 만들어진 AbstractDuckFactory 객체를 simulateAll() 메서드에 파라미터로 넘긴다.
  • simulateAll() 메서드의 선언부에서 AbstractDuckFactory duckFactory를 파라미터로 추가한 것을 확인할 수 있다.
  • duckFactory는 simulateAll() 메서드의 내부에서 데코레이터로 감싼 객체를 생성하는 데 사용된다.
//DuckSimulator.java

package com.example.designpatternstudy.compoundPattern;

public class DuckSimulator {

    public static void initDuckSimul(){
        DuckSimulator simulator = new DuckSimulator();
        //simulateAll()메서드에 전달할 팩토리 생성(데코레이터 사용)
        AbstractDuckFactory duckFactory = new CountingDuckFactory();

        //파라미터 추가
        simulator.simulateAll(duckFactory);
    }

    //파라미터 추가
    void simulateAll(AbstractDuckFactory duckFactory){
        //인터페이스를 새로 생성할 때마다 새로운 데코레이터로 감싼다.
        //객체의 인터페이스를 직접 생성하지 않고 팩토리 메소드로 생성한다.

        QuackableInterfaceForCompoundPattern mallardDuck
                = duckFactory.createMallardDuck();

        QuackableInterfaceForCompoundPattern redheadDuck
                = duckFactory.createRedheadDuck();

        QuackableInterfaceForCompoundPattern duckCall
                = duckFactory.createDuckCall();

        QuackableInterfaceForCompoundPattern rubberDuck
                = duckFactory.createRubberDuck();

        //GooseAdapter를 사용해서 오리가 됨
        QuackableInterfaceForCompoundPattern gooseDuck = new GooseAdapter(new Goose());

        //GooseAdapter오리도 데코레이터 사용
//        QuackableInterfaceForCompoundPattern gooseDuckDeco
//                = new QuackCountingDecorator(new GooseAdapter(new Goose()));



        System.out.println("\n오리 시뮬레이션 게임 (+ 추상 팩토리리)");

       simulate(mallardDuck); //1
        simulate(redheadDuck); //2
        simulate(duckCall); //3
        simulate(rubberDuck); //4
        //거위 시뮬레이트트
       simulate(gooseDuck);
//       simulate(gooseDuckDeco);//5

        System.out.println("오리가 소리 낸 횟수:" +
                QuackCountingDecorator.getQuacks() + " 번");

    }
    //데코레이터도 QuackableInterfaceForCompoundPattern 인터페이스이다.
    void simulate(QuackableInterfaceForCompoundPattern duck){
        duck.quack();
    }


}

 

 

package com.example.designpatternstudy;

import com.example.designpatternstudy.compoundPattern.DuckSimulator;
import org.junit.jupiter.api.Test;

public class CompoundPatternTests {


    @Test
    public void duckSimulTest(){

//        DuckSimulator duckSimulator = new DuckSimulator();
//        duckSimulator.initDuckSimul();
// 이렇게 했더니 아래와 같은 경고 발생
//        Static member 'com.example.designpatternstudy.compoundPattern.DuckSimulator.initDuckSimul()' accessed via instance reference
//        Inspection info: Reports references to static methods and fields via a class instance rather than the class itself.
//        Even though referring to static members via instance variables is allowed by The Java Language Specification, this makes the code confusing as the reader may think that the result of the method depends on the instance.
//                The quick-fix replaces the instance variable with the class name.
//        Example:
//        String s1 = s.valueOf(0);
//        After the quick-fix is applied:
//        String s = String.valueOf(0);
//        클래스의 인스턴스를 통해 static 멤버(여기서는 initDuciSimul())에 접근하는 것도 가능하다. 그러나 이는 코드를 헷갈리게 만든다.
//        독자는 해당 메소드의 결과가 객체에 의존적이라고 생각할 수도 있다.
//        ==> 해당 멤버가 static이라면 객체 생성하여 접근할 필요 없이 그냥 클래스로 접근하라는 뜻.

        DuckSimulator.initDuckSimul();
        // 오리 시뮬레이션 게임
        //꽥꽥
        //결과
        //꽥꽥
        //꽉꽉
        //삑삑

        //어댑터 패턴 결과
        //오리 시뮬레이션 게임 (+ 거위 어댑터)
        //꽥꽥
        //꽥꽥
        //꽉꽉
        //삑삑
        //끽끽

        //데코레이터 패턴 결과
        //오리 시뮬레이션 게임 (+ 데코레이터)
        //꽥꽥
        //꽥꽥
        //꽉꽉
        //삑삑
        //끽끽
        //끽끽
        //오리가 소리 낸 횟수:5 번
        
        //팰토리 패턴 결과
        //오리 시뮬레이션 게임 (+ 추상 팩토리리)
        //꽥꽥
        //꽥꽥
        //꽉꽉
        //삑삑
        //끽끽
        //오리가 소리 낸 횟수:4 번


    }

}

가장 아래의 "팩토리 패턴 결과"가 이번 테스트의 결과이다.

duckFactory를 사용하여 데코레이터가 추가된 오리 객체를 만든 것은 네 번이므로 오리가 소리낸 횟수는 총 네 번이다.

Comments