프로그래밍/Design Pattern

GoF의 디자인 패턴 - 구조 패턴

seungdols 2015. 10. 13. 13:26


* 디자인 패턴은 목적을 가장 중요시해야 한다.


 - 구조 패턴


구조(Structural) 패턴은 더큰 구조를 형성하기 위해서 어떻게 클래스와 객체를 합성하는지와 관련된 패턴입니다.


구조 클래스 패턴은 상속 기법을 이용하여 인터페이스나 구현을 복합 하고, 

적응자 클래스는 적응 대상 클래스에서 특성들을 상속 받아, 

적응 대상자에 정의 된 인터페이스를 마치 자신이 제공하는 것처럼 해준다. 

구조 객체 패턴은 인터페이스나 구현을 복합하는 것이 아니라 새로운 기능을 실현하기 위해 객체를 합성하는 방법을 제공한다.


복합체 패턴은 두 종류의 객체 대한 클래스로 클래스 계층 구조를 어떻게 형성하는지 보여준다. 

프록시 패턴에서 프록시는 다른 객체의 대리자 역할을  수행하며, 프록시의 기능은 다양하다. 

플라이급 패턴은 객체들을 공유할 수 있는 구조를 정의하며, 목적은 '적은 객체를 여러번 이용할 수 있도록 할 것인가'에 대한 고민의 패턴이다. 


퍼사드 패턴의 경우 '하나의 객체로 전체 서브 시스템을 표현할 수 있을까'를 고민한 패턴이다. 

가교 패턴은 객체의 개념적 추상화와 구현을 분리하여 각각 독립적으로 다양하게 변형할 수 있게 해준다.

장식자 패턴은 객체에 동적으로 새로운 책임을 추가할 수 있는 방법을 제공한다.






적응자(Adapter) 패턴

또는 Wrapper

 의도 


- 클래스의 인터페이스를 사용자가 시대하는 인터페이스 형태로 적응시킨다.(변환시킨다와 동일한 의미)

서로 일치 하지 않는 인터페이스를 갖는 클래스들을 함께 동작 할 수 있게 하는 목적.


활용 

* 이런 상황에서 사용하는 것이 좋다.

- 기존 클래스를 사용하고 싶은데 인터페이스가 맞지 않을 때

- 이미 만든 것을 재사용 하고자 하나 재사용 가능한 라이브러리를 수정 할 수 없을 때

- (Object adapter 해당사항) 이미 존재하는 여러 개의 서브클래스를 사용해야 하는데, 이 서브 클래스들의 상속을 통해서 이들의 인터페이스를 다 개조한다는 것은 현실적으로 불가능하는 때, 객체 적응자를 써 부모 클래스의 인터페이스를 변형하는 것이 더 바람직 하다.


결과


클래스 적응자 ( class adapter )

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

 - Adapter 클래스는 Adaptee 클래스를 상속하기에 adaptee에 정의된 Action을 Overriding 할 수 있다.

 - 한개의 객체만 사용하고, Adaptee로 가기 위해 기타의 포인터 간접화는 필요 없다.


객체 적응자 ( Obeject Adapter )


 -Adapter 클래스는 하나만 존재해도 수 많은 Adaptee 클래스들과 동작 할 수 있다.

 -Adaptee 클래스의 Action을 재정의하기가 매우 어렵다.



가교 ( Bridge ) 

의도 


- 구현에서 추상을 분리하여, 이들이 독립적으로 다양성을 가질 수 있도록 하는 패턴.


활용

* 이런 상황에서 사용하는 것이 좋다.

- 구상적 개념과 이에 대한 구현 사이의 지속적인 종속 관계를 피하고 싶을 때

- 추상적 개념과 구현 모두가 독립적으로 서브클래싱을 통해 확장되어야 할 때


결과 


- 인터페이스와 구현 분리

- 확장성 제고

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



복합체 ( Composite )

의도 


- 부분과 전체의 계층을 표현하기 위해 객체들을 모아 트리 구조로 구성하기 위한 패턴


활용

* 이런 상황에서 사용하는 것이 좋다.

- 부분-전체의 객체 계통을 표현하고 싶을 때

- 사용자가 객체의 합성으로 생긴 복합 객체와 개개의 객체 사이의 차이를 알지 않고도 

자기 일을 할 수 있도록 만들고 싶을 때

* 사용자는 복합 구조 ( composite structure )의 모든 객체를 똑같이 취급하게 된다.


결과 


- 기본 객체와 복합 객체로 구성된 하나의 일관 된 클래스 계통을 정의한다.

- 사용자 코드가 단순해진다.

- 새로운 종류의 구성요소를 쉽게 추가 할 수 있다.

- 단, 복합체의 구성요소에 제약을 가하기 힘들다.


관련 패턴 


- 구성 요소-부모간의 연결은 책임 연쇄 패턴에서 많이 사용 된다.

- 장식자 패턴은 자주 복합체 패턴과 함께 사용 된다.

- 플라이급 패턴으로 구성요소의 공유방법을 얻을 수 있다. 단, 공유되는 구성요소의 부모는 참조 할 수 없다.

- 반복자 패턴을 이용하면 구성요소를 순회하는 방법을 얻을 수 있다.

- 방문자 패턴을 이용하면, 이 패턴을 사용하지 않을 때 composite와 leaf 클래스에 걸쳐 분산될 수 있는 행동을 국소화시킬 수 있다.




장식자 ( Decorator )

다른 이름 Wrapper

의도 


- 객체에 동적으로 새로운 책임을 추가할 수 있게 한다. 

기능을 추가하려면, 서브클래스를 생성하는 것 보다 융통성 있는 방법을 제공한다.


동기 

- 전체 클래스에 새로운 기능을 추가할 필요는 없지만, 개별적인 객체에 새로운 책임을 추가할 필요가 있다.


활용

* 이런 상황에서 사용하는 것이 좋다.

- 동적으로 또한 투명하게, 다른 객체에 영향을 주지 않고, 개개의 객체에 새로운 책임을 추가하기 위해 사용한다.

- 제거될 수 있는 책임에 대해 사용한다.

- 실제 상속으로 서브클래스를 계속 만드는 방법이 실질적이지 못할 때 사용한다. 

( 상속으로 해결 할 경우 클래스 수가 폭발적으로 증가한다. )


결과 


1. 단순한 상속보다 설계의 융통성을 더 많이 증대시킬 수 있다.

2. 클래스 계통의 상부측 클래스에 많은 기능이 누적되는 상황을 피할 수 있다.

3. 장식자와 해당 그 장식자의 구성요소가 동일한 것은 아니다.

4. 장식자를 사용함으로 작은 규모의 객체들이 많이 생성 된다.


장식자 패턴은 객체의 외관을 변경하는 것이기에 구성요소는 장식자 패턴에 대해 알 필요가 전혀 없다.

그래서 장식자는 구성요소에 대해 투명하다.


관련 


적응자 패턴과 관련이 있으나, 적응자 패턴은 인터페이스를 변경해주는 목적이며, 장식자는 객체의 책임, 행동을 변화시킨다.




퍼사드 ( Facade )

의도 


- 한 서브시스템 내의 인터페이스 집합에 대한 획일화된 하나의 인터페이스를 제공하는 패턴.


동기 


- 시스템을 서브시스템으로 구조화하면 복잡성을 줄이는 데에 큰 도움을 준다.

- 공통적인 설계 목표는 서브시스템들 사이의 의사소통 및 종속성을 최소화하는 것인데, 

퍼사드 패턴은 그 목표를 달성하도록 돕는다.



활용 

* 이런 상황에서 사용하는 것이 좋다.

- 복잡한 서브 시스템에 대한 단순한 인터페이스 제공이 필요할 때

- 추상 개념에 대한 구현 클래스와 사용자 사이에 너무 많은 종속성이 존재 할 때

- 서브시스템을 계층화시킬 때


결과 


1. 서브시스템의 구성요소를 보호할 수 있다. 

그 결과로 다루어야 할 객체의 수가 줄고, 서브 시스템을 쉽게 사용할 수 있다.


2. 서브시스템과 사용자 코드 간의 결합도를 약하게 만든다.

단, 서브 시스템 내 정의된 요소들은 강하게 결합될 수 있다.


3. 응용프로그램에서 서브시스템 클래스를 사용하는 것을 완전히 막지는 않기에 Facade를 선택할지, 

서브시스템 클래스를 직접 사용할지 선택 할 수 있다.


퍼사드의 일반적인 주된 목표는 일반적인 사용을 위한 인터페이스를 간소화하는 것이다.


관련 


- 추상 팩토리 패턴은 서브시스템에 독립적인 방법으로 서브시스템 객체를 생성하는 인터페이스를 제공하기 위해 Facade와 함께 사용할 수 있다. 추상 팩토리는 Facade에 대한 대안으로 플랫폼에 종속적인 클래스를 감추는 데 사용한다. 


- 중재자 패턴도 기존에 존재하는 클래스의 기능성을 추상화 한다는 점에서 비슷하나, 

본래 목적은 여러 객체들의 사이의 협력 관계를 추상화하여 기능성의 집중화를 막는 것이다. 


중재자 패턴에 참여하는 객체는 서로를 직접 알지 못하고, 단지 중재자를 통해서만 상호작용한다. 

이에 반해 퍼사드는 서브시스템 인터페이스 자체를 추상화하여 사용을 쉽게 하려는 목적이 더 크다. 

결론적으로 퍼사드는 새로운 기능성을 추가 할 수도 없고, 새로운 추가 기능성에 대해 알 수도 없다. 


- 퍼사드 객체가 하나만 있어도 된다면, 단일체로 구현하기도 한다.



플라이급( Flyweight )

의도 


- 공유를 통해 많은 수의 소립 (fine-grained)객체들을 효과적으로 지원하기 위함이다.


동기 


- 객체 중심으로 설계함으로 많은 효과를 얻을 수는 있으나, 실제 구현에 있어서 비용이 만만치 않은 응용프로그램들이 있다.

- 예를 들어 문서편집기에서 글자들을 객체로 관리 한다고 생각하면, 많은 문자 객체들이 생성 된다. 

이 문제를 플라이급으로 해결 하면 이점을 얻을 수 있다. 이러한 문제를 객체 공유를 통해 해결한다.


본질적 상태 ( Intrinsic state )와 부가적 상태 ( Extrinsic state )

본질적 상태는 플라이급 객체에 저장되어야 하며, 이것이 적용되는 상황과 상관 없는 본질적 특성 정보들이 객체를 구성한다. 

부가적 상태는 플라이급 객체가 사용될 상황에 따라 달라질 수 있고, 상황에 종속적이다. 그리하여 공유 될 수 없다.


활용 

* 이런 상황에서 사용하는 것이 좋다.


- 응용프로그램이 대량의 객체를 사용해야 할 때

- 객체의 수가 너무 많아 저장 비용이 클 때

- 대부분의 객체 상태를 부가적인 것으로 만들 수 있을 때

- 부가적인 속성들을 제거 후 객체의 많은 묶음이 비교적 적은 수의 공유된 객체로 대체될 수 있을 때

- 응용 프로그램이 객체의 정체성에 의존하지 않을 때


결과 


- 공유해야 하는 인스턴스의 전체 수를 줄 일 수 있다.

- 객체별 본질적 상태의 양을 줄 일 수 있다. 

- 부가적인 상태는 연산되거나 저장될 수 있다.


* 더 많은 flyweight가 공유될 수록 저장소는 절약된다.

대부분의 본질적인 상태가 저장되고 부가적인 상태는 연산될 때라면,절약의 효과가 가장 크다.

이 때는 본질적 상태를 저장하는 비용이 줄어드는 대신 부가적 상태를 만들기 위한 연산의 시간을 투자해야 한다.


관련 


- 플라이급 패턴은 복합체 패턴과 함께 사용되는데, 공유되는 단말 노드를 갖는 

방향성 비순환 그래프 형태를 써 논리적으로 계층 구조를 구현하는 것이 여기에 해당된다. 

- 상태 패턴, 전략패턴을 플라이급 객체로 구현 할 수 있다.



프록시 ( Proxy ) 

의도 


- 다른 객체에 대한 접근을 제어하기 위한 대리자 또는 자리 채움자 역할을 하는 객체를 두기 위함이다.


동기 


- 어떤 객체데 대한 접근을 제어하는 한 가지 이유는 실제로 그 객체를 사용할 수 있을때까지 객체 생성과 초기화에 들어가는 비용과 시간을 물지 않겠다는 것이다. 


활용 

* 이런 상황에서 사용하는 것이 좋다.


1. 원격지 프록시는 서로 다은 주소 공간에 있는 객체를 가리키는 대표 객체로 로컨 환경에 위치한다. 


2. 가상 프록시는 요청이 있을때만 필요한 고비용 객체를 생성합니다. 


3. 보호용 프록시는 원래 객체에 대한 실제 접근을 제어한다. 

이는 객체별로 접근 제어 권한이 다를 때 유용하게 사용할 수 있다.


4. 스마트 참조자( smart reference )는 원시 포인터의 대체용 객체로 실제 객체제 접근이 일어날 때 추가적인 행동을 수행한다. 

보통 사용예는 다음과 같다.

- 실제 객체에 대한 참조 횟수를 저장하다가 더는 참조가 없을 때 해당 객체를 자동으로 없앤다. 

- 맨 처음 참조되는 시점에 영속적 저장소의 객체를 메모리로 옮긴다. 

- 실제 객체에 접근하기 전에, 다른 객체가 그것을 변경하지 못 하도록 실제 객체에 대해 잠금 (Lock)을 건다.


결과 


- 프록시 패턴은 어떤 객체에 접근할 때 추가적인 간접화 통로를 제공한다. 

이 통로는 프록시 종류에 따라 여러 쓰임새로 활용 된다. 


1. 원격지 프록시는 객체가 다른 주소 공간에 있다는 사실을 숨길 수 있다. 


2. 가상 프록시는 요구에 따라 객체를 생성하는 등의 처리를 최적화 할 수 있다.


3. 보호용 프록시 및 스마트 참조자는 객체가 접근할 때마다 추가 관리를 책임진다. 


추가적으로 또 다른 최적화 기능인데, 기록 시점 복사( copy on write )라는 것인데 이 개념은 요구가 들어올 때만 객체를 생성하는 개념과 관련이 있다. 


예로 덩치가 큰 복잡한 객체를 복사하려면 비용이 크다, 만약 사본이 변경 되지 않고, 원본과 똑같다면 굳이 이 비용을 지불할 필요가 없다. 프록시를 이용해 복사 절차를 Lazy(지연)하므로 사본이 수정 될 때만 실제 복사에 대한 Cost를 지불하도록 만드는 것이다.


기록시점 복사를 가능하게 하려면, 원본의 참조 카운트를 관리해야한다. 

프록시를 복사하는 연산은 이 원본에 대한 참조 카운트를 증가시키는 일 외에 다른 일은 하지 않는다. 

사용자가 원본을 수정하는 연산을 요청하면 , 프록시가 실제로 복사를 진행하여 사본이 별도로 값을 가지게 하고, 원본에 대한 참조자 수는 줄어들게 된다. 결국에는 참조 카운트가 0이면 대상을 삭제한다.


관련 


- 적응자는 자신이 개조할 객체가 정의된 인터페이스와 다른 인터페이스를 제공한다. 그에 반해 프록시는 자신이 상대하는 대상과 동일한 인터페이스를 제공한다. 


- 장식자는 프록시와 구현방법이 유사한데 장식자는 그 사용 목적이 하나 이상의 서비스를 추가하기 위한 것이고, 프록시는 객체에 대한 접근을 제어하는 목적이다.


물론 구현에서도 많은 차이가 존재한다. 보호성 프록시는 장식자 구현과 유사하나, 원격 프록시는 실제 처리 대상을 직접 참조하도록 관리하지 않고, 간접적 접근 방법을 관리한다. 이 간접적 접근은 호스트 식별자, 호스트 머신 내의 주소등을 포함하고, 가상 프록시는 파일 이름과 같은 간접적 참조자를 정의하나, 궁극적으로 직접적 참조자를 얻은 후 이를 사용하는 형태이다. 




적응자 패턴 vs 가교 패턴
 두 패턴의 가장 큰 차이점은 무엇일까?

적응자의 목적은 이미 존재하는 두 인터페이스 간의 불일치를 해결하려는 것이며, 어떤 인터페이스를 어떻게 구현하게 할 것인가? 인터페이스와 구현을 독립적으로 발전시키는 방법은 무엇일까? 등에 관해서는 전혀 고려하지 않는다. 그러나 가교패턴은 추상적 개념과 구현을 따로 만들고, 이를 연결시키려는 것이 주 목적이다. 
그리하여 시스템이 진화함에 따라서 새로운 구현을 추가할 수 있게 하려는 것에 초점을 맞춘다.

이런 차이로 인하여 적응자 패턴과 가교패턴이 사용되는 소프트웨어 개발 주기 시점이 달라진다. 
적응자는 주로 이미 개발이 완료되어 운영중인 두 클래스 사이에서 호환되지 않는 부분을 찾았을 때 적용하는 패턴이다. 즉, 두 클래스 간의 종속성을 미리 예측하지 못하고 개발했을 때 필요한 패턴이다. 
가교패턴은 이미 사용자가 추상적 개념을 구현하는 방법이 여러가지이고, 이를 각각 독립적으로 진화할 수 있음을 파악한 상태에서 적용하는 패턴이다. 
일반적으로 적응자패턴은 설계가 완료된 이후, 가교는 설계가 완료도기 이전에 적용된다.
또한 퍼사드가 적응자와 같아 보일 수 있으나 차이가 존재한다. 

적응자는 어찌하건 기존 인터페이스를 재사용하는 것이 목적이고, 퍼사드는 여러 객체를 모아서 새로운 인터페이스를 정의하는 것이 목적이므로 차이가 존재하는 것을 알 수 있다.

복합체 패턴 및 장식자 패턴은 구조가 유사하다. 두 패턴 모두 여러 객체를 조직화 하기위해 재귀적 합성기법을 사용했기 때문이다. 그래서 유사해보이지만, 그것은 구조에서만 나타나는 공통점이다. 
장식자 패턴은 상속 없이 객체에 새로운 서비스를 추가하는 것이 목적이다. 
복합체 패턴은 클래스 구조화에 관해 초점을 맞춘 것으로 어떻게 관련된 객체들을 하나의 인터페이스로 다룰 수 있도록 일관성을 부여할 것인가가 중요한 관건이다. 즉, 여러 객체들을 하나의 객체로 통일 하고자 함이다. 

장식자 패턴과 구조가 비슷한 또 다른 패턴은 바로 프록시 패턴이다. 두 패턴 모두 다른 객체에 간접적으로 접근할 수 있고, 프록시/장식자를 구현할 때 메시지를 전달할 상대 객체에 대한 참조자를 관리하는 공통점이 있지만, 두 패턴의 목적은 다르다.

장식자와 마찬가지로 프록시 또한 객체들을 합성하여 사용자에게는 동일한 인터페이스를 제공합니다. 그러나 프록시는 장식자와 달리 동적으로 어떤 기능성을 추가/제거 하지 않는다. 
프록시의 주된 목적은 서비스를 제공하는 대상에 대한 참조자를 직접 관리하는 불편함을 해결하려는 것이다.

프록시 패턴에서는 프록시가 대리한 주체가 중요한 실제 기능을 제공한다. 그 말은 즉, 프록시는 대리자에 대한 접근에 대해서만 관여한다고 볼 수 있다. 그러나 장식자는 기능적인 일도 담당 할 수 있다. 

즉, 유사해보이지만 목적과 의도가 각기 다르고, 그 다른 패턴들을 조합하면, 유용한 기능과 편리성을 안겨줄 수 있다는 점이 존재한다.




  •  무단 수정 및 배포는 금지합니다. 
  •  모든 내용은 본 블로그 운영자가 정리한 내용입니다. 
  •  참조한 정보에 대해서는 출처를 남기고 있습니다.
  •  틀린 내용 / 오류가 포함된 내용은 댓글로 남겨주세요.
  •  choiseungho0822@gmail.com 보내주셔도 됩니다.
  •  Seungdols Wiki 운영중입니다.


반응형