본문 바로가기
소프트웨어공학/디자인 패턴

Composite Pattern - 구조, 개별 객체와 복합객체를 통합적으로 다루기, children의 저장 순서가 요구될 때 / ex) 메뉴 | Design Pattern 디자인 패턴

by javapp 자바앱 2021. 10. 14.
728x90

 

 

Composite Pattern

Treat individual objects and Composite of objects uniformly.

개별 객체와 복합 객체를 클라이언트에서 동일하게 사용

 

 

child parent에 대한 참조자를 가지면

- tree traversal이 용이해지며

- child 자신이 제거되기를 원하는 경우 편리함

 

children의 저장 순서가 요구되는 경우

 

 

 

 

Composite Object (복합 객체)

사용자 정의 클래스를 도메인으로 하는 속성을 가진 객체

 

 

복합 객체

시스템에서 기본으로 제공하지 않는 사용자 정의 클래스(user-defined class)를 도메인으로 하는 속성을 가진 객체를 복합 객체(composite object)라 한다. 복합 객체에서 사용자 정의 클래스를 도메인으

terms.naver.com

 

 

 

 

Composite Pattern 에서는 복합 객체를 이렇게 정의한다.

Compose objects into tree structures
to represent Part-Whole (or HAS -A) hierarchies.

Let clients treat individual objects & composite objects uniformly.

복합 객체

 

 

문제점)

* composite object 와 leaf object를 동일하게 취급하고 싶다.

 

aPicture.draw();           - composite object 의 draw

aLine.draw();               - leaf object 의 draw

 

 

 


 

 

 

Composite Pattern 구현 두 가지 방법

 

 

Composite Pattern 1

   leaf 와 composite object의 공통 연산을 가지는 interface 정의

 

Component 의 실제 타입은 Leaf or Composite

childLeaf of Composite

 

 

 

Component leaf 와 composite object 의 공통 인터페이스 선언

 

 

Component

interface Graphic{
	public void draw(); //공통 연산
}

 

Leaf

class Circle implements Graphic 
{
  //공통 연산
  public void draw() {

	//자신을 그리는 코드
	System.out.println("원");
  }
}

 

Composite Object : 구성(집합체) – 위임

class CompositeGraphics implements Graphic {

  	//Composite object HAS parts (or children).
   	//List : 제네릭 타입, 제네릭 인터페이스/ Graphic: 타입실인수
  	private List<Graphic> children = new ArrayList<Graphic>();
    
  	//공통 연산: 실제 작업은 각 child에게 위임
  	public void draw() {
		for (Graphic child : children) child.draw(); 
  	}

  	//child 조작 연산
  	public void add(Graphic child) { children.add(child); }
  	public void rem(Graphic child) { children.rem(child); }
}

 

 


 

 

Client 1-1 메인 실행

// Four leafs
Circle c1 = new Circle();
Line c2 = new Line();
Rec c3= new Rectangle();
Tri c4 = new Triangle();


// Three composite graphics
CompositeGraphic g, g1, g2;
g = new CompositeGraphic();
g1 = new CompositeGraphic();
g2 = new CompositeGraphic();
// Composes the graphics
g1.add(c1);
g1.add(c2);
g1.add(c3);


//g2-c4
g2.add(c4);


g.add(g1);
g.add(g2);

 

 

해당방법의 문제점)

DIP 설계원칙 위배 : Depend on abstractions, not on concretions.

선언타입은 상위 타입(interface, 추상class)으로 선언해야 된다.

 

 

 


 

 

Client 1-2 메인 실행

compositeleaf의 타입을 모두 Component 타입으로 선언할 경우

child 조작 연산 (add, remove) 실행 type casting”이 요구됨. (NOTE) 투명성 규칙에 위배됨

 

// Four leafs
Graphics c1, c2, c3, c4;//참조변수의 선언타입
c1 = new Circle(); //참조변수의 실제타입
c2 = new Line();
c3 = new Rectangle();
c4 = new Triangle();

// Three composite graphics
Graphics g, g1, g2;
g  = new CompositeGraphic(); 
g1 = new CompositeGraphic();
g2 = new CompositeGraphic();
// Composes the graphics
(CompositeGraphics)g1.add(c1);
(CompositeGraphics)g1.add(c2);
(CompositeGraphics)g1.add(c3);

(CompositeGraphics)g2.add(c4);

(CompositeGraphics)g.add(g1);
(CompositeGraphics)g.add(g2);

//타입변환(downcasting)을 해줘야 호출이된다.-> 구현 클래스를 알게 된다.
method() 가 object 객체의 interface에 포함되는지(호출가능한지)

 

Graphics c1, c2, c3, c4;//참조변수의 선언타입

Graphics g, g1, g2;

DIP 에 따라 상위 타입(interface)로 선언했지만

 

(CompositeGraphics)g1.add(c1);

(CompositeGraphics)g1.add(c2);

(CompositeGraphics)g1.add(c3);

 

(CompositeGraphics)g2.add(c4);

 

(CompositeGraphics)g.add(g1);

(CompositeGraphics)g.add(g2);

 

 

강제 타입 변환이 발생

타입변환(downcasting)을 해줘야 호출이된다.-> 구현 클래스를 알게 된다.

 

 

method() object 객체의 interface에 포함되는지(호출가능한지) 어떻게 알 수 있을까

선언타입(interface)으로 객체 생성 -> add 호출 불가 -> CompositeGraphics에 있기 때문에 타입 변환 필요

 

그렇게 되면 LSP 투명성 규칙에 위배

 

 

다형성 프로그래밍 클라이언트는 구현 클래스를 알 필요 없이 인터페이스를 사용할 수 있어야 한다.

LSP : 타입체킹 X (instanceof, downcasting)

 

 

 


 

 

Composite Pattern 2

 

공통 연산 + child 조작 연산을 가지는 interface 정의

 

 

Composite Pattern 2 Diagram

 


Component

The Component defines an interface for all objects in the Composition : 공통연산 and Child 조작 연산

// Component
// 호출가능한 메소드 집합
// the composite and the leaf nodes (공통 연산 and child 조작 연산)
public interface Graphic
{
    // 공통 연산
    public void draw();

    // the leaf nodes (child 조작 연산)
    public void add(Graphic child);
    public void remove(Graphic child);
}

 

 

Composite

The Composite also implements the Leaf-related operations

The Composite's role is to define behavior of the components having children and to store child components

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

// Composite Object : 구성(Aggregation) , 위임
public class CompositeGraphics implements Graphic
{
    // Composite object Has Parts or Children
    private List<Graphic> children = new ArrayList<>();

    // 공통연산
    @Override
    public void draw() {
        for(Graphic child : children){
            child.draw();
        }
    }

    @Override
    public void add(Graphic child) {
        children.add(child);
    }

    @Override
    public void remove(Graphic child) {
        children.remove(child);
    }
}

 

 

Leaf

A Leaf defines the behavior for the elements in the Composition.

It does this by implementing the operations the Composite supports

// Leaf
// individual objects as nodes
public class Circle implements Graphic
{
    @Override
    public void draw() {
        System.out.println("원");
    }

    // they don’t have to do anything,
    // they can just inherit the
    // default implementation.
    // Sometimes the best you can do is throw a runtime exception.
    @Override
    public void add(Graphic child) {throw new UnsupportedOperationException(); }
    @Override
    public void remove(Graphic child) {throw new UnsupportedOperationException(); }
}

public class Line implements Graphic {
    @Override
    public void draw() {
        System.out.println("라인");
    }

    @Override
    public void add(Graphic child) {throw new UnsupportedOperationException();}
    @Override
    public void remove(Graphic child) { throw new UnsupportedOperationException(); }
}

public class Rectangle implements Graphic {
    @Override
    public void draw() {
        System.out.println("직사각형");
    }

    @Override
    public void add(Graphic child) { throw new UnsupportedOperationException(); }
    @Override
    public void remove(Graphic child) { throw new UnsupportedOperationException(); }
}

public class Triangle implements Graphic {
    @Override
    public void draw() {
        System.out.println("트라이앵글");
    }

    @Override
    public void add(Graphic child) { throw new UnsupportedOperationException(); }
    @Override
    public void remove(Graphic child) { throw new UnsupportedOperationException(); }
}

 

Client 2

// Four leafs
Graphics c1, c2, c3, c4;//선언타입
c1 = new Circle(); // 실제타입
c2 = new Line();
c3 = new Rectangle();
c4 = new Triangle();

// Three composite graphics
Graphics g, g1, g2;
g  = new CompositeGraphic(); 
g1 = new CompositeGraphic();
g2 = new CompositeGraphic();
// Composes the graphics
g1.add(c1);
g1.add(c2);
g1.add(c3);

g2.add(c4);

g.add(g1);
g.add(g2);

Graphics 다이어그램

 

실행결과

 

지원하지 않는 leaf 의 연산에 대해 디버그시 예외 처리하게 된다.

 

LSP 해결 :

   subtype 추가 메소드를 상위타입에도 추가

   leaf에는 ReV: null false, throw new UnsupportedOperationException();

 

 

 

댓글