디자인 패턴(Design pattern) - 객체지향 개발시 발생되는 어떤 문제를 해결해줄까
Clean Software/Design Pattern

디자인 패턴(Design pattern) - 객체지향 개발시 발생되는 어떤 문제를 해결해줄까

728x90
반응형

디자인 패턴이 설계 문제를 해결하는 방법

디자인 패턴은 개발자(객체 지향 디자이너가) 직면하는 많은 일상적인 문제를 다양한 방식으로 해결해줍니다.
다음은 이러한 문제 중 몇 가지와 디자인 패턴이 이를 해결하는 방법입니다. 
 
 
1. 적절한 객체(Object)를 찾아줍니다.
 
객체 지향 프로그램은 객체(Object)로 구성됩니다. 객체는 데이터와 해당 데이터에서 작동하는 프로시저(procedure: 절차)를 모두 패키지화합니다. 프로지서는 일반적으로 메소드(method) 또는 함수(operations)이라고 합니다. 객체는 클라이언트로부터 요청(request)혹은 메시지(message)를 받으면 작업을 수행합니다.

요청은 작업을 실행할 개체를 가져오는 유일한 방법입니다.
작업은 개체의 내부 데이터를 변경하는 유일한 방법입니다.
이러한 제한으로 인해 개체의 내부 상태가 캡슐화되었다고 합니다. 직접 액세스할 수 없으며 해당 표현은 개체 외부에서 보이지 않습니다.
 

객체 지향 설계에서 어려운 부분은 시스템을 객체로 분해하는 것입니다.

캡슐화, 세분성, 종속성, 유연성, 성능, 진화, 재사용성 등 많은 요소가 작용하기 때문에 작업이 어렵습니다.
그것들은 종종 상충되는 방식으로 분해에 영향을 미칩니다.

객체 지향 설계 방법론은 다양한 접근 방식을 제시합니다.
문제 진술을 작성하고 명사와 동사를 골라내어 해당하는 클래스와 함수를 생성할 수 있습니다.
또는 시스템의 협업 및 책임에 집중할 수도 있습니다.
또는 실제 세계를 모델링하고 분석 중에 발견된 개체를 설계로 변환할 수 있습니다.
접근 방식이 최선인지에 대해서는 항상 이견이 있을 것입니다.

설계의 많은 객체는 해석 모델에서 가져옵니다. 그러나 객체 지향 설계는 종종 현실 세계에 대응하는 클래스가 없는 클래스로 끝납니다.
이들 중 일부는 배열과 같은 저수준 클래스입니다. 다른 것들은 훨씬 더 높은 수준입니다.
예를 들어, Composite 패턴은 물리적 대응물이 없는 객체를 균일하게 처리하기 위한 추상화를 도입합니다.
현실 세계에 대한 엄격한 모델링은 오늘날의 현실을 반영하지만 반드시 내일의 현실은 아닌 시스템으로 이어집니다.
디자인 중에 나타나는 추상화는 디자인을 유연하게 만드는 핵심입니다.

디자인 패턴은 덜 분명한 추상화와 이를 포착할 수 있는 객체를 식별하는 데 도움이 됩니다.
예를 들어, 프로세스나 알고리즘(process or algorithm)을 나타내는 객체는 자연에서 발생하지 않지만 유연한 디자인의 중요한 부분입니다.
Strategy 패턴은 교환 가능한 알고리즘 제품군을 구현하는 방법을 설명합니다.
State 패턴은 개체의 각 상태를 개체로 나타냅니다.


이러한 객체는 분석 중이나 설계 초기 단계에서 거의 발견되지 않습니다. 
설계을 보다 유연하고 재사용할 수 있도록 만드는 과정에서 나중에 발견됩니다.
 
 
2. 객체를 얼마나 세분화해야 하는지(Object granularity) 결정해줍니다.
객체에 의해 선언된 모든 함수는 함수의 이름, 매개 변수로 사용하는 객체 및 함수의 반환 값을 지정합니다.
이것은 함수의 특징(signature)들로 알려져 있습니다.
객체의 함수에 의해 정의된 모든 특징 집합을 객체에 대한 인터페이스라고 합니다.
객체의 인터페이스는 객체에 보낼 수 있는 전체 요청 집합을 특성화합니다.
객체 인터페이스의 특징과 일치하는 모든 요청은 객체로 보낼 수 있습니다.
 
유형(type)은 특정 인터페이스를 나타내는 데 사용되는 이름입니다. "Window"라는 인터페이스에 정의된 작업에 대한 모든 요청을 수락하는 경우 개체가 "Window" 유형을 갖는 것으로 설명합니다. 개체에는 여러 유형이 있을 수 있으며 매우 다양한 개체가 유형을 공유할 수 있습니다. 개체 인터페이스의 일부는 한 유형으로 특성화되고 다른 부분은 다른 유형으로 특성화될 수 있습니다. 같은 유형의 두 개체는 인터페이스의 일부만 공유하면 됩니다. 인터페이스는 다른 인터페이스를 하위 집합으로 포함할 수 있습니다. 인터페이스에 상위 유형의 인터페이스가 포함되어 있으면 유형이 다른 유형의 하위 유형이라고 합니다. 종종 우리는 상위 유형의 인터페이스를 상속하는 하위 유형에 대해 이야기합니다.
 
3. 객체의 인터페이스(Object Interface)를 지정해줍니다.
 
 
 
4. 객체 구현(Object Implementations)을 도와줍니다.
 
 
 
5. 재사용(Reuse) 메커니즘을 활용할 수 있도록 도와줍니다.
 
- Inheritance vs Composition
 
- Delegation(대표자)
 - Inheritance vs Parameterized Types(매개변수화된 유형)
 
 
 
6. Run-Time(런타임)구조와 Compile-Time(컴파일 타임)구조 차이로 인해 발생되는 문제를 해결해줍니다.
 
객체 지향 프로그램의 run-time 구조는 종종 code 구조와 거의 유사하지 않습니다.
code 구조는 compile-time에 고정됩니다. 그것은 고정 상속 관계의 클래스로 구성됩니다.
프로그램의 run-time  구조는 빠르게 변화하는 통신 객체들의 네트워크(networks of communicating objects)로 구성됩니다.
사실, 두 구조는 대체로 독립적입니다. 서로를 이해하려고 하는 것은 식물과 동물의 정적인 분류에서 생물 생태계의 역동성을 이해하려고 하는 것과 같으며 그 반대의 경우도 마찬가지입니다.
사실, 두 구조는 대체로 독립적입니다. 서로를 이해하려고 하는 것은 식물과 동물의 정적인 분류에서 생물 생태계의 역동성을 이해하려고 하는 것과 같으며 그 반대의 경우도 마찬가지입니다.
 
예 
개체 집계(aggregation)와 지인(acquaintance) 사이의 차이점과 컴파일 및 런타임에 이들이 얼마나 다르게 나타나는지 생각해봅시다.
 
aggregation은 한 객체가 다른 객체를 소유하거나 책임이 있음을 의미합니다. 일반적으로 우리는 객체가 다른 객체를 가지거나 일부가 되는 것에 대해 말합니다. aggregation는 aggregation 개체와 해당 소유자의 수명이 동일함을 의미합니다.
 
acquaintance 것은 대상이 다른 대상을 단순히 알고 있음을 의미합니다. 때때로 acquaintance을 "association" 또는 "using" 관계라고 합니다.
친한 객체는 서로의 작업을 요청할 수 있지만 서로에 대한 책임은 없습니다. acquaintance은 aggregation보다 약한 관계이며 객체 간의 훨씬 느슨한 결합을 나타냅니다. 
 
다이어그램에서 일반 화살촉 선은 acquaintance을 나타냅니다. 밑부분에 다이아몬드가 있는 화살촉 선은 aggregation를 나타냅니다.

 

aggregation와 지인은 동일한 방식으로 구현되는 경우가 많기 때문에 혼동하기 쉽습니다.
스몰토크에서 모든 변수는 다른 객체에 대한 참조입니다.
프로그래밍 언어에서는 집계와 지인 간의 구분이 없습니다.
C++에서 aggregation는 실제 인스턴스인 멤버 변수를 정의하여 구현할 수 있지만 인스턴스에 대한 포인터 또는 참조로 정의하는 것이 더 일반적입니다. 지인은 포인터와 참조로도 구현됩니다.

궁극적으로 지인과 집계는 명시적 언어 메커니즘보다 의도(intent)에 의해 더 많이 결정됩니다.
컴파일 타임 구조에서는 구별이 어려울 수 있지만 중요합니다.
aggregation 관계는 아는 사람보다 적고 영구적인 경향이 있습니다.
대조적으로, 지인은 더 자주 만들어지고 다시 만들어지며 때로는 수행 동안에만 존재합니다.
아는 사람도 더 역동적이어서 소스 코드에서 식별하기가 더 어렵습니다.

프로그램의 런타임 구조와 컴파일 시간 구조 사이에 이러한 차이가 있기 때문에 코드가 시스템 작동 방식에 대한 모든 것을 나타내지는 않을 것이 분명합니다.
시스템의 런타임 구조는 언어보다 설계자가 더 많이 부과해야 합니다.
개체와 해당 형식 간의 관계는 런타임 구조가 얼마나 좋은지 나쁜지를 결정하기 때문에 세심한 주의를 기울여 설계해야 합니다.

많은 디자인 패턴(특히 개체 범위가 있는 패턴)은 컴파일 시간과 런타임 구조를 명시적으로 구분합니다.
Composite(163) 및 Decorator(175)는 복잡한 런타임 구조를 구축하는 데 특히 유용합니다. 

Observer(293)는 패턴을 알지 못하면 이해하기 어려운 런타임 구조를 포함합니다.
Chain of Responsibility(223) 상속이 드러내지 않는 커뮤니케이션 패턴을 초래합니다.
 일반적으로 런타임 구조는 패턴을 이해할 때까지 코드에서 명확하지 않습니다.

 

 

 
 
7. 변화(Redesign)가 가능한 설계(design)를 해줍니다. 
 
 

(7-1 ) 재설계시 발생하는 대표적인 원인과 이를 해결하는 디자인 패턴

문제 1) 클래스를 명시적으로 지정하여 객체를 생성합니다 Creating an object by specifying a class explicitly

객체를 생성할 때 클래스 이름을 지정하면 특정 인터페이스 대신 특정 구현이 적용됩니다.

이러한 약속은 향후 변경 사항을 복잡하게 만들 수 있습니다. 이를 피하려면 간접적으로 객체를 생성하십시오.

 

디자인 패턴: Abstract Factory,  Factory Method, Prototype

 

문제 2) Dependence on specific operations 특정 작업에 대한 의존성

특정 작업을 지정할 때 요청을 충족하는 한 가지 방법을 커밋합니다.

하드 코딩된 요청을 피함으로써 컴파일 타임과 런타임 모두에서 요청이 충족되는 방식을 더 쉽게 변경할 수 있습니다.

디자인 패턴: Chain of Responsibility, Command

 

문제 3) Dependence on hardware and software platform 하드웨어 및 소프트웨어 플랫폼에 대한 의존성

외부 운영 체제 인터페이스와 API(응용 프로그래밍 인터페이스) 하드웨어  소프트웨어 플랫폼에 따라 다릅니다.

특정 플랫폼에 의존하는 소프트웨어는 다른 플랫폼으로 이식하기가  어려울 것입니다. 기본 플랫폼에서 최신 상태로 유지하는 것이 어려울 수도 있습니다.

따라서 플랫폼 종속성을 제한하도록 시스템을 설계하는 것이 중요합니다.

디자인 패턴: Abstract Factory, Bridge

 

문제 4) Dependence on object representations or implementations 객체 표현 또는 구현에 대한 의존성

개체가 어떻게 표시, 저장, 위치 지정 또는 구현되는지 알고 있는 클라이언트는 개체가 변경될 때 변경해야 할 수도 있습니다.

클라이언트에게 이 정보를 숨기면 변경 사항이 계단식으로 바뀌는 것을 방지할 수 있습니다.

 

디자인 패턴: Abstract Factory, Bridge, Memento, Proxy

 

문제 5) Algorithmic dependencies 알고리즘 종속성

알고리즘은 개발 및 재사용 중에 확장, 최적화 및 교체되는 경우가 많습니다. 알고리즘에 의존하는 객체는 알고리즘이 변경되면 변경되어야 합니다.

 

따라서 변경될 가능성이 있는 알고리즘은 격리되어야 합니다.

디자인 패턴: Builder, Iterator, Strategy, Template Method, Visitor

 

문제 6) Tight coupling 긴밀한 연결

밀접하게 결합된 클래스는 서로 의존하기 때문에 단독으로 재사용하기 어렵습니다. 긴밀한 결합은 다른 많은 클래스를 이해하고 변경하지 않고는 클래스를 변경하거나 제거할 수 없는 모놀리식 시스템으로 이어집니다. 시스템은 학습, 이식 및 유지 관리가 어려운 밀집된 덩어리가 됩니다.

느슨한 결합은 클래스가 자체적으로 재사용될 수 있고 시스템이 더 쉽게 학습, 이식, 수정 및 확장될 수 있는 가능성을 높입니다. 디자인 패턴은 추상 결합 및 계층화와 같은 기술을 사용하여 느슨하게 결합된 시스템을 촉진합니다.

디자인 패턴: Abstract Factory, Bridge, Chain of Responsibility, Command, Facade, Mediator, Observer

 

문제 7) Extending functionality by subclassing 서브클래싱을 통한 기능 확장

서브클래싱으로 개체를 사용자 정의하는 것은 종종 쉬운 일이 아닙니다. 모든 새 클래스에는 고정 구현 오버헤드(초기화, 종료 등)가 있습니다. 하위 클래스를 정의하려면 상위 클래스에 대한 심층적인 이해도 필요합니다. 예를 들어, 한 작업을 재정의하려면 다른 작업을 재정의해야 할 수 있습니다. 상속된 작업을 호출하려면 재정의된 작업이 필요할 수 있습니다. 그리고 서브클래싱은 클래스의 폭발로 이어질 수 있습니다. 간단한 확장을 위해 많은 새로운 서브클래스를 도입해야 할 수도 있기 때문입니다.

일반적으로 개체 구성과 특히 위임은 동작 결합을 위한 상속에 대한 유연한 대안을 제공합니다. 기존 클래스의 새 하위 클래스를 정의하는 대신 기존 개체를 새로운 방식으로 구성하여 응용 프로그램에 새 기능을 추가할 수 있습니다. 반면에 오브젝트 구성을 많이 사용하면 디자인을 이해하기가 더 어려워질 수 있습니다. 많은 디자인 패턴은 하나의 서브클래스를 정의하고 해당 인스턴스를 기존 서브클래스로 구성하여 사용자 정의 기능을 도입할 수 있는 디자인을 생성합니다.

디자인 패턴: Bridge, Chain of Responsibility, Composite, Decorator, Observer, Strategy

 

문제 8) Inability to alter classes conveniently 클래스를 편리하게 변경할 수 없음

가끔 수정이 불가능한 클래스를 편리하게 수정해야 할 때가 있습니다. 아마도 당신은 소스 코드가 필요하고 그것을 가지고 있지 않을 것입니다(상업 클래스 라이브러리의 경우처럼). 또는 변경하려면 기존 하위 클래스를 많이 수정해야 합니다. 디자인 패턴은 이러한 상황에서 클래스를 수정하는 방법을 제공합니다.

 

디자인 패턴: Adapter, Decorator, Visitor

 

 

(7- 2) 응용 프로그램, 툴킷, 프레임워크 세 가지 광범위한 소프트웨어 클래스의 개발에서 디자인 패턴이 수행하는 역할 

 

1) 응용 프로그램 (Application Programs)

 

2 )툴킷(Toolkits)

 

3) 프레임워크(Frameworks)

 

 

참고서적 GoF의 디자인 패턴

728x90
반응형