책의 흐름 : 패턴을 설명하기 전에 알아야 하는 원칙이 있다면 미리 설명하면서 패턴을 그 이후에 설명하는 방식으로 전개 하고 있습니다. 그림이 많아 이해하기 쉽습니다.
1차적으로는 책의 내용을 요약 하였고, ChatGPT 에게 보완 받았습니다.
디자인 패턴
- 객체지향 기초지식만 가지고는 훌륭한 객체지향 디자이너가 되기 어렵습니다.
- 훌륭한 객체지향 디자인이라면 재사용성, 확장성, 관리의 용이성을 갖춰야 합니다.
- 패턴은 훌륭한 객체지향 디자인 품질을 갖추고 있는 시스템을 만드는 방법을 제공하곤 합니다.
- 패턴은 오랜시간 많은 사람들에게 검증받은 객체지향 경험의 산물 입니다.
- 패턴이 코드를 바로 제공해주는 것은 아님, 디자인 문제에 대한 일반적인 해법을 제공해줍니다.(특정 어플리케이션 패턴을 적용하는 것은 우리가 해야할 일)
- 패턴이 발명되는 것이 아니라 발견되는 것입니다.
- 대부분 패턴과 원친은 소프트웨어의 잦은 변경 문제와 관련 있습니다.
- 대부분의 패턴은 시스템의 일부분을 나머지 부분과 무관하게 변경하는 방법을 제공합니다.
- 많은 경우 시스템에서 바뀌는 부분을 골라내서 캡슐화시켜야 합니다.
- 패턴은 다른 개발자들과의 의사소통의 가치를 극대화 시킬 수 있는 전문 용어 역할을 합니다.
객체지향 원칙
- 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리 시킵니다..(바뀌는 부분은 따로 뽑아서 캡슐화 시킨다 그렇게 하면 나중에 바뀌지 않는 부분에는 영향을 미치지 않은 채로 그 부분만 고치거나 확장할 수 있습니다)
- 구현이 아닌 인터페이스에 맞춰서 프로그래밍 합니다.(인터페이스에 맞춰서 프로그래밍 한다 == 상위 형식에 맞춰서 프로그래밍 합니다)
- 상속보다는 구성을 활용합니다
- 서로 상호작용을 하는 객체사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 합니다.(Loose Coupling)
- 클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 합니다.(Open-Closed Principle)
- 추상화된 것에 의존하도록 만들어라 구상 클래스에 의존하도록 만들지 않도록 합니다.(Dependency Inversion Principle)
- 최소 지식 원칙 - 정말 친한 친구하고만 이야기하자
Strategy Pattern
- 알고리즘을 정의하고 각각을 캡슐화하여 실행시간에도 교환해서 사용할 수 있도록 만듭니다.. Strategy 패턴을 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있습니다.
Observer Pattern
- 한 객체의 상태가 바뀌면 그 객체의 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의 합니다.
Observer Pattern Summary
- 옵저버 패턴에서는 객체들 사이에 일대다 관계를 정의한다.
- 주제, 또는 Observable 객체는 동일한 인터페이스를 써서 옵저버에 연락을 한다.
- Observable 에서는 옵저버들이 Observer 인터페이스를 구현한다는 것을 제외하면 옵저버에 대해 전혀 모르기 때문에, 이들 사이의 결합은 느슨한 결합이다.
- 옵저버 패턴을 이용하면 주제객체에서 데이터를 보내거나(푸시 방식) 옵저버가 데이터를 가져오는(풀 방식)을 쓸 수 있다.(일반적으로 풀 방식을 권장함)
- 옵저버들한테 연ㄹ가을 돌리는 순서에 절대로 의존해선 안됨
- 자바에는 범용으로 쓸 수 있는 javautilObservable 을 비롯하여 옵저버 패턴을 구현한 것들이 여럿 있다.
- javautilObservable 몇가지 문제점이 있으니 사용에 주의하자.
- 따라서 필요하면 언제든지 주저하지 말고 Observable 클래스에 상응하는 클래스를 직접 구현해서 사용하자.
- 여러 GUI 프레임 워크, GUI 가 아닌 환경에서도 옵저버 패턴은 많이 활용되고 있음
Decorator Pattern
- 객체에 추가적은 교언을 동적으로 첨가한다 데코레이터는 서브 클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있는 방법을 제공한다.
Decorator Pattern Summary
- 상속을 통해 확장을 할 수도 있지만, 디자인의 유연성 면에서 보면 별로 좋지 않은 경우들이 있다. (이때 데코레이터 패턴을 사용을 고려해 볼 수 있다)
- 데코레이터 패턴은 객체에 동적으로 기능을 추가하고 확장하기 위한 패턴으로, 기존 코드를 수정하지 않고도 행동을 확장하는 방법이 필요할 때 사용할 수 있다.
- 구성과 위임을 통해서 실행중에 새로운 행동을 추가할 수 있습니다.
- 데코레이터 패턴에서는 구성요소(new로 할당해서 사용하는 객체)를 감싸주는 데코레이터들을 사용한다.
- 데코레이터 클래스의 형식은 그 클래스가 감싸고 있는 클래스의 형식을 반영하며, 구성요소와 데코레이터는 동일한 인터페이스나 추상 클래스를 구현하여야 한다.
- 데코레이터에서는 자신이 감싸고 있는 구성요소의 메서드를 호출한 결과에 새로운 기능을 더하여 행동을 확장한다.
- 구성요소를 감싸는 데코레이터의 개수에는 제한이 없으며, 여러 개의 데코레이터를 쌓아 올려서 기능을 확장할 수 있다.
- 구성요소의 클라이언트 입장에서는 데코레이터의 존재를 알 수 없으며, 클라이언트는 구성요소의 인터페이스나 추상 클래스에만 의존하게 된다. (클라이언트에서 구성요소의 구체적인 형식에 의존하게 되는 경우는 예외적인 상황으로 데코레이터가 최선의 선택인지 확인하자)
- 데코레이터 패턴을 사용하면 코드의 유연성은 높아지지만, 데코레이터가 많아질수록 객체들이 많이 생길 수 있고, 코드가 필요 이상으로 복잡해질 수 있다.
- 데코레이터 패턴은 Java I/O에서 많이 사용되며, 예시로는 new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("text.txt")))와 같이 여러 개의 데코레이터를 적용하여 입력 스트림의 기능을 확장하는 경우가 있다.
Factory Method Pattern (팩토리 메서드 패턴)
- 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만듬. 즉 팩토리 메서드 패턴을 이용하면 클래스의 인스턴스를 만드는 일을 서브클래스에게 맡김.
Abstract Factory Pattern (추상 팩토리 패턴)
- 인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성할 수 있다.
Factory Pattern Summary
- 팩토리를 사용하면 객체 생성을 캡슐화할 수 있습니다.
- 간단한 팩토리는 엄밀하게 말해서 디자인 패턴은 아니지만, 클라이언트와 구상 클래스를 분리시키기 위한 간단한 기법으로 활용될 수 있습니다.
- 팩토리 메서드 패턴에서는 상속을 활용합니다. 객체 생성이 서브클래스에 위임되며, 서브클래스에서는 팩토리 메서드를 구현해서 객체를 생성합니다.
- 추상 팩토리 패턴에서는 객체 구성(Composition)을 활용합니다. 객체 생성이 팩토리 인터페이스에서 선언된 메서드들에서 구현됩니다.
- 모든 팩토리 패턴에서는 애플리케이션의 구상 클래스에 대한 의존성을 줄여줌으로써 느슨한 결합을 도와줍니다.
- 팩토리 메서드 패턴에서는 어떤 클래스에서 인스턴스를 만드는 일을 서브클래스에게 넘깁니다. (변화가 생길 시에 서브클래스만 수정하면 되기 때문에 변화에 용이합니다.)
- 추상 팩토리 패턴은 구상 클래스에 직접 의존하지 않고도 서로 관련된 객체들로 이루어진 제품군을 만들기 위한 용도로 쓰입니다.
- 의존성 역전 원칙(DIP)을 따르면 구상 형식에 대한 의존을 피하고 추상화를 지향할 수 있습니다.
- 팩토리는 구상 클래스가 아닌 추상 클래스/인터페이스에 맞춰서 코딩할 수 있게 해주는 강력한 기법입니다.
Singleton Pattern
- 해당 클래스의 인스턴스가 하나만 만들어지고, 어디서든지 그 인스턴스에 접근할 수 있도록 하기 위한 패턴입니다
Singleton Pattern Summary
- 어떤 클래스에 싱글턴 패턴을 적용하면 애플리케이션에 그 클래스의 인스턴스가 최대 한개까지만 있도록 할 수 있습니다.
- 싱글턴 패턴을 이용하면 유일한 인스턴스를 어디서든지 접근할 수 있도록 할 수 있다.
- 자바에서 싱글턴 패턴을 구현할 때는 private 생성자와 정적 메서드, 정적 변수를 사용합니다.
- 다중 스레드 사용하는 애플리케이션에서는 속도와 자원 문제를 파악해 적절한 구현법을 사용해야 합니다.(거의 대부분 애플리케이션이 멀티 스레딩을 활용하기 때문에 염두해두자)
- DCL(Double-checking Locking 으로 volatile 키워드를 사용하는 방식) 을 사용하는 방법은 자바 1.5 보다 낮은 버전에서는 사용할 수 없습니다.
- 일반적인 상황이라면 클래스 로더가 하나지만, 클래스 로더가 여러개 존재하면 싱글턴이 제대로 동작하지 않게 되어 여러개의 인스턴스가 생길 수 있습니다.
- Java 1.2 버전에서 나온 JVM 을 사용하는 경우 가비지 컬렉터 관련 버그 때문에 싱글턴 레지스트리(서로 다른 싱글턴 객체를 관리하는 중앙 집중 저장소 클래스로 예시 : Map<String, Object>)를 사용해야 할 수도 있습니다.
- 싱글턴 패턴을 사용하면 객체를 생성하는 비용이 높은 경우 메모리를 절약할 수 있다는 장점이 있습니다.
- 싱글턴 패턴을 사용하면 전역 상태를 유지할 수 있기 때문에, 애플리케이션 전체에서 공유하는 객체를 만들 때 유용합니다. 그러나 이로 인해 다른 부분에서 의도치 않은 영향을 미칠 수도 있기 때문에 신중하게 사용해야 합니다.
- 다른 디자인 패턴과 결합하여 사용할 수도 있습니다. 예를 들어, 싱글턴 패턴을 사용하는 Factory Method 패턴이 있습니다.
Command Pattern
- 요구사항을 객체로 캡슐화 할 수 있으며, 매개변수를 써서 여러가지 다른 요구 사항을 집어넣을 수도 있습니다. 또한 요청 내역을 큐에 저장하거나 로그로 기록할 수도 있으며, 작업 취소 기능도 지원 가능합니다.
Command Pattern Summary
- 커맨드 패턴에서는 커맨드 객체가 리시버를 캡슐화합니다. 따라서 커맨드 객체는 어떤 리시버를 다룰지 결정하는 인자를 가지고 있어야 합니다.
- 커맨드 패턴에서는 인보커와 커맨드 객체가 1:1 대응됩니다. 인보커는 커맨드 객체를 생성하고, 커맨드 객체는 하나의 행동을 캡슐화합니다.
- 인보커에서는 요청을 할 때는 커맨드 객체의 execute(일반적인 네이밍) 메서드를 호출하면 된다. execute 메서드에서는 리시버에 있는 행동을 호출합니다.
- 인보커는 커맨드를 통해서 매개변수화될 수 있다. 이런 실행중에 동적으로 설정할 수도 있습니다.
- 커맨드 패턴에서는 작업 취소 기능을 구현하기 위해 커맨드 객체에 undo 메서드를 추가할 수 있습니다. 이 메서드는 execute 메서드가 실행된 이전 상태로 되돌리는 역할을 합니다.
- 매크로 커맨드는 여러 개의 커맨드 객체를 묶어서 하나의 큰 커맨드로 처리하는 것을 말합니다. 이를 통해 여러 개의 작은 커맨드를 한 번에 실행하거나 작업 취소할 수 있습니다.
- 스마트 커맨드 객체는 요청을 처리하는 데 필요한 정보를 모두 가지고 있는 객체를 말합니다. 따라서 이 경우에는 인보커와 리시버가 필요하지 않습니다. 스마트 커맨드 객체는 필요한 정보를 모두 가지고 있으므로, execute 메서드를 호출할 때 모든 정보를 함께 전달할 수 있습니다.
- 로그 및 트랜잭션 시스템을 구현하는 데에도 커맨드 패턴을 활용할 수 있습니다. 이 경우 커맨드 객체는 트랜잭션을 수행하거나, 로그를 기록하는 등의 작업을 캡슐화합니다.
Adapter Pattern
- 한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환합니다. 어댑터를 이용하면 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스들을 연결해서 사용할 수 있습니다.
Adapter Pattern Summary
- 기존 클래스를 사용하려고 하는데 인터페이스(ex. 시그니처)가 맞지 않으면 어댑터를 쓰면 됩니다.
- 인터페이스를 클라이언트에서 원하는 인터페이스로 변경해주는 역할을 합니다.
- 어댑터를 구현할 때는 타겟 인터페이스의 크기와 구조에 따라 코딩해야 할 분량이 결정됩니다.
- 어댑터 패턴에는 객체 어댑터 패턴과 클래스 어댑터 패턴이 있습니다. 클래스 어댑터를 사용하려면 언어에서 다중 상속 기능을 제공해야 합니다.
Facade Pattern(퍼사드 패턴)
- 어떤 서브 시스템의 일련의 인터페이스에 대한 통합된 인터페이스를 제공합니다. 퍼사드에서 고수준 인터페이스를 정의하기 때문에 서브 시스템을 쉽게 사용할 수 있습니다.
Facade Pattern Summary
- 큰 인터페이스, 또는 여러 인터페이스를 단순화시키거나 통합 시켜야 되는 경우에는 퍼사드를 쓰면 됩니다
- 클라이언트를 복잡한 서브 시스템과 분리시켜주는 역할을 합니다.
- 서브 시스템을 가지고 퍼사드를 만들고, 실제 작업은 서브클래스에 맡깁니다.
- 한 서브 시스템에 퍼사드를 여러개 만들어도 됩니다.
그럼 데코레이터 패턴과 어댑터 패턴은 같은 것일까? 퍼사드와는 어떤 차이가 있을까 ?
- 데코레이터 : 한 인터페이스를 다른 인터페이스로 변환(객체를 감싸서 새로운 행동을 추가하기 위한 용도)
- 어댑터 : 인터페이스는 바꾸지 않고 책임(기능)만 추가 (객체를 감싸서 인터페이스를 바꾸기 위한 용도)
- 어댑터 패턴은 인터페이스를 변경해서 클라이언트에서 필요로 하는 인터페이스로 적응시키기 위한 용도로 사용 되고, 퍼사드 패턴은 어떤 서브시스템에 대한 간단한 인터페이스를 제공하기 위한 용도로 쓰입니다.
- 퍼사드 : 인터페이스를 간단하게 바꿈 (복잡한 시스템을 쉽게 사용할 수 있도록 도와줌 - 일련의 객체들을 감싸서 단순화시키기 위한 용도)
Template Method Pattern
- 메서드에서 알고리즘의 골격을 정의합니다. 알고리즘의 여러 단계 중 일부는 서브클래스에서 구현할 수 있습니다. 템플릿 메서드를 이용하면 알고리즘 구조를 그대로 유지하면서 서브 클래스에서 특정 단계를 재정의할 수 있습니다.(간단히 말하면 공통의 알고리즘 틀을 만들기 위한 것)
Template Method Pattern Summary
- “템플릿 메서드” 에서는 알고리즘의 단계들을 정의하는데, 일부 단계는 서브 클래스에서 구현하도록 할 수 있습니다.
- 템플릿 메서드 패턴은 코드 재사용에 크게 도움이 됩니다.
- 템플릿 메서드가 들어있는 추상 클래스에서는 구상 메서드, 추상 메서드, 훅(hook)를 정의할 수 있습니다.
- 추상메서드는 서브클래스에서 구현합니다.
- 훅은 추상 클래스에 들어있는, 아무 일도 하지 않거나, 기본 행동을 정의하는 메서드로, 서브 클래스에서 오버라이드 할 수 있습니다.
- 서브클래스에서 템플릿 메서드에 들어있는 알고리즘을 함부로 바꾸지 못하게 하고 싶다면 템플릿 메서드를 final 로 선언하면 됩니다.
- 저수준 모듈(하위)은 언제 어떻게 호출할지는 고수준 모듈(상위)에서 결정하는 것이 좋습니다.
- 템플릿 메서드 패턴은 실전에서도 꽤 자주 쓰이지만 반드시 “교과서적인”방식으로 적용되는 것은 아닙니다.
- 스트래티지 패턴과 템플릿 메서드 패턴은 모두 알고리즘을 캡슐화하는 패턴이지만 전자에서는 구성(Composition)을 후자에서는 상속(Inheritance)을 이용합니다.
- 팩토리 메서드 패턴은 특화된 템플릿 메서드 입니다.
Strategy vs Template Method 같은 개념 아니야?
- 스트래티지 패턴은 객체 구성을 통해서 동적으로 알고리즘 구현(Composition)을 선택할 수 있도록 도와줍니다
- 템플릿 메서드 패턴은 알고리즘의 일부를 서브 클래스에서 처리합니다. 이때 상속 개념이 활용 됩니다.(스트래티지는 알고리즘 자체를 서브 클래스에서 선택 혹은 교환 가능한 형태로 만들어 줍니다)
작성중..
'한달에 교양 책 한권' 카테고리의 다른 글
안드로이드 뜻밖의 역사 (0) | 2023.06.19 |
---|---|
금융 초보자가 가장 알고싶은 최다 질문 feat. 크레마S (0) | 2023.05.07 |
돈의 속성 (1) | 2023.04.23 |
좋은 코드, 나쁜 코드 (Good code, Bad code) (0) | 2023.04.16 |
부의 확장 (0) | 2023.04.08 |