디자인 패턴은 프로그램을 개발하는 과정에서 빈번하게 발생하는 디자인 문제를 정리해서 상황에 따라 간편하게 적용할 수 있게 정리한 것이다.
소프트웨어 개발은 요구사항 분석 -> 설계 -> 구현 -> 테스트를 반복하게 되는데
이때, 디자인 패턴은 설계에 해당한다.
객체 생성에 관련된 패턴
클래스나 객체를 조합해 더 큰 구조를 만드는 패턴
클래스와 객체들이 상호작용하는 방법과 역할을 분담하는 방법을 다루는 패턴
한 객체의 상태 변화에 따라 다른 객체의 상태도 연동되도록 일대다 객체 의존 관계를 구성하는 패턴
복잡한 객체를 생성하는 클래스와 표현하는 클래스를 분리하여, 동일한 절차에서도 서로 다른 표현을 생성하는 방법이다.
객체를 생성할 때 필요한 인터페이스를 만든다. 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정한다.
팩토리 메소드 패턴을 사용하면 클래스 인스턴스 만드는 일을 서브클래스에게 맡기게 된다. .
객체를 생성하는데 비용이 많이 들고, 비슷한 객체가 이미 있는 경우에 사용한다.
객체를 새로운 객체에 복사하여 필요에 따라 수정한다.
특정 클래스의 객체 인스턴스가 하나만 만들어지도록 해주는 패턴
소프트웨어를 만들다보면 어떠한 클래스의 객체가, 해당 프로세스에 단 하나만 존재해야되는 경우가 생긴다. 예를들어 사용자가 페이지를 다크모드로 설정한다면 다른 페이지로 이동하더라도 다크모드가 유지되어야 한다. 즉, 어떤 페이지에 있든, setting 객체는 반드시 동일 객체여야 한다.
class FirstPage {
public void setAndPrintSettings() {
Settings settings = new Settings();
settings.darkMode = true;
System.out.println(settings.darkMode); // true 출력
}
}
class SecondPage {
public void printSettings() {
Settings settings = new Settings();
System.out.println(settings.darkMode); // false출력
}
}
class Settings{
boolean darkMode = false;
}
위에서 FirstPage의 settings객체와 SecondPage의 settings객체는 서로 다른 메모리를 주소에 할당되어 있다. 즉, 서로 다른 객체이다. 따라서 FirstPage에서 darkMode로 변경해주어도 SecondPage에서는 적용이 안 된다.
class FirstPage {
public void setAndPrintSettings() {
private Settings settings = Settings.getSetting(); // 그때그때 객체를 생성하지 않음
settings.darkMode = true;
System.out.println(settings.darkMode); // true 출력
}
}
class SecondPage {
public void printSettings() {
private Settings settings = Settings.getSetting(); // static메서드 가져옴
System.out.println(settings.darkMode); // true 출력 settings객체는 단 하나만 만들어졌기 때문
}
}
class Settings{
static boolean darkMode = false; //변수를 static으로 바꾸어줌
private Settings(){};// 생성자를 private으로 감싸줌
private static Settings settings = null;
public static Settings getSetting() { //메서드또한 static임
if(settings == null) {
settings = new Settings(); // 객체는 단 한 번 만들어짐
}
return settings;
}
static boolean getDarkMode(){return darkMode;}
}
static으로 설정된 객체는 메모리의 지정된 공간에 단 하나만 존재할 수 있다.
setting객체가 static으로 선언되었을 때,
즉, FirstPage에서 setting 객체의 darkmode를 true로 변경해주면
SecondPage에서도 그 setting 객체의 darkmode값을 가져와 true라고 읽을 수 있다는 것이다.
싱글턴을 잘 못사용하다보면 멀티 쓰레딩환경에서 오류가 발생할 수 있다.
Adapter: 형식이 다른 두 도구 사이에 연결되어 그 둘이 호환될 수 있도록 하는 도구
인터페이스가 서로 다른 객체들이 같은 형식 아래 작동할 수 있도록 하는 역할을 한다.
구현부에서 추상층을 분리하여 각자 독립적으로 변형할 수 있게 하는 패턴이다.
객체를 트리구조로 구성하여 계층구조를 구현할 때 사용한다.
객체에 추가 요소를 동적으로 더할 수 있고, 서브 클래스를 만들때 보다 더 유연하게 기능을 확장할 수 있다.
여러 클래스의 객체들을 복합적으로 사용하여야 수행할 수 있는 작업이 있을 때, 클래스에 하나하나 접근하여 일일이 객체를 만들고 이를 연결하는 것은 상당히 비효율적이다.
작업을 실행하는 사용자 측에서 이러한 복잡한 연결관계를 알 필요가 없도록 프랑스어로 외벽을 뜻하는 facade class 뒤에 숨기는 것이다.11. FlyWeight
클래스의 객체 한 개만 가지고 여러 개의 가상 인스턴스를 갖고 싶을 때 사용하는 패턴이다.
인스턴스를 가능한대로 공유시켜 new연산자를 통한 메모리 낭비를 줄이는 방식이다.
특정 객체로의 접근을 제어하고, 대변하는 객체를 제공한다.
유튜브의 썸네일에 마우스를 가져가면, 유튜브의 프리뷰를 확인 할 수 있다.
제목을 화면에 나타내는 건 가벼운 작업이지만, 프리뷰를 보여주기 위해서는 영상데이터를 받아와야 한다. 썸네일을 담당하는 객체는 제목과 프리뷰를 두 메소드(제목 보여주기, 프리뷰보여주기)를 통해 각각 보여주도록 하되, 썸네일이 처음 화면에 나타날 때는 일단 제목만 보여줄 수 있는 프록시로 생성되게 하고, 프리뷰로 보여주는 무거운 작업은 실제 클래스가 담당하도록 해서, 프록시 객체로 생성된 썸네일에 커서를 올리면, 실제 클래스가 호출되어 프리뷰를 보여주게 한다.
요구를 처리할 수 없는 요청이 들어온다면 수신하는 객체가 다음 객체에게 책임을 넘김으로써 최종적으로 요청을 처리할 수 있는 객체에 의해 처리가 가능하도록 하는 패턴이다.
Strategy 패턴은 같은 일을 하되,알고리즘이나 방식이 교체 되는 거라면,
Command 패턴은 그 할 일 자체가 다른 것이다.
command패턴은 매우 다양하게 작성된다.
Strategy 패턴처럼 모드 변경에 따라 명령을 교체하는 식으로 작성하기도 하고, 여러 명령들을 목록으로 실어 보내 차례로 실행시킬 수도 있다.
간단한 언어의 문법을 정의하고 해석하는 패턴이다.
컬렉션의 구현 방법을 노출하지 않으면서 집합체 내의 모든 항목에 접근하는 방법 제공한다.
객체들의 집합이 상호작용하는지를 함축해놓은 객체를 정의한다.
객체의 상태 정보를 저장하고 사용자의 필요에 의해 원하는 시점의 데이터를 복원할 수 있는 패턴이다.
한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 일대다 의존성을 정의한다.
특정 상태마다 다르게 할 일을 특정 상태마다 실행시 할 일과 함께 하나하나 모듈화해서 지정할 때 사용한다.
ex) 앱 화면의 다크모드 여부를 켰다 껐다 한다면 다크모드에서 라이트 모드가 되고, 라이트 모드에서는 다크모드로 상태가 전환된다.
Strategy 패턴이 지정된 특정 메소드가 모듈화된 모드에 따라 다르게 실행되도록 한다면,
State는 메서드가 실행될 때, 모드도 전환되도록 하는 것이다.
- 프로그램 실행 중 모드가 바뀔 때마다 전략이 수정되는 경우
모듈마다의 동작 하나하나를 모듈로 따로 분리하여, 모드가 바뀔때마다 실행되는 모듈을 교체하는 방식으로 코드를 방식이다.
interface SearchStrategy{
public void search();
}
class SearchStrategyAll implements SearchStrategy {
public void search() {System.out.println("Search All");}
}
class SearchStrategyImage implements SearchStrategy {
public void search() {System.out.println("Search image");}
}
class SearchStrategyMap implements SearchStrategy {
public void search() {System.out.println("Search map");}
}
public class SearchButton{
SearchStrategy searchStrategy = new SearchStrategyAll();
public void setSearchStrategy(SearchStrategy _searchStrategy) {
SearchStrategy = _ searchStrategy;// <<모드에 따라 setSearchStrategy을 통해 모듈을 갈아끼운다
}
}
위처럼 SearchStrategey라는 인터페이스와 그 인터페이스를 implements한 SearchStrategyAll, SearchStrategyImage, SearchStrategyMap를 각각 모드에 따라 갈아 끼워서 사용하면 된다.
이처럼 옵션들마다의 행동들을 모듈화하여 독립적이고 상호 교체 가능하게 만든 것이다.
어떤 같은 형식을 지닌 특정 작업들의 세부 방식을 다양화하고자 할때 사용하는 패턴
다양화된 방식을 각각 자식 클래스들에서 오버라이딩하는 방식으로 구현하는 것이다.
이는 상속과 다를바가 없어보이지만, 템플릿 메소드에서의 상속은 일정 형식이 존재한다.
부모 클래스에 전반과정을 수행하는 메인 메소드가 있고, 그 과정 가운데 세부 메소드가 존재한다. 메인 메소드를 호출하면 실행 중에 이 세부 메소드들이 호출되는 형태이다.
자식 과정에서는 그 세부 메소드를 오버라이딩한다.
어떤일을 수행하는 몇 가지 방법이 있고, 그 전반적 과정에 공통된 절차가 있을 때 코드를 효율적으로 짜기 위해 만들어졌다.
데이터 구조와 처리를 분리한다.
개방-폐쇠 원칙을 적용하는 방법 중 한 가지이다.