CS_Step2 - 디자인 패턴

장선웅·2022년 7월 12일
0

디자인 패턴(Design Pattern)?


1. 디자인 패턴의 정의

  • 소프트웨어를 설계 도중에 특정 맥락에서 자주 발생하는 고질적인 문제들이 또 발생했을 때 재사용할 수 있는 해결책
  • 이미 잘 만들어져 있는 것을 다시 만들 필요가 없음
    • 여기서 패턴(Pattern)이란 각기 다른 소프트웨어 모듈이나 기능을 가진 다양한 응용 소프트웨어 시스템들을 개발할 때도 서로 간의 공통되는 설꼐 문제가 존재하며 이를 해결하는 방법에도 공통점이 있는데 이를 패턴이라 한다.

2. 디자인 패턴의 구조

  • 콘텍스트(Context) : 문제가 발생하는 여러 상황 즉, 패턴이 적용될 수 있는 상황을 의미한다. (경우에 따라서는 패턴이 유용하지 못한 상황을 의미하기도 한다.)
  • 문제(Problem) : 패턴이 적용되어 해결해야하는 여러 디자인 이슈를 의미한다.
  • 해결(Solution) : 문제를 해결하도록 설계를 구성하는 요소들과 그 요소들 사이의 관계, 책임, 협력 관계를 의미한다. (반드시 구체적일 필요는 없으며, 언어에 의존적이지 않고 다양한 상황에 적용할 수 있는 가이드북같은 것이다.)

3. GoF 디자인 패턴

  • GoF 디자인 패턴은 "Gang of Fout"라 불리우는 소프트웨어 개발 영역에서 디자인 패턴을 구체화하고 체계화한 4명의 개발자들의 이름을 따서 지어진 이름이다.
  • 23가지의 디자인 패턴을 정리하고 각각의 디자인 패턴을 3가지로 분류 하였다.

GoF 디자인 패턴 분류


  1. 생성(Creational) 패턴
    --> 객체 인스턴스를 생성하고 이와 관련된 패턴
    --> 객체의 생성가 조합을 캡슐화하여 객체들 사이의 연결을 끊어주어 새로운 객체가 생성되거나 변경 되어도 프로그램 구조에 영향을 받지 않도록 한다.
  1. 구조(Structural) 패턴
    --> 클래스나 객체를 조합해 더 큰 구조를 만드는 패턴
    --> 서로 다른 인터페이스를 단일 인터페이스로 묶어 제공하거나,객체들을 서로 묶어 새로운 기능을 제공한다.
  1. 행동(Behavioral) 패턴
    --> 객체나 클래스 사이의 알고리즘이나 책임 분배에 관련된 패턴
    --> 한 객체가 할 수 없는 일을 여러 개의 객체로 분배하여 객체 사이의 결합도를 최소화 하는 것에 중접을 둔다.

GoF 디자인 패턴 분류에 따른 23가지 패턴


생성(Creational) 패턴

  1. 싱글턴 패턴(Singleton Pattern) : 특정 클래스에 객체 인스턴스가 하나만 만들어지도록 해주는 패턴. 전역변수와 마찬가지로 객체 인스턴스를 어디서든 액세스 할 수 있게 만들어준다. 클래스 인스턴스를 하나만 만들고 그 인스턴스로의 전역 접근을 허용한다.
    • 장점 : 싱글턴 패턴으로 구현된 인스턴스는 '전역'이므로 다른 클래스의 인스턴스들이 데이터를 공유하는 것이 가능하다.
    • 단점 : 만약 승글턴 패턴으로 구현된 인스턴스가 너무 많은 작업을 해야한다면, 다른 클래스들 간의 결합도가 높아지게 되는데, 이때 개방-폐쇄 원칙[ O - (OCP : Open Closed Principle) ]에 위배된다. 또한 결합도가 높아지게 되면, 유지보수가 힘들고 테스트도 원활하게 진행할 수 없다.
  1. 추상 팩토리 패턴(Abstract Factory Pattern) : 객체가 생성되거나 구성, 표현하는 방식과 무관하게 시스템을 독립적으로 만들어주는 패턴. 한 객체를 생성하여 인터페이스를 구현한 뒤에 다른 객체들도 사용할 수 있게 해주며, 이 부분에 대해 다른 타 클래스에 구현된 인터페이스들로부터 지켜질 수 있도록 한다.
    • 장점 : 구체적인 클래스를 분리하여 가독성이 올라가며, 한 객체를 다른 객체로 쉽게 대체 가능하다.
    • 단점 : 다른 기능을 하는 객체를 생성하기가 어렵다.
  1. 팩토리 메소드 패턴(Factory Method Pattern) : 상위(부모) 클래스에서 구현되지 않은 기능을 하위(자식) 클래스에서 직접 생성하도록 하는 패턴. 하위 클래스가 어떤 객체를 생성할지를 결정하도록 한다. 생성할 객체의 타입을 할 수 없을때, 생성할 객체를 기술하는 책임을 서브 클래스에게 정의하고자할 때 사용한다. 객체 생성의 책임을 서브클래스에 전가하며 서브클래스에 대한 정보를 숨길 수 있다.
    • 장점 : 코드가 간결해지며, 기존 코드를 수정하지 않고 새로은 인스턴스를 다른 방법으로 생성하도록 확장 시킬 수 있다.
    • 단점 : 상위 클래스의 인터페이스를 반드시 상속하여 하위 객체를 생성해야하며, 새로운 객체가 생길 때 마다 클래스를 생성시켜야 해서 클래스가 많아진다.
  1. 프로토타입 패턴(ProtoType Pattern) : 기존에 사용하는 인스턴스를 새로운 인스턴스로 만드는 패턴. 코드가 실행되는 도중에 새로운 클래스를 추가하고 삭제할때, 동적으로 클래스에 따라 응용프로그램을 설정해야 할때 사용한다.
    • 장점 : 객체를 생성해주기 위한 별도의 객체 생성 클래스가 필요 없으며, 각 객체의 필요한 부분만 조합해서도 생성할 수 있다.
    • 단점 : 생성될 객체들의 자료형인 클래스들을 모두 clone() 메서드로 구현해야 한다.
  1. 빌더 패턴(Builder Pattern) : 복합 객체의 생성 과정과 표현 방법을 분리하는 패턴. 서로 다른 표현 결과를 만들 수 있게 해준다.
    • 장점 : 필요한 데이터만 설정할 수 있으며, 유연성과 불변성을 확보 할 수 있다. 또한 가독성이 높다.
    • 단점 : 처리해야할 변수가 많다면, 객체의 성능이 떨어질 수 있다.

구조(Structural) 패턴

  1. 어뎁터 패턴(Adapter Pattern) : 클래스의 인터페이스를 사용자가 기대하는 다른 인터페이스로 변환하는 패턴. 즉, 상속을 이용 호환성이 없는 인터페이스 때문에 함께 동작할 수 없는 클래스들이 함께 작동하도록 해준다.
    • 장점 : 상위 클래스가 하위 클래스의 서브 클래스이기 때문에 하위 클래스의 행동을 오버라이드 할 수 있으며 하위 클래스의 객체를 만들지 않아도 된다.
    • 단점 : 상속을 이용하기 때문에 상위 클래스 하나가 특정 하위 클래스에만 적용이 가능하다.
  1. 브리지 패턴(Bridge Pattern) : 객체의 확장성을 향상하기 위한 패턴. 객체에서 동작을 처리하는 구현부와 확장을 위한 추상부를 분리한다.
    • 장점 : 상속을 통해 객체의 계층을 분리하고 기능을 확장한다.
    • 단점 : 강력한 결합 관계를 갖게되며, 이로 인해 불필요한 메서드도 상속에 같이 포함된다.
  1. 컴포지트 패턴(Composite Pattern) : 객체들 간의 계급 및 계층구조가 있고 이를 표현할 수 있도록 하는 패턴. 단일 객체와 집합 객체를 구분하지 않고 동일한 형태로 나타낼 때 사용한다.
    • 장점 : 객체들이 모두 같은 타입으로 취급되기 때문에 새로운 클래스를 추가하기 편하다.
    • 단점 : 설계가 지나치게 범용성을 갖게되어 많은 객에 제한을 가하기 힘들다.
  1. 데코레이터 패턴(Decorator Pattern) : 객체가 상황에 따라 다양한 메서드를 갖게되거나 삭제될 때 사용되는 패턴.
    • 장점 : 객체에 동적으로 기능을 추가할때 용이하다.
    • 단점 : 자잘한 클래스들이 계속 추가되어 클래스가 많아질 수 있으며, 이로 인해 객체의 정체를 알아보기 힘들어 가독성이 떨어질 수 있다.
  1. 퍼사트 패턴(Facade Pattern) : 복잡한 소프트웨어 라이브러리/API( = 서브시스템)에 대해 간단한 인터페이스를 제공할 때 사용하는 패턴. 서브시스템들 사이에 의존관계가 많을 때 사용한다.
    • 장점 : 서브시스템간의 결합도를 낮출 수 있으며, 간결하게 코드를 알아볼 수 있게 해준다.
    • 단점 : 사용자가 서브시스템 내부의 클래스를 직접 사용하는 것을 막을 수 없다.
  1. 플라이급 패턴(Flyweught Pattern) : 어플리캐이션에 의해 성성되는 객체 수가 많을 때 사용하는 패턴. 생성될 객체의 외적 특성이 사용자 프로그램으로부터 정의되어야 할 때 사용한다.
    • 장점 : 많은 객체를 생성할 때, 성능을 향상시키며, 메모리를 줄일 수 있다.
    • 단점 : 특정 인스턴스의 공유 컴포넌트를 다르게 행동하게 하는 것이 불가능하다.
  1. 프록시 패턴(Proxy Pattern) : 기본 객체에 리소스가 몰려있을 때 사용하는 패턴.
    • 장점 : 기본 객체의 리소스가 무거을 경우 프록시 객체에서 간단한 처리가 용이하며, 기본 객체에 대한 수정 없이 기본 객체 사이의 알고리즘을 프록시 객체를 통해 넣을 수 있다.
    • 단점 : 프록시 객체가 중간에 껴있기 때문에 처리 속도가 느려질 수 있다.

행동(Behavioral) 패턴

  1. 템플릿 메소드 패턴(Template Method Pattern) : 특정 작업을 처리하는 일부분을 서브 클래스로 캡슐화하여 전체적인 구조는 바꾸지 않으면서 특정 단계에서 수행하는 내용을 바꾸는 패턴. 2개 이상의 객체가 기본적으로 동일한 골격 하에 동작할때 사용한다.
    • 장점 : 중복되는 코드를 줄이고, 자식 클래스의 역할을 줄여 핵심 클래스의 관리가 용이하다.
    • 단점 : 추상 메서드가 많아지며 이로 인해 클래스 관리가 복잡해진다. 그로 인해 클래스간의 관계와 코드가 꼬여버릴 위험성이 높아진다.
  1. 반복자 패턴(iterator pattern) : 내부 표현부를 노출하지 않고 어떤 객체 집합에 속한 원소들을 순차적으로 접근할 수 있는 방법을 제공하는 패턴. 유저의 코드가 다른 데이터 구조를 순회하기를 원하거나 이러한 구조의 유형을 미리 알 수 없는 경우 사용한다.
    • 장점 : 크기가 큰 순회 알고리즘을 별도의 클래스로 추출하며[ S - (SRP : Single Responsibility Principle) ], 새로운 유형의 반복자를 구현하여 기존의 코드는 수정하지 않는다.[ O - (OCP : Open Closed Principle) ]
    • 단점 : 간단한 작업을 할 때는 오히려 지나칠 수 있다.
  1. 스트래티지 패턴(Strategy Pattern) : 알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 하는 패턴. 알고리즘을 사용하는 사용자하고는 독립적으로 알고리즘을 변경한다.
    • 장점 : 기존 코드를 변경하지 않아도 새로운 메서드를 추가할 수 있다.
    • 단점 : 알고리즘이 적을 때 코드가 길어진다.
  1. 비지터 패턴(Visitor Pattern) : 자료 구조(데이터)와 이를 처리하는 알고리즘을 분리해야 할때 사용하는 패턴. 알고리즘이 자주 바뀔 때 사용한다.
    • 장점 : 객체와 객체가 해야하는 알고리즘을 분리할 수 있다.
    • 단점 : 객체가 추가될때마다 알고리즘 클래스가 늘어나며, 같은 알고리즘을 갖고 있는 객체간의 결합도가 높아진다.
  1. 책임 연쇄 패턴(Chain of Responsibility Pattern) : 요청의 발신자와 수신자를 분리하거나, 요청을 처리할 수 있는 객체가 여러개이고 그 중 하나에 요청을 보내주는 패턴. 코드에서 처리 객체를 명시적으로 지정하고 싶지 않을때 사용한다.
    • 장점 : 발신자와 수신자의 결합도를 낮추며, 사용자가 처리 객체의 내부의 구조를 알 필요가 없다. 새로운 요청에 대한 처리 객체 생성이 매우 편리하다.
    • 단점 : 집합 내부에서 사이클이 발생할 수 있고, 이에 대한 디버깅 및 테스트가 어렵다.
  1. 미디에이터 패턴(Mediator Pattern) : 명령 객체와 일련의 처리 객체들을 포함하는 패턴. 모든 클래스간의 복잡한 관게를 캡슐화하여 하나의 클래스에 담아 처리하거나, 서로 전혀 다른 객체간의 상호 통신이 매우 복잡할 때 사용한다.
    • 장점 : 객체간의 통신을 위해 서로 직접 참조할 필요가 없다.
    • 단점 : 객체간 통신 알고리즘이 복잡해지거나 객체의 형태가 자주 변경되는 경우 유지보수와 관리가 어렵다.
  1. 메멘토 패턴(Memento Pattern) : 객체의 상태 정보를 가지는 클래스를 따로 생성하여, 객체의 상태를 저장하거나 이전 상태로 복원할 수 있게 해주는 패턴. 객체를 이전의 상태로 복구시켜야 하는 경우가 존재할때 사용한다.
    • 장점 : 저장된 상태를 핵심 객체와는 다른 별도의 객체에 보관하기 때문에 안전하고, 복구 기능을 구현하기 쉽다.
    • 단점 : 상태를 저장하고 복구하는 데에 시간이 오래 걸릴 수 있다.
  1. 이터레이터 패턴(Iterator Pattern) : 자료 구조를 분리시켜서 객체화 하는 패턴. 객체 내부 표현 방식을 모르고도 집합체의 요소에 접은하고 싶을 때 사용한다.
    • 장점 : 집합체의 다양한 순회 방법을 제공할 수 있으며, 집합체에 따라 다양한 순회 방법을 제공한다.
    • 단점 : 캡슐화 조건에 위배될 수 있으며, 사용자가 직접 반복자를 삭제해야한다.
  1. 커맨드 패턴(Command Pattern) : 실행될 기능을 캡슐화함으로써 주어진 여러 기능을 실행하는 재사용성이 높은 클래스를 설계하는 패턴. 이벤트가 발생했을 때, 실행될 기능이 다양하고, 변경될 상황이 많은 경우 사용한다.
    • 장점 : 작업을 수행하는 객체와 작업을 요청하는 객체가 분리되며[ S - (SRP : Single Responsibility Principle) ], 기존 코드를 수정할 필요없이 새로운 리시버와 새로운 커맨드 추가가 가능하다.[ O - (OCP : Open Closed Principle) ]
    • 단점 : 전체적으로 이해가 필요하며 설계구조가 복잡하다.
  1. 스테이트 패턴(State Pattern) : 객체가 자신의 상태를 직접 체크하여 상태에 따라 알고리즘의 호출 여부를 결정하며, 각 상태를 객체화 하는 패턴. 상태에 따른 조건문이 많아질 때 사용한다.
    • 장점 : 가독성이 좋아지며, 상태와 행위를 행동 구현과 분리한다.
    • 단점 : 상태 종류가 많아지면 코드가 늘어나며, 상태와 행동에 강력한 결합이 생긴다.
  1. 옵저버 패턴(Observer Pattern) : 한 객체의 상태 변화에 따라 다른 객체의 상태도 변화하도록 하는 패턴. 한 객체의 상태 변화를 자동으로 알아야 할때 사용한다.
    • 장점 : 실시간으로 한 객체의 변화를 다른 객체에게 전달할 수 있으며, 낮은 결합성으로 시스템이 유연하고 객체간의 의존성을 낮출 수 있다.
    • 단점 : 너무 많이 사용된다면 객체의 상태에 대해 관리가 힘들고, 객체에 대한 데이터 배분에 문제가 생기면 큰 문제로 이어질 수 있다.
profile
개발을 꿈꾸는 초짜

0개의 댓글