CS - Design Pattern Basic


디자인 패턴

  • 개발 전에 어떤 방식으로 개발을 할 지 설계를 해야 함.

  • 객체지향의 특성을 잘 살리고, 설계자들이 “올바른” 설계를 “빨리” 만들 수 있도록 도와주는 정형화 된 패턴.

  • 생성 패턴, 구조 패턴, 행위 패턴이 있다.

  • 재사용성

    • 반복적인 문제에 대한 일반적인 해결책을 제공하므로,

    • 이를 재사용하여 유사한 상황에서 코드를 더 쉽게 작성할 수 있다.

  • 가독성

    • 일정한 구조로 정리하고 명확하게 작성하여

    • 개발자가 코드를 이해하고 유지보수하기 쉽게 만든다.

  • 유지보수성

    • 코드를 쉽게 모듈화 할 수 있으며,

    • 변경이 필요한 경우 해당 모듈만 수정하여 유지보수가 쉬워진다.

  • 확장성

    • 새로운 기능을 추가하거나 변경할 때,

    • 디자인 패턴을 활용하여 기존 코드를 변경하지 않고도 새로운 기능을 통합할 수 있다.

  • 안정성과 신뢰성

    • 수많은 사람들이 인정한 모범 사례로 검증된 솔루션을 제공한다.



생성 패턴

  • Singleton(싱글톤 패턴) : 하나의 클래스 인스턴스를 전역에서 접근 가능하게 하면서 해당 인스턴스가 한 번만 생성되도록 보장하는 패턴이다.

  • Factory Method(팩토리 메서드 패턴) : 객체를 생성하기 위한 인터페이스를 정의하고, 서브클래스에서 어떤 클래스의 인스턴스를 생성할지 결정하는 패턴이다.

  • Abstract Factory(추상 팩토리 패턴) : 관련된 객체들의 집합을 생성하는 인터페이스를 제공하며, 구체적인 팩토리 클래스를 통해 객체 생성을 추상화하는 패턴이다.

  • Builder(빌더 패턴) : 복잡한 객체의 생성 과정을 단순화하고, 객체를 단계적으로 생성하며 구성하는 패턴이다.

  • Prototype(프로토타입 패턴): 객체를 복제하여 새로운 객체를 생성하는 패턴으로, 기존 객체를 템플릿으로 사용하는 패턴이다.


구조 패턴

  • Adapter(어댑터 패턴) : 호출당하는 쪽의 메소드를 호출하는 쪽의 코드에 대응하도록 중간에 변환기를 통해 호출하는 패턴

  • Bridge(브릿지 패턴) : 추상화와 구현을 분리하여 두 가지를 독립적으로 확장할 수 있는 패턴이다.

  • Composite(컴포지트 패턴) : 개별 객체와 복합 객체를 동일하게 다루어, 트리 구조의 객체를 구성하는 패턴이다.

  • Decorator(데코레이터 패턴) : 객체에 동적으로 새로운 기능을 추가하여 객체를 확장할 수 있는 패턴이다.

  • Facade(퍼사드 패턴) : 서브시스템을 더 쉽게 사용할 수 있도록 단순한 인터페이스를 제공하는 패턴이다.

  • Flyweight(플라이웨이트 패턴) : 공유 가능한 객체를 통해 메모리 사용을 최적화하는 패턴이다.

  • Proxy(프록시 패턴) : 다른 객체에 대한 대리자(Proxy)를 제공하여 접근 제어, 지연 로딩 등을 구현하는 패턴이다.


행위 패턴

  • Observer(옵저버 패턴) : 객체 간의 일대다 종속 관계를 정의하여 한 객체의 상태 변경이 다른 객체들에게 알려지도록 한다.

  • Strategy(전략 패턴) : 알고리즘을 정의하고, 실행 중에 선택할 수 있게 한다.

  • Command(커맨드 패턴) : 요청을 객체로 캡슐화하여 요청을 매개변수화 하고, 요청을 큐에 저장하거나 로깅하고 실행을 지연시킨다.

  • State(상태 패턴) : 객체의 상태를 캡슐화하고, 상태 전환을 관리한다.

  • Chain of Responsibility(책임 연쇄 패턴) : 요청을 보내는 객체와 이를 처리하는 객체를 분리하여, 다양한 처리자 중 하나가 요청을 처리한다.

  • Visitor(방문자 패턴) : 객체 구조를 순회하면서 다양한 연산을 수행할 수 있게 한다.

  • Interpreter(인터프리터 패턴) : 언어나 문법에 대한 해석기를 제공하여, 주어진 언어로 표현된 문제를 해결하는 패턴이다.

  • Memento(메멘토 패턴) : 객체의 내부 상태를 저장하고 복원할 수 있는 기능을 제공하는 패턴이다.

  • Mediator(중재자 패턴) : 객체 간의 상호 작용을 캡슐화하여, 객체 간의 직접적인 통신을 방지하는 패턴이다.

  • Template Method(템플릿 메서드 패턴) : 알고리즘의 구조를 정의하면서 하위 클래스에서 각 단계의 구현을 제공하는 디자인 패턴이다.

  • Iterator(이터레이터 패턴) : 컬렉션 내의 요소들에 접근하는 방법을 표준화하여 컬렉션의 내부 구조에 독립적으로 접근할 수 있는 패턴이다.



스프링이 사랑하는 디자인 패턴들

  • 싱글톤 패턴

    • 클래스의 인스턴스, 즉 객체를 하나만 만들어 사용하는 패턴

    • private 생성자를 갖는다.(new를 통한 인스턴스 생성을 금지)

    • 단일 객체 참조 변수를 정적 속성으로 갖는다.

    • 단일 객체 참조 변수가 참조하는 단일 객체를 반환하는 getInstatnce() 정적 메서드를 갖는다.

    • 장점 : 자원을 공유하고, 전역 상태로 관리에 용이하다.

    • 단점 : 테스트가 어렵고, OOP 원칙을 위배하는 것으므로, 읽기 전용 클래스로 만들어야 한다.

    • Spring 에서 MemberService 와 같은 클래스는 스프링 컨테이너에서 싱글톤 패턴으로 만들어져있다.

  • 팩토리 메서드 패턴

    • 클래스 생성과 사용의 처리 로직을 분리하여 결합도를 낮추고자 할 때 사용한다.

    • 장점 : 단일 책임원칙과, 개발/폐쇄 원칙을 준수 할 수 있다.

    • 장점 : 캡슐화와 추상화를 통해 생성되는 객체의 구체적인 타입을 감출 수 있다.

    • 단점 : 각 제품 구현체마다 팩토리 객체들을 모두 구현해주어야 하기 때문에, 구현체가 늘어날때 마다 팩토리 클래스가 증가하여 서브 클래스 수가 폭발한다.

    • 예를 들어,

      • 로직에서는 CouponService service = factory.getProduct(); 로 해서

      • 그 factory 안에서 JoinEventCouponService 인지, NewYearCouponService 인지를 정해서 구현 클래스를 꽂아줌.

    • 우리는 이미, 스프링 컨테이너 안에서 이렇게 의존성 주입을 하고 있음.

  • 어댑터 패턴

    • 클래스의 인터페이스를 사용자가 기대하는 다른 인터페이스로 변환하는 디자인 패턴.

    • 어댑터 패턴은 개방 폐쇄 원칙을 활용한 설계 패턴이라고 할 수 있다

    • 자바는 다중 상속이 안되기 때문에, 인터페이스를 통한 구현이 가능하다.

    • 장점 : SRP 와 OCP 를 만족한다. 즉, 재사용성이 증가하고 확장성이 용이해진다는 것이며, 결합도가 감소한다.

    • 단점 : 어댑터를 안쓰는 경우가 더 간단한 경우들이 있으므로 잘 사용해야 하며, 코드 복잡도가 올라갈 수 있음.

    • 단점 : 어댑터 오버헤드가 생길 수 있다. ( 변환 과정에서 성능 저하 발생 가능성이 있음. )

    • BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

      • 해당 코드가 어댑터를 활용한 코드이다.

      • input을 행하는 System.in 을 BufferedReader 객체에 사용하고 싶은데 이 중간을 InputStreamReader 가 어댑터 역할을 하고 있는 것이다.

  • 프록시 패턴

    • 이 패턴에 대해서는 사실 할 말이 많다.

    • Spring 에서 사용하는 AOP, Transactional 도 다 프록시 객체로 만들어지는 것이기 때문이다.

    • 우선은 쉽게 이야기하면 A 클래스에 대한 프록시 객체를 만들어서, 그 프록시 객체를 통하여, A의 메서드에 진입한다.

    • 그래서 해당 메서드 앞 혹은 뒤에 원하는 공통 로직을 넣을 수 있다.

    • 장점 : 원본 객체에 직접 접근하지 않으므로 보안성이 향상되며, 로그를 남기는 등 추가 기능을 적용하기 쉽다.

    • 장점 : 원하는 공통 기능을 묶을 수 있으므로 유지보수성이 좋다.

    • 장점 : 결합도를 낮출 수 있다.

    • 단점 : 어떻게 활용하느냐에 따라 복잡성이 증가하고 성능이 저하 될 수 있다.

  • 데코레이터 패턴

    • 프록시 패턴과 좀 비슷하다.

    • 그러나, 앞 뒤에 인프라 로직을 넣는 개념보다는,

    • 실제 메서드에서 리턴되는 값에 무언가를 추가하는 개념이다.

    • 말 그대로 데코레이션을 해주는 것이다.

    • 장점 : 런타임에 동적으로 기능을 변경할 수 있으며, SRP, OCP, DIP 를 만족시킬 수 있다.

    • 단점 : 초기 코드가 보기 안좋을 수 있다. ( new A(new B(new C(new D)))) )

  • 전략 패턴

    • 알고리즘을 정의하고, 실행 중에 선택할 수 있게 한다.
      // 총을 람보에게 전달해서 전투를 수행하게 한다.
      strategy = new StrategyGun();
      rambo.runContext(strategy);
    
      System.out.println();
    
      // 검을 람보에게 전달해서 전투를 수행하게 한다.
      strategy = new StrategySword();
      rambo.runContext(strategy);
    
      System.out.println();
    
      // 활을 람보에게 전달해서 전투를 수행하게 한다.
      strategy = new StrategyBow();
      rambo.runContext(strategy);
    
    • 이렇게 했을 때, 생성한 구현 객체에 따라 다른 결과가 나온다.

    • 이렇게 번갈아가며 알고리즘의 동작이 런타임에 실시간으로 교체 되어야 할때 사용하는 패턴이다.

    • 장점 : OOP 의 모든 특징을 잘 지키는 완성형 패턴이다.

    • 단점 : 코드 복잡도가 증가한다.

  • 탬플릿 메서드 패턴

    • 공통 메서드는 부모 클래스에서 만들고, 자식 클래스를 위한 추상 메서드를 만드는 방법.
      abstract class Animal {
          public void testMethod_A() {
              System.out.println("나는 동물이야.");
          }
    		
          public abstract void testMethod_B();
      }
    	
      class Dog extends Animal {
          @Override
          public void testMethod_B() {
              System.out.println("멍멍 !!!");
          }
      }
    	
      class Cat extends Animal {
          @Override
          public void testMethod_B() {
              System.out.println("야옹 !!!");
          }
      }
    
    • 장점 : 코드 중복을 줄여 유지보수성을 높일 수 있다.

    • 단점 : 유연성이 제한될 수 있으며, 추상 메서드가 많아지면 하위 클래스 관리가 어려워질 수 있다.

    • 단점 : LSP 를 위반할 여지가 생긴다.






results matching ""

    No results matching ""