[GOF 디자인 패턴] 구조 패턴 - 어댑터, 브릿지, 컴포지트 패턴

600g (Kim Dong Geun)·2021년 6월 5일
0

구조패턴

  • 더 큰 구조를 형성하기 위해 어떻게 클래스객체 를 합성하는가와 관련된 패턴.
  • 상속 기법을 이용하여 인터페이스나 구현을 복합

구조 패턴의 종류

  • 어댑터 패턴
  • 브릿지 패턴
  • 복합체 패턴
  • 플라이급 패턴
  • 데코레이터 패턴

적응자 패턴 (Adapter Pattern)

  • 의도 : 클래스의 인터페이스를 사용자가 기대하는 인터페이스 형태로 적응 시킵니다. 서로 일치하지 않는 인터페이스를 갖는 클래스들을 함께 동작시킵니다.

동기

  • 재사용을 목표로 개발한 툴킷도 실제 재 사용성 을 발휘하지 못할 때가 발생.
    • 응용프로그램이 요청하는 인터페이스와 툴킷에 정의된 인터페이스가 일치하지 않을 때가 있기 때문

예시

  • 기존의 툴킷은 Shape 라는 클래스에 맞게끔 DrawingEditor 가 지원을 하고 있음.
  • 다른 개발자가 TextView 라는 클래스를 제작했을때, TextShape 는 이 둘의 기능을 모두 지원받고 싶음
  • TextShapeDrawingEditor ,TextView에 대해 지원을 할 수 있게끔 하는 방법이 2가지가 있음
    1. Shape의 인터페이스와 TextView의 구현을 모두 상속 받는다.
    2. TextView의 인스턴스에 TextShape에 포함시키고, TextView의 인터페이스를 사용하여 TextShape 을 구현
  • 이 두가지 방법 모두 Adapter 패턴에 해당되고 전자를 클래스 버전 후자를 객체 버전이라고 할 수 있음.
  • 이때 TextShape 을 적응자라고 할 수 있음.

활용성

  • 기존 클래스를 사용하고 싶은데 인터페이스가 맞지 않을 때
  • 아직 예측하지 못한 클래스나 실제 관련되지 않는 클래스들이 기존 클래스를 재사용하고자 하지만, 이미 정의된 재사용 가능한 클래스가 지금 요청하는 인터페이스를 꼭 정의하고 있지 않을때, 다시말해 이미 만든 것을 재사용하고자 하나 이 재사용 가능한 라이브러리를 수정할 수 없을 때
  • 이미 존재하는 여러개의 서브클래스를 사용해야 하는데 이 서브클래스들의 상속을 통해서 이들의 언터페이스를 다 개조한다는 것이 현실성이 없을 때, 객체 적응자를 써서 부모 클래스의 인터페이스를 변형하는 것이 더 바람직함.

구조

  • Target : 사용자가 사용할 응용 분야에 종속적인 인터페이스를 정의하는 클래스 (Shape)
  • Client : Target 인터페이스를 만족하는 객체와 동작할 대상 (DrawingEditor)
  • Adaptee : 인터페이스의 적응이 필요한 기존 인터페이스를 정의하는 클래스로서, 적응 대상자라고 함. (TextView)
  • Adapter : Target 인터페이스에 Adaptee 인터페이스를 적응시키는 클래스

협력방법

사용자는 적응자에 해당하는 클래스의 인스턴스에게 연산을 호출하고, 적응자에 해당하는 해당 요청을 수행하기 위해 적응 대상자의 연산을 호출한다.

결과

클래스 적응자와 객체 적응자는 각각 장단점이 있습니다.

  • 먼저 클래스 적응자를 살펴보자면, Adapter 클래스는 Adaptee 클래스를 Target 클래스로 변형하는데, 이를 위해서 Adaptee 클래스를 상속받아야 하기 때문에, 하나의 클래스와 이 클래스의 모든 서브클래스들을 개조할 때라면 클래스 적응자 방식을 사용할 수 없음

  • Adapter는 명시적으로 Adaptee를 상속받고 있을 뿐 Adaptee의 서브클래스들을 상속받는 것은 아니므로, Adaptee 의 서브클래스에 정의된 기능들을 사용할 수 없음.

  • Adapter 클래스는 Adaptee 클래스를 상속하기 때문에 Adaptee에 정의된 행동을 재정의할수도 있음.

  • 한 개의 객체만 사용하며, Adaptee로 가기 위한 추가적인 포인터 간접화는 필요하지 않음

객체 적응자를 사용하면 다음과 같은 특징을 경험 가능함.

  • Adapter 클래스는 하나만 존재해도 수많은 Adaptee 클래스들과 동작할 수 있습니다. 왜냐하면 Adapter 객체가 포함하는 Adaptee에 대한 참조자는 Adaptee 인스턴스를 관리할 수도 있고, Adaptee 클래스를 상속받는 다른 서브클래스들의 인터페이스를 관리할 수 있기 때문입니다.
  • 그러므로 하나의 Adapter 클래스로 모든 Adaptee 클래스와 이를 상속받는 서브클래스 모두를 이용할 수 있음.
  • Adapter 클래스의 행동을 재정의하기가 매우 어렵다. 이것을 위해서는 Adaptee 클래스를 상속받아서 새로운 서브 클래스를 만들고, Adapter 클래스가 아닌 Adaptee 클래스의 해당 서브클래스를 참조하도록 해야함.

고려사항

  1. Adapter 클래스가 실제 적응 작업을 위해 들어가는 작업량은 얼마나 되는가?

    • Target 인터페이스와 Adaptee 간에 얼마만큼의 유사성을 갖는가 하는 부분
  2. 대체가능 적응자

    • 클래스의 재사용성을 높이려면, 누가 이 클래스를 사용할지에 대한 생각을 최소화
    • 만약 인터페이스의 변경이 필요하다면 이 내용을 담은 클래스를 만들어 해결하면 되므로 하나의 클래스를 설계할때 모든 사용자가 원하는 표준화된 인터페이스를 정의해야 한다는 부담을 덜 수 있음
    • 다른 인터페이스를 원하는 사용자가 있다면, Adapter 클래스를 만들면됨. 또한 인터페이스 개조를 통해 내가 만들 클래스는 기존에 존재하는 시스템과 함께 연동할 수 있게 됨.
    • 기존 시스템은 내가 클래스를 만들기전에 개발된 것이고, 또 나와 무관하게 개발되어 내가 만든 인터페이스를 기대하고개발한 것이 아닐지라도 개조를 통해서 나와 연동이 가능해집니다. 이를 대체 가능 적응자(pluggable adapter) 라고 함.
  1. 양방향 적응자를 통한 투명성 제공
    • 적응자의 잠재적인 문제는 적응자가 모든 사용자에게 투명하지 않다는 점.
    • 적응된 객체는 Adaptee 인터페이스를 만족하기 보다는 Target의 인터페이스를 만족하기 때문이다.
      • 이게 무슨 말이지 //todo 궁금하네.
    • 그렇게 되면 Target이 필요한 사용자에게는 적응된 클래스라고 사용할 수 있겠지만, Target을 사용해야 하는 사용자라면 적응된 객체를 사용할 수 없음.
    • 따라서 양 방향 적응이 되려면 이런 상황 모두 지원해야 한다.

구현

  • Adapter 패턴의 구현은 매우간단하지만 몇 가지 염두에 두어야할 것이 있음

    • Adapter 클래스는 Target 클래스에서 public으로 상속받고, Adaptee 는 private로 상속 받음

      • Target에 정의된 인터페이스는 Adapter에서도 public으로 공개되지만, Adaptee는 내부 구현에 필요한 것이므로, Adaptee가 사용자에게 알려질 필요는 없음.
      • Adapter는 Target의 서브클래스이기는 하지만, Adaptee의 서브클래스는 아니다.
    • 대체 가능 적응자 : TreeDisplay 문제를 해결할 수 있는 적응자를 구현하는 방법은 총 3개가 됨.

      먼저 Adaptee에 정의된 인터페이스들 중 적응이 필요한 연산의 최소 집합을 만듬.

      이렇게 인터페이스를 축소하는 이유는 수십 개 연산을 갖는 인터페이스를 적응시키기 보다는 한 두개 인터페이스만을 적응시키는 것이 더 쉽기 때문.

      TreeDisplay를 위한 피적응자는 어떤 계층 구조도 될 수 있음.

      1. 추상 연산을 사용하는 방법
      2. 위임 객체를 사용하는 방법
      3. 매개변수화된 적응자를 사용하는 방법

예제코드 : 클래스로 구현한 Adapter

abstract class Shape{
  public Shape(){}
  
  abstract public void boundingBox(Point bottomLeft, Point topRight);
  abstract public Manipulator createManipulator();
}

abstract class TextView{
  public TextView(){}
  //시작점이 되는 x,y 좌표를 얻음
  void getOrigin(Coord x, Coord y){
    // 구현되어있겠지
  }
  
  //넓이와 길이를 가지는 상자를 구현할 수 있음.
  void getExtent(Coord witdh, Coord height){
    //뭐 구현되어있을꺼라 생각되는 부분.
  }
  
  abstract boolean isEmpty();
}

로 정의된 클래스가 있다고 침.

  • 일단, TextView 클래스에는 이 도형을 어떻게 이동시키는지에 대한 연산이 없음.

이를 TextView 클래스가 Shape가 원하는 형태의 연산 이름으로 서비스를 제공하고자 한다면 TextShape라는 클래스를 설계해서 해결할 수 있음.

  • 어라 근데 자바에서는 다중상속이 안되네? 그렇다면 Shape에 대한 연산을 interface 형태로 변경
  • 그리고 TextView에 대한 부분은 상속받은 객체에 대해서는 사용이 가능해야 되기 때문에 protected로 변경되어야함.
public interface Shape{
  public void boundingBox(Point bottomLeft, Point topRight);
  public void Manipulator createManipulator();
}

abstract class TextView{
  public TextView(){}
  //시작점이 되는 x,y 좌표를 얻음
  protected void getOrigin(Coord x, Coord y){
    // 구현되어있겠지
  }
  
  //넓이와 길이를 가지는 상자를 구현할 수 있음.
  protected void getExtent(Coord witdh, Coord height){
    //뭐 구현되어있을꺼라 생각되는 부분.
  }
  
  abstract public boolean isEmpty();
}
class TextShape extends TextView implements Shape{
  public TextShape(){};
  
  @Override
 	public void boundingBox(Point bottomLeft, Point topRight){
    Coordi bottom, left, width, height;
    getOrigin(bottom, left);
    getExtent(width, height);
    
    bottomLeft = Point(bottom, left);
    topRight = Point(bottom + height, left + width);
  }
  
  @Override
  public boolean isEmpty(){
    return super.isEmpty();
  }
  
  @Override
  public Manipulator createManipulator{
    return new TextManipulator(this);
  }
}
  • 즉 Shape가 필요로 하는 기능들을 TextShape 에서 재정의해서 DrawingEditor 에서 사용할 수 있는 상태가 됐다.
  • 또한 DrawingEditor(사용자)는 내부적으로 TextView가 어떻게 돌아가는지는 모른채 Shape에 대한 추상적인 인터페이스만을 사용해서 TextView의 기능을 온전히 사용할 수 있다.

예제코드 : 객체 적응자 방식

class TextShape implements Shape{
  
  private TextView textView;
  
  public TextShaple(){
    super();
  }
  
  @Override
  public void boundingBox(Point bottomLeft, Point topRight){
    Coord bottom, left, width, height;
    
    textView.getOrigin(bottom,left);
    textView.getExtent(width, height);
    bottomRight = Point(bottom,left);
    topRight = Point(bottom +height, left + width);
  }
  
  @Override
  public boolean isEmpty(){
    return textView.isEmpty();
  }
  
  @Override
  public Manipulator createManipulator{
    return new TextManipulator(this);
  }
}

관련패턴

  • 브릿지 패턴과의 차이
    1. 브릿지 패턴은 정의와 구현을 분리 하여 서로에게 영향을 주지않고 각각 확장
    2. 어댑터 패턴은 존재하는 객체의 인터페이스를 변경하여 사용하려는 것.
    3. 장식자 패턴은 다른 인터페이스의 변경없이도 객체에 새로운 행동을 추가할 수 있는 패턴, 이것은 응용프로그램을 위해 어댑터 패턴보다는 좋은 방식이고, 순수한 적응자로는 불가능한 재귀적 합성을 가능하게 함.
    4. 프록시 패턴은 다른 객체에 대한 대표자 또는 대리인의 역할을 수행하지만 인터페이스를 변경하려는 책임은 없음

브릿지 패턴

목적

구현에서 추상(정의)를 분리하여 이들이 독립적으로 다양성을 가질 수 있도록 함.

동기

  • 하나의 추상적 개념이 여러가지 구현으로 추상화 될 수 있을때 대부분 상속을 통해서 문제를 해결함.
  • 추상 클래스로 추상적 개념에 대한 인터페이스를 정의하고 구체적인 서브클래스들에서 서로 다른 방식으로 이들 인터페이스를 정의하는 방법으로는 충분한 융통성을 얻을 수 없음
  • 상속이라는 것 자체가 구현에 대해서 종속성을 영구적으로 종속시키기 때문에, 구현을 분리해서 재사용하거나 수정 확장하기가 용이 하지 않음.
  • 이런 문제를 해결하기 위해 브릿지 패턴 을 사용할 수 있음

활용성

  • 가교 패턴은 다음과 같은 경우에 사용합니다.
    • 추상적 개념과 이에 대한 구현 사이의 지속적인 종속 관계를 피하고 싶을때 이를테면, 런타임에 구현방법을 선택하거나 구현 내용을 변경하고 싶을때 여기에 해당합니다
    • 추상적 개념과 구현 모두가 독립적으로 서브클래싱을 통해 확장되어야 할때 이때 브릿지 패턴은 개발자가 구현을 또 다른 추상자 개념과 연결할 수 있게 할 뿐 아니라, 독립적으로 확장 가능하게 한다.
    • 추상적 개념에 대한 구현 내용을 변경하는 것이 다른 관련 프로그램에 아무런 영향을 주지 않아야 할 때, 즉 추상적 개념에 해당하는 클래스를 사용하는 코드들은 구현 클래스가 변경되었다고 해서 다시 컴파일 되어야 하면 안됨.
    • 구현을 완벽하게 은닉하길 원할때, 이 의미는 여러 입장에서 클래스에 정의된 속성이 private로 정의되어 있을 때 다른 부분 에서는 클래스의 private 영역으로의 접근이 불가능합니다. 그러나 가교패턴을 사용하지 않았을시 정의와 구현이 분리되지 않았기 때문에 어떤 연산을 사용하여 구현하였는지에 대해 다 알 수 있습니다.
    • 처음의 Motivation 다이어그램에서 본 것 처럼 클래스 계통에서 클래스 수가 급증하는 것처ㅏ럼 방지하고자 할때, 클래스 상속 계통에서 하나의 이유로 여러 개 클래스가 갑자기 정의되어야 하는 상황이라면 객체를 두 부분으로 분리할 필요가 있음을 보여줌
    • 여러 객체들에 걸쳐 구현을 공유하고자하며 이런 사실을 사용자 쪽에 공개하고싶지 않을때

구조

  • Abstraction : 추상적 개념에 대한 인터페이스를 제공하고 객체 구현자에 대한 참조자를 관리
  • RefinedAbstraction : 추상적 개념에 정의된 인터페이스를 확장
  • Implementor : 구현 클래스에 대한 인터페이스를 제공
  • ConcreteImplementor : Implementor 인터페이스를 구현하는 것으로 실제적인 구현 내용을 담음

결과

  • 인터페이스에 구현 분리

    구현이 인터페이스에 얽매이지 않게 됩니다. 추상적 개념에 대한 어떤 방식의 구현을 택할지가 런타임에 결정될 수 있음

    이는 런타임에 어떤 객체가 자신의 구현을 수시로 변경할 수 있음.

    AbstractionImplementor 의 분리는 컴타일 타임 의존성을 제거할 수 있음.

    구현을 변경하더라도 추상적 개념에 대한 클래스를 다시 컴파일할 필요가 없음.

  • 확장성 제고

    Abstraction과 Implementor를 독립적으로 확장할 수 있음.

  • 구현 세부 사항을 사용자에게서 숨기기

    상세한 구현 내용을 사용자에게 은닉할 수 있음.

예제코드

public interface Speaker{
  public void turnOn();
  public void turnDown();
  public void volumeUp();
  public void volumeDown();
}

public class SamsungSpeaker implements Speaker{
  @Override
  public void turnOn(){
    System.out.println("삼성 Speaker를 켭니다.");
  }
  
  @Override
  public void turnDown(){
    System.out.println("삼성 Speaker를 끕니다.");
  }
  
  @Override
  public void volumeUp(){
    System.out.println("volume을 키자");
  }
  
  @Override
  public void volumeDown(){
    System.out.println("volumne을 다운하자");
  }
}

public class LGSpeaker implements Speaker{
  @Override
  public void turnOn(){
    System.out.println("LG Speaker를 켭니다.");
  }
  
  @Override
  public void turnDown(){
    System.out.println("LG Speaker를 끕니다.");
  }
  
  @Override
  public void volumeUp(){
    System.out.println("volume을 키자");
  }
  
  @Override
  public void volumeDown(){
    System.out.println("volumne을 다운하자");
  }
}

관련패턴

  • 추상 팩토리 패턴을 이용해서 특정 가교를 생성하고 복합할 수 있도록 함.

복합체 패턴 (Composite 패턴)

목적

부분과 전체의 계층을 표현하기 위해 객체들을 모아 트리 구조로 구성, 사용자로 사여금 개별 객체와 복합 객체를 모두 동일하게 다룰수 있도록 하는 패턴

동기

  • 예를들어 그래픽 편집기나 파워포인트를 보면 텍스트, 그림등 여러 컴포넌트가 있음
  • 그래픽 편집기등은 텍스트, 그림등에 대한 공통적인 기능들을 동일하게 제공하고 있음
  • 그래픽 편집기는 텍스트, 그림등 여러 공통적인 객체들을 묶는 그룹 기능도 제공
  • 이 그룹 객체도 동일한 컴포넌트로써 공통적인 기능을 그래픽 편집기는 제공해야함
  • 이럴때 사용하기 편한게 Composite 패턴이다.

활용성

  • 부분-전체의 객체 계통을 표현하고 싶을 때
  • 사용자가 객체의 합성으로 생긴 복합 객체와 개개의 객체 사이의 차이를 알지 않고도 공통된 기능을 할 수 있도록 만들고 싶을때

구조

  • Component(Graphic) : 집합관계에 정의된 모든 객체에 대한 모든 인터페이스를 정의. 모든 클래스에 해당하는 안터페이스에 대해서 공통의 행동을 구현. 순환 구조에서 요소들을 포함하는 전체 클래스로 접근하는데 필요한 인터페이스를 정의
  • Leaf(Retangle, Line, Text, 기타) : 말단의 객체, 자식이 없는 객체. 객체 합성에 가장 기본이 되는 객체의 행동을 정의
  • Composite(Picture) : 자식이 있는 구성요소에 대한 행동을 정의. 자신이 복합하는 요소들을 저장하면서 Component 인터페이스에 정의된 자식 관련 연산을 구현
  • Client : Component 인터페이스를 통해 복합 구조내의 객체들을 조작

결과

  • 기본 객체와 복합 객체로 구성된 하나의 일관된 클래스 계통을 정의
  • 사용자의 코드가 단순해짐
  • 새로운 종류의 구성요소를 쉽게 추가할 수 있음
  • 설계가 지나치게 범용성을 많이 가짐

구현

  • 포함 객체에 대한 명확한 참조자 : 자식 구성요소에서 부모를 가리키는 참조자를 관리하면 복합체 구조의 관리를 단순화할 수 있음. 부모에 대한 참조자는 구조를 거슬러 올라가거나 요소를 하나 삭제하는 과정을 단순화 시킴. 부모 객체에 대한 참조자는 주로 Component 클래스에 두는데, Leaf 클래스와 composite 클래스는 이 Component를 상속받고 있으므로 실제로 두 클래스 몯 이 참조자를 관리하는 셈.
  • 구성요소 공유 : 구성요소를 공유하는 일은 매우 유용한 일, 메모리 저장공간의 필요량을 줄일 수 있기 때문, 가능한 해결책 중 한가지는 자식이 여러부모를 가지는 것이지만, 이는 어떤 부모의 메소드를 사용해야할지 애매할 수 있음. 따라서 부모들을 다 저장하지 않고도 설계를 개선하는지에 대한 설명은 플라이급 패턴 에 대해서 알 수 있음
  • Component 인터페이스를 최대화 : 복합체 패턴의 주요 목표중 하나가 사용자가 어떤 Leaf나 Composite 클래스가 존재하는지 모르도록 하는것. 이런 목표를 달성하려면 Component 클래스는 Composite와 Leaf에 정의된 모든 공통의 연산을 다 정의하고 있어야 됨.
  • 자식을 관리하는 연산 선언 : Composite 클래스가 Add()와 Remove() 연산을 통해서 자식들을 관리하기는 하지만, 복합체 패턴에서 매우 중요한 관심사는 Composite 클래스 계통 내의 어느 클래스에 이 연산을 선언하는것인가 결정하는 것
    • 이 결정은 안전성투명성 사이에 양자택일의 어려운 숙제를 냄.
    • 자식을 관리하는 인터페이스를 클래스 계통의 최상위 클래스에 정의하면, 서브클래스 모두에게 동일한 인터페이스가 유지되어 이를 사용하는 사용자에게 인터페이스의 투명성을 제공할 수 있음, 반대로 사용자가 Leaf 클래스의 인스턴스에게 Add()나 Remove연산을 호출하는 의미없는 행동을 하지 않도록 안정성 유지를 위한 비용을 지불
    • Composite 클래스에만 자식을 관리하는 연산을 정의한다면, 이를 사용하는 사용자는 아예 Leaf 클래스의 인스턴스에 이런 연산을 요청하지 않을 것이므로 안전성은 보장받습니다. 그러나 Leaf 클래스와 Composite 클래스가 서로 다른 인터페이스를 갖게 되므로 사용자는 이를 동일한 대상으로 간주할 수 없게 됩니다.
    • 그러나 우리는 이 패턴을 통해서 주로 투명성을 강조하고 싶음.
  • Component가 Component의 리스트를 구현할 수 있나?
    • 자식들 집합을 Component 클래스의 인스턴스 변수로 관리하고 싶은 유혹에 빠질수도 있음.
      • (Component 클래스에는 자식에 접근하고 관리하는 연산이 정의되있기 떄문에)
    • 그러나 최상위 클래스에 자식 포인터를 정의하는 것은 모든 Leaf클래스 인스터들도 이 집합을 관리하기 위한 메모리를 정의해야 하기 때문에 바람직하지 않음. 왜냐하면 Leaf 클래스의 인스터스는 자식클래스를 갖고 있지 않기 때문에..
  • 자식 사이의 순서 정하기
    • 자식 간의 순서가 의미있고 문제가 될때는 자식에게 접근, 관리하는 인터페이스를 설계시 자식들의 순서를 관리할 수 있도록 주의를 기울여야 함.
  • 성능 개선을 위한 캐싱
    • 복합 구조 내부를 수시로 순회하고 탐색해야 한다면, Composite 클래스는 자식을 순회하는 정보를 미리 담고 있을수도 있음.
    • 구성요소가 변경되면 부모가 캐싱하는 정보는 의미가 없어지기 때문에, 자신의 부모가 누구인지 아는 상황에서만 구현이 중요함.
    • 캐싱을 이용하려면, 현재 저장된 캐시의 내용이 유효한지 유효하지 않은지에 대해 확인하는 연산을 정의해야함
  • 누가 구성요소를 삭제하는 책임을 지냐?
    • 가비지 컬렉션의 기능을 제공하지 않는 언어에서는 자식이 없어질 때 Composite 클래스가 보통 그 삭제의 책임을 집니다.
  • 구성 요소를 저장하기 위해 가장 적당한 데이터 구조는?
    • 연결리스트, 배열, 트리, 해시 모두 선택할 수 있기때문에 어떤 데이터 구조를 선택해야하는지는 그 구조에 따라 다름.

예제 코드

public abstract class ComputerDevice {
  public abstract int getPrice();
  public abstract int getPower();
}



public class Keyboard extends ComputerDevice {
  private int price;
  private int power;
  public Keyboard(int power, int price) {
    this.power = power;
    this.price = price;
  }
  public int getPrice() { return price; }
  public int getPower() { return power; }
}
public class Body { 동일한 구조 }
public class Monitor { 동일한 구조 }

}

public class Computer extends ComputerDevice {
  // 복수 개의 ComputerDevice 객체를 가리킴
  private List<ComputerDevice> components = new ArrayList<ComputerDevice>();

  // ComputerDevice 객체를 Computer 클래스에 추가
  public addComponent(ComputerDevice component) { components.add(component); }
  // ComputerDevice 객체를 Computer 클래스에서 제거
  public removeComponent(ComputerDevice component) { components.remove(component); }

  // 전체 가격을 포함하는 각 부품의 가격을 합산
  public int getPrice() {
    int price = 0;
    for(ComputerDevice component : components) {
      price += component.getPrice();
    }
    return price;
  }
  // 전체 소비 전력량을 포함하는 각 부품의 소비 전력량을 합산
  public int getPower() {
    int power = 0;
    for(ComputerDevice component : components) {
      price += component.getPower();
    }
    return power;
  }
}



public class Client {
  public static void main(String[] args) {
    // 컴퓨터의 부품으로 Keyboard, Body, Monitor 객체를 생성
    Keyboard keyboard = new Keyboard(5, 2);
    Body body = new Body(100, 70);
    Monitor monitor = new Monitor(20, 30);

    // Computer 객체를 생성하고 addComponent()를 통해 부품 객체들을 설정
    Computer computer = new Computer();
    computer.addComponent(keyboard);
    computer.addComponent(body);
    computer.addComponent(monitor);

    // 컴퓨터의 가격과 전력 소비량을 구함
    int computerPrice = computer.getPrice();
    int computerPower = computer.getPower();
    System.out.println("Computer Price: " + computerPrice + "만원");
    System.out.println("Computer Power: " + computerPower + "W");
  }
}

https://gmlwjd9405.github.io/2018/08/10/composite-pattern.html
profile
수동적인 과신과 행운이 아닌, 능동적인 노력과 치열함

3개의 댓글

comment-user-thumbnail
2021년 6월 10일

스프링 환경에서 GoF 패턴을 활용하는 방법도 소개해주실 수 있나요? :0

1개의 답글