[CS] Design Pattern

Myung A Lee·2023년 8월 6일
0

CS

목록 보기
11/11

Design Pattern


Design Pattern 디자인 패턴

  • 소프트웨어 코드 작성 시에 생기는 공통적인 문제를 해결할 때 참조할 수 있는 코드 패턴으로 SW 재사용성, 호환성, 유지 보수성을 보장
  • 장점
    • 소프트웨어의 구조를 파악하기 용이
    • 재사용을 통한 개발 시간 단축
    • 설계 변경 시 유연성 있는 조치 가능
  • 단점
    • 객체 지향 언어에서 사용할 경우, 객체 지향적 설계를 추가로 고려해야 함
    • 초기 투자 비용이 큼

GoF(Gang of Four) 디자인 패턴

  • 소프트웨어 공학에서 가장 많이 사용되는 디자인 패턴 23개를 3가지 유형으로 나눈 것
  • 분류 기준 : 각각의 패턴이 어떤 일을 하기 위한 것인지에 관한 것
    • 생성
      • 객체의 생성과 관련된 패턴
      • 객체의 생성과 조합을 캡슐화해 특정 객체가 생성되거나 변경되어도 프로그램 구조에 영향을 크게 받지 않도록 유연성을 제공
    • 구조
      • 프로그램 구조를 설계하는데 사용되는 패턴
      • 클래스나 객체를 조합해 더 큰 구조를 만드는 패턴
    • 행동
      • 클래스나 객체들이 서로 상호 작용하는 방법이나 책임 분배 방법을 정의하는 패턴
      • 결합도를 최소화하는 것이 주 목적

Creational Pattern 생성 패턴

  • 객체의 생성과 관련된 패턴으로 객체의 인스턴스 과정을 추상화하는 방법
  • 객체의 생성과 참조 과정을 캡슐화하여 객체가 생성되거나 변경되어도 프로그램 구조에 영향을 받지 않도록 하여 프로그램에 유연성을 더 함
  • 종류


    |이름|의도|
    |:---|:---|
    |Abstract Factory 추상 팩토리| 구체적인 클래스를 지정하지 않고 > 인터페이스를 통해 서로 연관되는 객체들을 그룹으로 표현 |
    |Builder 빌더| 복합 객체의 생성과 표현을 분리하여 동일한 생성 절차에서도 다른 표현 결과를 만들어낼 수 있음 |
    |Factory Method 팩토리 메소드|객체 생성을 서브클래스로 위임하여 캡슐화함 |
    |Prototype 프로토타입| 원본 객체를 복사함으로써 객체를 생성 |
    |Singleton 싱글톤| 어떤 클래스의 인스턴스는 하나임을 보장하고 어디서든 참조할 수 있도록 함구조 패턴 |

Builder Pattern 빌더 패턴

  • 복잡한 객체의 생성 과정을 생성(construction)과 표기(representation)로 분리하여 처리하는 방식
  • 객체를 생성하는 코드를 단순하게 유지하면서도 유연성과 확장성을 제공
  • 장점
    • 다양한 빌더를 구현하여 다양한 종류의 유연성 있는 객체 생성 가능
    • 빌더를 사용하여 객체를 생성하면 코드의 가독성이 향상
  • 단점
    • 빌더 패턴을 사용하면 빌더 클래스가 추가로 필요하므로, 클래스의 개수가 늘어날 수 있음

Singleton pattern 싱글톤 패턴

  • 클래스의 인스턴스가 오직 하나만 생성되도록 보장하는 패턴으로 여러 곳에서 동시에 같은 인스턴스에 접근할 수 있으며, 자원을 절약하고 전역 상태 관리에 유용
  • 장점
    • 인스턴스가 하나만 생성되므로 메모리 사용이 최적화하여 메모리 낭비를 방지
    • 여러 객체가 동일한 인스턴스에 접근하므로 데이터를 공유하고 공통 상태를 유지하여 전역 상태를 관리하기에 용이
  • 단점
    • 인스턴스가 공유되므로, 다른 객체 사이의 의존성이 높아질 수 있고 전역 상태 관리가 복잡해 질 수 있음
    • 다른 인스턴스를 상속하거나 확장하기 어려울 수 있으며 상속이 불가능한 클래스일 경우 싱글톤 패턴을 사용할 수 없음
  • 예시
    • 로깅: 로그를 기록하는 기능을 싱글톤으로 구현하여 어디서나 동일한 로그 기능을 사용
    • 데이터베이스 연결 : 데이터베이스 연결을 담당하는 클래스를 싱글톤으로 구현하여 데이터베이스 접근을 관리
    • 애플리케이션 설정 : 애플리케이션 설정 정보를 담당하는 클래스를 싱글톤으로 구현하여 설정 정보에 접근

Factory Method Pattern 팩토리 메소드 패턴

  • 클래스의 인스턴스를 만드는 것을 서브 클래스에서 결정하는 패턴으로 객체를 생성하는 코드와 실제 객체를 사용하는 코드를 분리하여 유지보수성과 확장성을 높임
  • 객체 생성을 처리하는 팩토리(Factory) 클래스를 만들고, 클라이언트 코드는 팩토리를 통해 객체를 생성하므로써 클라이언트는 구체적인 객체 생성 방법을 알 필요 없이 팩토리에 의존하여 객체를 생성하고 사용할 수 있음
  • 장점
    • 객체 생성 코드를 별도의 팩토리 클래스로 분리함으로써 클라이언트 코드가 객체의 구체적인 생성 방법에 대해 알 필요가 없음
    • 새로운 객체를 추가하거나 객체 생성 방식을 변경해도 클라이언트 코드에는 영향을 미치지 않으며, 기존 코드를 수정하지 않고도 팩토리 클래스를 확장하여 처리할 수 있음
    • 객체 생성 코드가 여러 곳에 중복되는 것을 방지하여 코드의 중복을 최소화
  • 단점
    • 팩토리 패턴을 사용하려면 별도의 팩토리 클래스를 생성해야 하므로 간단한 객체 생성에서는 너무 많은 클래스를 도입하여 코드의 복잡성이 증가할 수 있음
    • 팩토리 패턴을 도입하면서 코드의 추상화 수준이 올라가 복잡성 증가할 수 있음

Abstract Factory Method 추상 팩토리 패턴

  • 서로 연관된 객체들을 생성하기 위한 인터페이스를 제공하는 패턴
  • 슈퍼 클래스에서 기본적인 알고리즘의 흐름을 정의하고, 하위 클래스에서 특정 단계를 구현
  • 장점
    • 추상 팩토리 패턴을 사용하여 연관된 객체들을 한 번에 생성 가능
    • 서로 다른 종류의 객체들을 생성하는 팩토리를 구현하여 시스템의 유연성을 향상
  • 단점
    • 새로운 종류의 객체를 추가하려면 해당 객체에 대한 팩토리를 추가해야 하므로 클래스의 수가 늘어날 수 있음
  • 예시
    • 슈퍼 클래스, Algorithm
      • 추상 메서드 step1(), step2(), step3() ...
      • algorithm(): 추상 메서드 step1(), step2(), step3()로 구성된 메서드
    • 하위 클래스, (AlgorithmA, AlgorithmB)
      • step1(), step2(), step3()을 구현하여 algorithm()을 사용

Structural Pattern 구조 패턴

  • 클래스나 객체들을 조합해 더 큰 구조로 만들 수 있게 해주는 패턴
  • 상속을 통해 클래스나 인터페이스를 합성하고, 구조 객체 패턴은 객체를 합성하는 방법을 정의
  • 종류
    이름의도
    Adapter 어댑터클래스의 인터페이스를 다른 인터페이스로 변환하여 다른 클래스가 이용할 수 있도록 함
    Bridge 브리지구현부에서 추상층을 분리하여 각자 독립적으로 확장할 수 있게 함
    Composite 컴포지트객체들의 관계를 트리 구조로 구성하여 복합 객체와 단일 객체를 구분없이 다룸
    Decorator 데코레이터주어진 상황 및 용도에 따라 어떤 객체에 다른 객체를 덧붙이는 방식
    Facade 퍼싸드서브시스템에 있는 인터페이스 집합에 대해 하나의 통합된 인터페이스(Wrapper) 제공
    Proxy 프록시접근이 어려운 객체로의 접근을 제어하기 위해 객체의 Surrogate나 Placeholder를 제공

Adapter Pattern 어댑터 패턴

  • 서로 다른 인터페이스를 가진 두 개의 클래스를 함께 동작하도록 해주는 구조적인 패턴으로 호환되지 않는 인터페이스를 변환하여 하나의 인터페이스를 제공하여 객체들이 함께 작동할 수 있도록 하는 패턴

  • 장점

    • 기존 클래스를 수정하지 않고 어댑터를 통해 사용 가능
    • 서로 다른 인터페이스를 가진 클래스들 간에 호환성을 확보
  • 단점

    • 어댑터를 도입하면 중간 계층이 추가되므로 코드가 복잡해질 수 있음
  • 예시

      // Adaptee : 클라이언트에서 사용하고 싶은 기존의 서비스 (하지만 호환이 안되서 바로 사용 불가능)
      class Service {
    
          void specificMethod(int specialData) {
              System.out.println("기존 서비스 기능 호출 + " + specialData);
          }
      }
    
      // Client Interface : 클라이언트가 접근해서 사용할 어댑터 모듈
      interface Target {
          void method(int data);
      }
    
      // Adapter : Adaptee 서비스를 클라이언트에서 사용하게 할 수 있도록 호환 처리 해주는 어댑터
      class Adapter implements Target {
          Service adaptee; // composition으로 Service 객체를 클래스 필드로
    
          // 어댑터가 인스턴스화되면 호환시킬 기존 서비스를 설정
          Adapter(Service adaptee) {
              this.adaptee = adaptee;
          }
    
          // 어댑터의 메소드가 호출되면, Adaptee의 메소드를 호출하도록
          public void method(int data) {
              adaptee.specificMethod(data); // 위임
          }
      }
    
      class Client {
          public static void main(String[] args) {
              // 1. 어댑터 생성 (기존 서비스를 인자로 받아 호환 작업 처리)
              Target adapter = new Adapter(new Service());
    
              // 2. Client Interfac의 스펙에 따라 메소드를 실행하면 기존 서비스의 메소드가 실행된다.
              adapter.method(1);
          }
      }

Composite Pattern 컴포지트 패턴

  • 컴포지트 패턴은 객체들을 트리 구조로 구성하여 단일 객체와 복합 객체를 동일하게 취급할 수 있도록 하는 구조적인 패턴
  • 장점
    • 단일 객체와 복합 객체를 동일하게 취급할 수 있으므로 일관성 있는 방식으로 객체들을 다룰 수 있음
    • 새로운 객체를 추가하거나 삭제하는 것이 용이
  • 단점
    • 트리 구조를 사용하기 때문에 코드가 복잡해질 수 있음

Proxy Pattern 프록시 패턴

  • 어떤 객체에 대한 접근을 제어하기 위해 대리자(Proxy) 객체를 사용하는 패턴
  • 프록시는 실제 객체와 동일한 인터페이스를 구현하며, 클라이언트는 프록시를 통해 실제 객체에 접근하여 클라이언트와 실제 객체 사이에 중간 계층을 두어 접근을 제어하거나 추가 기능을 제공
  • 장점
    • 프록시를 사용하여 리소스에 접근을 제어. 클라이언트가 실제 리소스에 직접 접근하는 것을 허용하지 않거나, 인증과 같은 접근 제어 로직을 프록시에 구현 가능
    • 프록시를 이용하여 리소스에 대한 불필요한 연산을 제거하여 성능 향상
  • 단점
    • 프록시를 도입하면서 중간 계층이 추가되므로 코드가 복잡해질 수 있음
    • 실제 리소스에 접근하기 전에 프록시를 거치기 때문에, 성능 저하가 발생할 수 있음

Behavioral Pattern 행위패턴

  • 객체나 클래스의 교류 방법에 대해 정의하는 것
  • 행위 패턴은 하나의 객체로 수행할 수 없는 작업을 여러 객체로 분배하면서 그들 간의 결합도를 최소화 할 수 있도록 도와준다.
  • 행위 클래스 패턴은 상속을 통해 알고리즘과 제어 흐름을 기술하고, 행위 객체 해턴은 하나의 작업을 수행하기 위해 객체 집합이 어떻게 협력하는지를 기술
  • 종류
    이름의도
    Chain of Responsibility 책임 연쇄청을 받는 객체를 연쇄적으로 묶어 요청을 처리하는 객체를 만날 때까지 객체 Chain을 따라 요청을 전달
    Command 커맨드청을 객체의 형태로 캡슐화하여 재사용하거나 취소할 수 있도록 저장
    Interpreter 인터프리터특정 언어의 문법 표현을 정의
    Iterator 반복자내부를 노출하지 않고 접근이 잦은 어떤 객체의 원소를 순차적으로 접근할 수 있는 동일한 인터페이스 제공
    Mediator 중재자한 집합에 속해있는 객체들의 상호작용을 캡슐화하여 새로운 객체로 정의
    Memento 메멘토객체가 특정 상태로 다시 되돌아올 수 있도록 내부 상태를 실체화
    Observer 옵서버객체 상태가 변할 때 관련 객체들이 그 변화를 통지받고 자동으로 갱신될 수 있게 함
    State 상태객체의 상태에 따라 동일한 동작을 다르게 처리해야할 때 사용
    Strategy 전략동일 계열의 알고리즘군을 정의하고 캡슐화하여 상호교환이 가능하도록 함
    Visitor 방문자객체의 원소에 대해 수행할 연산을 분리하여 별도의 클래스로 구성함
    Template Method Pattern 템플릿 메서드여러 클래스에서 공통으로 사용하는 메서드를 템플릿화 하여 상위 클래스에서 정의하고, 하위 클래스마다 세부 동작 사항을 다르게 구현하는 패턴

Iterator Pattern 이터레이터 패턴

  • 컬렉션 객체의 내부 구조를 노출하지 않고 요소에 순차적으로 접근하는 방법을 제공하는 패턴으로 클라이언트는 컬렉션의 내부 구조를 알 필요 없이 요소들에게 순차적으로 접근 가능
  • 컬렉션 객체와 이터레이터 객체로 구성
    • 컬렉션 객체 : 여러 개의 요소를 포함
    • 이터레이터 객체 : 컬렉션의 요소에 접근하는데 사용되는 인터페이스를 정의
  • 장점
    • 이터레이터 패턴을 사용하면 컬렉션 객체의 내부 구조와 요소에 접근하는 방법을 분리하여 컬렉션 객체가 변경되더라도 클라이언트 코드를 수정할 필요가 없음
    • 이터레이터 패턴은 서로 다른 컬렉션 객체에 대해 동일한 인터페이스를 제공할 수 있으므로, 다양한 종류의 컬렉션을 사용할 때 유용
    • 요소에 순차적으로 접근하기 위해 필요한 메서드를 이터레이터에 정의하므로, 클라이언트는 간편하게 요소들을 순차적으로 처리할 수 있음
  • 단점
    • 이터레이터를 도입하면서 추가적인 추상화 계층이 필요하므로 코드가 복잡해질 수 있음
    • 이터레이터를 통해 접근하는 방식은 직접 컬렉션에 접근하는 것보다 약간의 성능 저하가 발생할 수 있음
  • 예시
    • 자바에서 제공하는 Iterator Interface는 ArrayList, TreeSet, HashMap 등 컬렉션 클래스의 요소들을 순회하는 데 사용

Observer pattern 옵저버 패턴

  • 객체 간의 일대다 의존 관계를 정의하는 패턴으로 객체의 상태가 변하면, 이 객체에 의존하는 다른 객체들이 자동으로 알림을 받아 상태 변화를 처리
  • 주체(Subject)와 옵저버(Observer)라고 불리는 두 가지 종류의 객체로 구성되며 주체와 옵저버 사이에는 일대다 의존 관계가 형성
    • 주체 : 상태를 가지고 있으며, 이 상태가 변하면 등록된 모든 옵저버에게 알림을 보냄
    • 옵저버 : 주체의 상태 변화를 감시하다가 알림을 받으면 적절한 동작을 수행
  • 장점
    • 주체와 옵저버는 서로 독립적으로 변경할 수 있으며, 느슨한 결합을 형성
    • 새로운 옵저버를 추가함으로써 기능을 확장 용이
    • 옵저버 패턴은 변경 사항이 발생할 때마다 옵저버들에게 알림을 보내는 간단한 업데이트 메커니즘을 제공
  • 단점
    • 주체의 상태가 매우 빈번하게 변경되면, 옵저버들에게 너무 많은 알림을 보내게 되어 성능 저하가 발생
    • 옵저버들에 대해 알림을 보낼 때, 알림을 받는 순서가 보장되지 않을 수 있으므로 순서가 중요한 경우에는 주의가 필요

Strategy Pattern 전략 패턴

  • 알고리즘의 유연한 교체를 가능하게 하는 패턴 비슷한 동작을 하지만 다르게 구현되어 있는 행위(전략)들을 공통의 인터페이스를 구현하는 각각의 클래스로 구현하고, 동적으로 바꿀 수 있도록 하는 패턴

  • 장점

    • 클라이언트는 인터페이스를 통해 알고리즘을 사용하므로, 실행 중에 다른 알고리즘으로 교체하는 것이 쉬워 애플리케이션의 유연성과 확장성이 증가
    • 각각의 알고리즘을 독립적인 클래스로 구현하기 때문에, 코드를 재사용이 용이
    • 전략 클래스를 인터페이스를 통해 분리하여 개별적으로 테스트 가능
  • 단점

    • 각각의 알고리즘을 독립적인 클래스로 구현해야 하므로, 전략의 개수가 많을 경우 클래스의 수가 증가할 수 있음
    • 전략 패턴을 사용하면 컨텍스트와 전략이 느슨하게 결합되긴 하지만, 여전히 컨텍스트는 전략을 명시적으로 선택해야 함
  • 예시

     // 전략 인터페이스 (Strategy Interface)
     interface Strategy {
       int execute(int a, int b);
     }
    
     // 전략 구현 클래스 (Concrete Strategies)
     class AddOperation implements Strategy {
       public int execute(int a, int b) {
         return a + b;
       }
     }
    
     // 컨텍스트 클래스 (Context)
     class Context {
       private Strategy strategy;
    
       // 전략 교체 메소드
       public Context(Strategy strategy) {
         this.strategy = strategy;
       }
    
       // 전략 실행 메소드
       public int executeStrategy(int a, int b) {
         return strategy.execute(a, b);
       }
     }
    
     // 사용 예시
     public class Main {
       public static void main(String[] args) {
         int a = 10, b = 5;
    
         // 전략 설정
         Strategy addStrategy = new AddOperation();
         Context context = new Context(addStrategy);
    
         // 전략 실행
         int result = context.executeStrategy(a, b);
       }
     }
    

Template Method Pattern 템플릿 메소드 패턴

  • 여러 클래스에서 공통으로 사용하는 메서드를 템플릿화 하여 상위 클래스에서 정의하고, 하위 클래스마다 세부 동작 사항을 다르게 구현하는 패턴

  • 장점

    • 알고리즘의 공통 부분을 부모 클래스에 두기 때문에 코드의 중복을 최소화
      *새로운 알고리즘을 추가하기 쉽고, 기존 알고리즘의 변경이 자식 클래스에 영향이 없음
  • 단점

    • 일부 단계가 서브클래스에 의해 고정되어 있기 때문에 일부 단계를 동적으로 변경하는 것이 어려움
  • 예시

      abstract class Ramyun {
          public void cook() {
              System.out.println("불을 켠다.");
              System.out.println("냄비에 물을 받고 끓인다.");
    
              getRamyun();
    
              System.out.println("스프와 면을 넣는다.");
              System.out.println("알맞게 끓인다.");
          }
    
          protected abstract void getRamyun();
      }
      // JinRamyun.java
      public class JinRamyun extends Ramyun {
          @Override
          protected void getRamyun() {
              System.out.println("진라면을 준비한다.");
          }
      }
    
      // SamyangRamyun.java
      public class SamyangRamyun extends Ramyun {
          @Override
          protected void getRamyun() {
              System.out.println("삼양라면을 준비한다.");
          }
      }

0개의 댓글