Skip to content

luvram/design-pattern-study

Repository files navigation

패턴이란?

특정 컨텍스트 내에서 주어진 문제에 대한 해결책이다. 즉, 어떤 컨텍스트 내에서 일련의 제약조건에 의해 영향을 받을 수 있는 문제에 봉착했다면, 그 제약조건 내에서 목적을 달성하기 위한 해결책을 찾아낼 수 있는 디자인을 적용하는것을 의미한다.

용어

  • 컨텍트스(context): 패턴이 적용되는 상황을 뜻한다. 반복적으로 일어날 수 있는 상황이어야만 한다.
  • 문제(problem): 컨텍스트 내에서 이루고자 하는 목적을 뜻한다. 하지만 컨텍스트 내에서 생길 수 있는 제약조건도 문제에 포함된다.
  • 해결책(solution): 우리가 찾아내야 하는 것. 누구든지 적용해서 일련의 제약조건 내에서 목적을 달성할 수 있는 일반적인 디자인을 뜻한다.

패턴을 분류하는 방법

범주별로 분류하였을 경우

  • 생성 관련 패턴 (Creational Pattern)

    객체 인스턴스 생성을 위한 패턴으로, 클라이언트와 그 클라이언트에서 생성해야 할 객체 인스턴스 사이의 연결을 끊어주는 패턴

      싱글턴, 빌더, 프로토타입, 추상 팩토리
    
  • 행동 관련 패턴 (Behavioral Pattern)

    클래스와 객체들이 상호작용하는 방법 및 역할을 분담하는 방법과 관련된 패턴이다.

      템플릿 메소드, 미디에이터, 비지터, 이터레이터, 커맨드, 메멘토, 인터프린터, 역할 변경, 옵저버, 스테이트, 스트래티지
    
  • 구조 관련 패턴 (Structural Pattern)

    클래스 및 객체들을 구성을 통해서 더 큰 구조로 만들 수 있게 해 주는 것과 관련된 패턴

      데코레이터, 컴포지트, 프록시, 플라이웨이트, 브리지, 어댑터
    

패턴 사용시 주의사항

최대한 단순하게 (KISS - Keep it Simple)

디자인을 할 때 최대한 단순한 방법으로 문제를 해결해야 한다는 것이다. 패턴을 사용하지 않더라도 단순하게 문제를 해결하기위한 방법이 있다면 단순한 방법을 사용하는것이 좋다.

디자인 패턴은 만병통치약이 아니다.

패턴은 반복적으로 발생하는 문제에 대한 일반적인 해결책일 뿐이다. 따라서 패턴을 사용할 때는 그 패턴을 사용했을 때 설계한 디자인의 다른 부분에 미칠 수 있는 영향과 결과에 대해 주의깊게 생각해야 한다.

패턴의 종류

스트래티지 (Strategy) 패턴

개념

일련의 알고리즘군을 정의하고 그 알고리즘들을 서로 바꿔가면서 쓸 수 있게 한다.

데코레이터 (Decorator) 패턴

개념

객체에 추가 요소를 동적으로 더할 수 있다. 데코레이터를 사용하면 서브 클래스를 만드는 경우에 비해 훨씬 유연하게 기능을 확장할 수 있다.

설명

  • 데코레이터 패턴에서는 구상 구성요소를 감싸주는 데코레이터들을 사용한다.
  • 데코레이터 클래스의 형식은 그 클래스가 감싸고 있는 클래스의 형식을 반영한다.
  • 데코레이터에서는 자기가 감싸고 있는 구성요소의 메소드를 호출한 결과에 새로운 기능을 더함으로서 행동을 확장한다.
  • 구성요소를 감싸는 데코레이터의 개수에는 제한이 없다.
  • 구성요소의 클라이언트 입장에서는 데코레이터의 존재를 알 수 없다. 클라이언트에서 구성요소의 구체적인 형식에 의존하게 되는 경우는 예외.

단점

  • 자잘한 클래스들이 엄청나게 추가되는 경우가 있다. 그러다 보면 남들이 봤을때 이해하기 힘든 디자인이 만들어질 수 있다.
  • 구성 요소를 초기화하는데 필요한 코드가 훨씬 복잡해진다. (팩토리와 빌더 패턴으로 해결 가능)

추가

  • 예제에서는 이해를 돕기 위해 abstract class를 사용하여 데코레이터 패턴을 구현했지만 가능하다면 interface를 사용하는것이 좋다.

팩토리 (Factory) 패턴

팩토리 메서드

개념

팩토리 메소드 패턴에서는 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만든다. 즉, 상속을 통해 패턴을 구현하며 클래스를 extends하고 팩토리 메소드를 오버라이드 하여 구현한다. 클라이언트 코드와 인스턴스를 만들어야 할 구상클래스를 분리시켜야 할 때 사용한다.

장점

  • 객체 생성을 처리하며, 이를 이용하면 객체를 생성하는 작업을 서브클래스에 캡슐화시킬 수 있다. 이렇게 하면 수퍼클래스에 있는 클라이언트 코드와 서브클래스에 있는 객체 생성 코드를 분리시킬 수 있다.
  • 객체 생성 코드를 전부 한 객체 또는 메소드에 집어넣으면 코드에서 중복되는 내용을 제거할 수 있고 나중에 관리할 때도 한 군데에만 신경을 쓰면 된다.

추상 팩토리

개념

추상 팩토리 패턴에서는 인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성할 수 있다. 즉, 객체 구성을 통해 구현한다. 연관된 클래스들의 제품군(family)를 묶어 생성하기 위해 사용한다.

싱글턴 (Singleton) 패턴

개념

인스턴스를 반드시 하나만 만들어야 하는 클래스를 생성하는 방법이다.

설명

  • 생성자를 private로 설정해두고 정적 메서드를 통해 생성된 인스턴스를 얻도록 구현할 수 있다.
  • 기본 구현(ClassicSingleton) 은 thread-safe 하지 않으므로 multi thread 작업시 별도의 처리를 해주어야 한다.
    • 속도가 그다지 중요하지 않다면 getInstance 메소드에 synchronized 키워드를 추가한다. (SynchronizedSingleton. 이 방법은 성능이 100배정도 저하될 수 있다.)
    • 애플리케이션에서 반드시 싱글톤의 인스턴스를 사용한다는 보장이 있다면 처음부터 싱글톤의 인스턴스를 만들어둔다 (EagerInitSingleton)
    • DCL(Double-Checking Locking) 방식을 사용하여 동기화 부분을 최소화 하여 구현한다 (DCLSingleton)

단점

  • 클래스 로더가 두 개 이상이라면 같은 클래스를 여러번( 각 클래스 로더마다 한번씩) 로딩할 수 있다. 만약 싱글턴을 그런식으로 로딩하면 그 클래스가 여러번 로딩되었기 때문에 싱글턴의 인스턴스가 여러 개 만들어지는 문제가 발생할 수 있다.
  • 생성자가 private으로 되어있어 상속이 어렵다.

커맨드 (Command) 패턴

개념

요구사항을 공통된 메서드로 접근할 수 있도록 캡슐화 하는 방법이다.

설명

  • 인보커에서는 요청을 할 때 커맨드 객체의 execute()메소드를 호출하고 커맨드에서는 리시버를 호출하는 방식으로 구현할 수 있다.
  • 요청을 하는 객체와 그 요청을 수행하는 객체를 분리시킬 수 있다.
  • 매크로 커맨드를 구현하여 여러개의 커맨드를 한번에 호출할 수 있도록 구성할수도 있다.
  • 로그 및 트랜잭션 시스템을 구현할때와 같은 상황에 이용할 수 있다.

용어

  • 리시버: 요구사항을 수행하기 위해 어떤 일을 처리해야 하는지 알고 있는 객체
  • 인보커: execute() 메소드를 호출함으로써 커맨드 객체에게 특정 작업을 수행해 달라는 요구를 하게됨
  • 커맨드: 리시버에 특정 작업을 처리하라는 지시를 전달함
  • 클라이언트: ConcreteCommand를 생성하고 리시버를 설정한다.

어댑터 (Adapter) 패턴

개념

한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환한다. 인터페이스를 변경하여 클라이언트에서 필요로 하는 인터페이스로 적응시키기 위한 용도로 사용된다.

설명

  • 어댑터를 이용하면 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스들을 연결해서 쓸 수 있다.
  • 클라이언트를 특정 구현이 아닌 인터페이스에 연결 시킨다.
  • 어댑터에는 두 종류가 있다.
    • 객체 어댑터: 객체 구성을 사용하여 구현한다.
    • 클래스 어댑터: 다중상속을 이용하여 구현하는 방식. 하지만 자바에서는 다중상속이 불가능하므로 사용할 수 없다.

용어

  • 타겟(target): 어댑터가 구현할 인터페이스
  • 어댑티(adaptee): 실제 동작될 클래스.

퍼사드 (Facade) 패턴

개념

어떤 서브시스템에 대한 간단한 인터페이스를 제공하기 위한 용도로 사용된다.

설명

  • 복잡한 시스템을 훨씬 쉽게 사용할 수 있다.
  • 한 어댑터에서 타겟 인터페이스를 구현하기 위해 두 개 이상의 어댑티를 감싸야 하는 상황도 생겼을때 사용할 수 있다.
  • 서브시스템 클래스들을 캡슐화하지 않는다. 그냥 서브시스템의 기능을 사용할 수 있는 간단한 인터페이스를 제공한다.
  • 클라이언트 구현과 서브시스템을 분리시킬 수 있다.

템플릿 메소드 (Template Method) 패턴

개념

메소드에서 알고리즘의 골격을 정의한다. 알고리즘의 여러단계 중 일부는 서브클래스에서 구현한다.

설명

  • 템플릿 메소드에서는 알고리즘의 각 단계들을 정의하며, 그 중 한 개 이상의 단계가 서브클래스에 의해 제공될 수 있다.
  • 여러 단계 가운데 하나 이상이 추상 메소드로 정의되며, 그 추상 메소드는 서브클래스에서 구현된다.
  • 서브클래스에서 재정의 하지 못하도록 강제하기 위해 final 키워드를 사용할 수 있다.
  • 후크(hook)는 서브클래스에서 필요에 따라 오버라이드 할 수 있는 메서드이다. 추상클래스에서 선언되는 메소드긴 하지만 기본적인 내용만 구현되어 있거나 아무 코드도 들어있지 않은 메소드이다. 이렇게 하면 서브클래스 입장에서는 다양한 위치에서 알고리즘에 끼어들 수 있다.

이터레이터 (Iterator) 패턴

개념

컬렉션 구현 방법을 노출시키지 않으면서도 그 집합체 안에 들어있는 모든 항목에 접근할 수 있게 해 주는 방법을 제공해준다.

설명

  • 컬렉션 입장에서는 그 안에 들어있는 모든 항목에 접근할 수 있게 하기 위해서 여러 메소드를 외부에 노출시키지 않으면서도, 컬렉션에 들어있는 모든 객체들에 접근할 수 있게 해준다.
  • 반복자를 구현한 코드를 컬렉션 밖으로 끄집어낼 수 있다.
  • 각 항목에 일일이 접근할 수 있게 해 주는 기능을 집합체가 아닌 반복자 객체에서 책임지게 되면서 집합체 인터페이스 및 구현이 간단해진다.
  • 집합체에 대한 반복작업을 별도의 객체로 캡슐화할 수 있다.
  • 다양한 집합체에 들어있는 객체에 대한 반복작업들에 대해 똑같은 인터페이스를 적용할 수 있기 때문에, 집합체에 있는 객체를 활용하는 코드를 만들 때 다형성을 활용할 수 있다.

컴포지트 (Composite) 패턴

개념

부분-전체 관계를 가진 객체 컬렉션이 있고, 그 객체들을 모두 똑같은 방식으로 다루고 싶을 때 쓰이는 패턴 객체들을 트리 구조로 구성한다.

설명

  • 클라이언트에서 개별 객체와 다른 객체들로 구성된 복합 객체(composite)를 똑같은 방법으로 다룰 수 있다.
  • 객체들을 트리 구조로 구성하여 부분과 전체를 나타내는 계층구조를 만들 수 있다.
  • 컴포지트 패턴은 단일 책임 원칙을 깨면서 대신에 투명성을 확보하기 위한 패턴이다.
    • 디자인 원칙에서 제시하는 가이드라인을 따르는 것이 좋긴 하지만, 항상 그 원칙이 우리가 생각하고 있는 디자인에 어떤 영향을 끼칠지는 생각해봐야 한다.
  • 컴포지트 패턴을 적용할 때는 상황에 따라 투명성과 안정성 사이에서 적절한 평형점을 찾아서 사용해야 한다.

스테이트 (State) 패턴

개념

내부 상태가 바뀜에 따라 객체의 행동이 바뀔 수 있도록 해준다.

설명

  • 객체 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있다. 마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있다.

프록시 (Proxy) 패턴

개념

어떤 객체에 대한 접근을 제어하기 위한 용도로 대리인이나 대변인에 해당하는 객체를 제공하는 패턴

설명

  • 클라이언트에서 실제 객체의 메소드를 호출하면, 그 호출을 중간에 가로챈다.
  • 프록시에서 접근을 제어하는 몇 가지 방법
    • 원격 프록시를 써서 원격 객체에 대한 접근을 제어할 수 있다.
    • 가상 프록시(virtual proxy)를 써서 생성하기 힘든 자원에 대한 접근을 제어할 수 있다.
    • 보호 프록시(protection proxy)를 써서 접근 권한이 필요한 자원에 대한 접근을 제어할 수 있다.
  • 원격프록시, 가상프록시, 보호프록시, 캐싱프록시, 동기화 프록시, 방화벽 프록시, 지연 복사 프록시 등의 다양한 프록시가 있다.

원격 프록시

  • 개념: 다른 JVM에 들어있는 객체의 대변인에 해당하는 로컬 객체다. 프록시의 메소드를 호출하면 그 호출이 네트워크를 통해서 전달되어 결국 원격 객체의 메소드가 호출된다. 그리고 그 결과는 다시 프록시를 거쳐 클라이언트한테 전달된다.
  • 용어
    • 원격 객체(remote object): 다른 자바 가상 머신의 힙에서 살고 있는 객체(다른 주소 공간에서 돌아가고 있는 객체)
    • 로컬 대변자(local representative): 로컬 대변자의 어떤 메소드를 호출하면 다른 원격 객체한테 그 메소드 호출을 전달해주는 역할을 맡고 있는 객체
    • RMI(Remote Method Invocation): 원격 JVM에 있는 객체를 찾아서 그 메소드를 호출한다.
    • 클라이언트 보조 객체(client helper): 원격의 객체에게 요청을 전달하기 위해 통신을 처리해주는 보조객체. RMI에서는 스터브(stub) 이라 한다.
    • 서비스 보조 객체(service helper): 클라이언트 보조 객체로부터 요청을 받아오고, 호출에 대한 정보를 해석해서 진짜 서비스 객체에 있는 진짜 메소드를 호출. 서비스 보조 객체는 서비스로부터 리턴값을 받아서 잘 포장해서 (Socket의 출력 스트림을 통해서) 클라이언트 보조 객체한테 전송한다. RMI에서는 스켈레톤(skeleton)이라 한다.
  • 원격 서비스 만들기
    1. 원격 인터페이스 만들기
      • 스터브와 실제 서비스에서 사용할 인터페이스를 구현한다.
      • java.rmi.Remote를 상속한다.
      • 모든 메소드를 RemoteException을 던지는 메소드로 선언한다
      • 인자와 리턴값은 반드시 원시형식(primitive),또는 Serializable 형식으로 선언한다.
    2. 서비스 구현 클래스 만들기
      • (1에서 만든)원격 인터페이스를 구현한다.
      • (간단한 방법으로 선택하자면) UnicastRemoteObject를 확장한다.
      • RemoteException을 선언하는 no-argument-constructor 생성하기
      • 서비스를 RMI 레지스트리에 등록
    3. rmic를 이용하여 스터브와 스켈레톤 만들기
      • 서비스를 구현한 클래스에 대해서 rmic를 돌린다.
    4. RMI 레지스트리(rmiregistry)를 실행
    5. 원격 서비스 시작

가상 프록시 (virtual proxy)

  • 개념: 생성하는 데 많은 비용이 드는 객체를 대신하는 역할을 맡는다. 실제로 진짜 객체가 필요하게 되기 전까지 객체의 생성을 미루게 해 주는 기능을 제공하기도 한다. 객체 생성 전, 또는 객체 생성 도중에 객체를 대신하기도 한다.

보호 프록시(protection proxy)

  • 개념: 호출하는 쪽의 권한에 따라 객체에 있는 메소드에 대한 접근을 제어한다.
  • 설명
    • 동적 프록시(dynamic proxy): 자바에 내장되어있는 프록시 기능이며 실제 프록시 클래스를 실행중에 생성한다.

컴파운드 (Compound) 패턴

개념

반복적으로 생길 수 있는 일반적인 문제를 해결하기 위한 용도로 두 개 이상의 패턴을 결합해서 사용하는것을 뜻한다.

객체지향 지식

최소 지식 원칙

  • 최소 지식 원칙에 따르면, 객체 사이의 상호작용은 될 수 있으면 아주 가까운 "친구" 사이에서만 허용하는것이 좋다.
  • 원칙을 지키기 위해 호출할 수 있는 4가지 메서드
    1. 객체 자체
    2. 메소드에 매개변수로 전달된 객체
    3. 그 메소드에서 생성하거나 인스턴스를 만든 객체
    4. 그 객체에 속하는 구성요소

장점

  • 이 원칙을 잘 따르면 객체들 사이의 의존성을 줄일 수 있고, 소프트웨어 관리가 더 용이해질 수도 있다.

단점

  • 다른 구성요소에 대한 메소드 호출을 처리하기 위해 "래퍼" 클래스를 더 만들어야 할 수도 있다. 그러다보면 시스템이 더 복잡해지고, 개발 시간도 늘어나고 성능이 떨어질 수도 있다.

헐리우드 원칙

  • 먼저 연락하지 말라.
  • 저수준 구성요소가 컴퓨테이션에 참여할 수는 있으면서도 저수준 구성요소와 고수준 계층 사이에 의존성을 만들어내지 않도록 프레임워크 또는 구성요소를 구축하기 위한 기법이다.
  • 의존성이 복잡하게 꼬여있는 것을 의존성 부패라 부르는데,의존성이 부패되면 시스템이 어떤식으로 디자인된 것인지 알아볼 수 없게 된다.
  • 헐리우드 원칙을 이용하면 의존성 부패를 방지할 수 있다.
  • 저수준 구성요소에서 시스템에 접송은 라 수는 있지만, 언제 어떤식으로 그 구성요소들을 사용할지는 고수준 구성요소에서 결정하게 된다.

의존성 역전의 원칙

  • 구상 클래스 사용을 줄이고 대신 추상화된 것을 사용해야 한다는 원칙

단일 책임 원칙

  • 클래스를 바꾸는 이유는 한 가지 뿐이여야 한다.
  • 응집도?
    • 한 클래스 또는 모듈이 특정 목적 또는 역할을 얼마나 일관되게 지원하는지를 나타내는 척도라고 할 수 있다.
    • 어떤 모듈 또는 클래스의 응집도가 높다는 것은 일련의 서로 연관된 기능이 묶여있다는 것을, 응집도가 낮다는 것은 서로 상관 없는 기능들이 묶여있다는 것을 뜻한다.

About

Study for design patterns

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages