관리 메뉴

bright jazz music

Compound Pattern: 4. Composite 패턴 + Iterator패턴 본문

Design Pattern/Compound Pattern

Compound Pattern: 4. Composite 패턴 + Iterator패턴

bright jazz music 2022. 10. 31. 22:40

 

팩토리 패턴을 사용함으로써, 일일이 데코레이터로 객체를 감싸는 작업에서 벗어났다. 데코레이터로 감싸는 팩토리 클래스를 사용하면 데코레이터로 감싸진 오리 객체(QuackableInterfaceForCompoundPattern)이 생성되기 때문이다.

 

그러나 여전히 오리를 개별로 관리해 줘야 하는 불편이 존재한다. 오리를 종류별로 분류하여 관리할 수는 없을까? 이런 경우 Composite 패턴을 사용할 수 있다.

 

Composite패턴은 "객체를 원소로 가지는 컬렉션"을 개별 객체와 같은 방식으로 다루기 위해 사용하는 패턴이다. 

 

우선 컴포지트 패턴을 사용한 Flock 클래스부터 생성하자.

//Flock.java
//오리 무리를 만드는 클래스
//composite패턴 사용: 객체들로 구성된 컬렉션을 개별 객체와 같은 방식으로 다룰 수 있게 해준다.
package com.example.designpatternstudy.compoundPattern;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

//복합객체와 잎 원소에서 같은 인터페이스를 구현해야 한다.
//여기서는 QuackableInterfaceForCompoundPattern이 잎 원소가 된다.
public class Flock implements QuackableInterfaceForCompoundPattern {

    List<QuackableInterfaceForCompoundPattern> quackers
            = new ArrayList<QuackableInterfaceForCompoundPattern>();

    //Flock에 QuackableInterfaceForCompoundPattern(오리)을 추가하는 메서드
    public void add(QuackableInterfaceForCompoundPattern quacker){
        //List에 새로운 QuackableInterfaceForCompoundPattern(오리)추가
        quackers.add(quacker);
    }

    @Override
    public void quack() {
        //반복자(Iterator)패턴 사용
        //오리 리스트(quackers)를 이터레이터로 만든다.
        Iterator<QuackableInterfaceForCompoundPattern> iterator
                = quackers.iterator();

        while (iterator.hasNext()) {
            //이터레이터에 남은 원소가 없을 때까지 반복한다.
            QuackableInterfaceForCompoundPattern quacker = iterator.next();
            quacker.quack();
        }

    }
}

quck()메서드를 보면 이터레이터를 사용해서 리스트 안의 원소들이 저마다의 quck()을 수행할 수 있도록 하였다.

 

 

//DuckSimulator.java

package com.example.designpatternstudy.compoundPattern;

public class DuckSimulator {

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

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

    //파라미터 추가
    void simulateAll(AbstractDuckFactory duckFactory){
        QuackableInterfaceForCompoundPattern redheadDuck = duckFactory.createRedheadDuck();
        QuackableInterfaceForCompoundPattern duckCall = duckFactory.createDuckCall();
        QuackableInterfaceForCompoundPattern rubberDuck = duckFactory.createRubberDuck();
        //gooseDuck은 데코레이터로 감싸지 않았다. 단지 어댑터로 감쌌을 뿐.
        QuackableInterfaceForCompoundPattern gooseDuck = new GooseAdapter(new Goose());


        System.out.println("\n오리 시뮬레이션 게임: 무리 (+ 컴포지트)");

        Flock flockOfDucks = new Flock();
        flockOfDucks.add(redheadDuck);
        flockOfDucks.add(duckCall);
        flockOfDucks.add(rubberDuck);
        flockOfDucks.add(gooseDuck);


        //물오리만 들어가는 Flock객체(Composite객체) 생성
        Flock flockOfMallards = new Flock();

        //개별 물오리(mallard) 생성
        QuackableInterfaceForCompoundPattern mallardOne = duckFactory.createMallardDuck();
        QuackableInterfaceForCompoundPattern mallardTwo = duckFactory.createMallardDuck();
        QuackableInterfaceForCompoundPattern mallardThree = duckFactory.createMallardDuck();
        QuackableInterfaceForCompoundPattern mallardFour = duckFactory.createMallardDuck();

        //개별 물오리를 컴포지트 객체에 추가하기
        flockOfMallards.add(mallardOne);
        flockOfMallards.add(mallardTwo);
        flockOfMallards.add(mallardThree);
        flockOfMallards.add(mallardFour);

        //물오리 무리(flockOfMallards)를 아까 만든 오리 무리(flockOfDucks)에 넣는다.
        flockOfDucks.add(flockOfMallards);

        System.out.println("\n오리 시뮬레이션 게임: 전체 무리");
        simulate(flockOfDucks);

        System.out.println("\n오리 시뮬레이션 게임: 물오리 무리");
        simulate(flockOfMallards);

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

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


}

 

DuckSimulator의 구성은 아래와 같이 설명한다.

  • 팩토리 클래스를 사용하여 MallardDuck(물오리)을 네 마리 만든다. cf. mallardDuckOne, ... mallardDuckFour
  • 이 네 마리를 Flock 타입으로 생성한 flockOfMallards에 추가(flockOfMallard.add())한다.
  • 이로써 flockOfMallards는 네 마리의 MallardDuck을 가진  객체가 된다.
  • flockOfMallards를 다시 Flock타입으로 생성한 flockOfDucks에 추가(flockOfDucks.add())한다.
  • 이로써 flockOfDucks는 "네 마리의 MallardDuck을 보유한 객체인 flockOfMallard"를 보유한 객체가 된다.

 

 

테스트 해보자

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.compositPattern.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();
        
        
        //오리 시뮬레이션 게임: 무리 (+ 컴포지트)
	//
	//오리 시뮬레이션 게임: 전체 무리
	//꽥꽥
	//꽉꽉
	//삑삑
	//끽끽
	//꽥꽥
	//꽥꽥
	//꽥꽥
	//꽥꽥
	//
	//오리 시뮬레이션 게임: 물오리 무리
	//꽥꽥
	//꽥꽥
	//꽥꽥
	//꽥꽥
	//오리가 소리 낸 횟수:11 번


    }

}

"오리 시뮬레이션 게임: 전체무리"의 오리 소리는 flockOfDucks를 사용하며 다음의 순서와 같다.

1. readheadDuck : 꽥꽥

2. duckCall : 꽉꽉

3. rubberDuck : 삑삑

4. gooseDuck : 끽끽

5. mallardDuckOne : 꽥꽥

6. mallardDuckTwo : 꽥꽥

7. mallardDuckThree : 꽥꽥

8. mallardDuckFour : 꽥꽥

 

"오리 시뮬레이션 게임: 물오리 무리"의 오리 소리는 flockOfMallards를 사용하며 다음의 순서와 같다.

1. mallardDuckOne : 꽥꽥

2. mallardDuckTwo : 꽥꽥

3. mallardDuckThree : 꽥꽥

4. mallardDuckFour : 꽥꽥

 

총 울음소리는 8+4=12번이지만 집계는 11번이다. 이는 gooseDuck 때문이다. 이 녀석은 어댑터로 감싼 QuackabldInterfaceForCompoundPattern일 뿐 데코레이터로 감싸지는 않았다. 때문에 "끽끽'하고 소리를 냈지만 집계에 포함되지는 않았다. 

 

 

 

*

여기에서 컴포지트 패턴 사용 시에 고려해야 할 안정성과 투명성에 대해서 다루지는 않았다. 추후 추가 예정.

Comments