[참고 링크]
생성 패턴Singleton팩토리 메서드 매턴추상 팩토리 패턴구조 패턴AdapterDecoratorFacadeProxyBridge 패턴행위 패턴ObserverStrategyTemplate Method pattern
5 Design patterns every engineer should know
- Singleton
- Facade
- Bridge
- Strategy
- Observer
생성 패턴
Singleton
- 주로 사용하는 곳은 서로 자원을 공유 할 때 사용하는데, 실물 세계에서는 프린터가 해당되며, 실제 프로그래밍에서는 TCP Socket 통신에서 서버와 연결된 connect 객체에 주로 사용함
public class SocketClient{ private static SocketClient socketClient = null; private SocketClient(){ } public static SocketClient getInstance(){ if(socketClient == null){ socketClient = new SocketClient(); } return socketClient; } }
- 싱글톤 패턴의 문제점
- 객체지향의 장점인 상속과 이를 이용한 다형성을 적용할 수 없음
- 기술적인 서비스만 제공하는 경우라면 상관없겠지만, 애플리케이션의 로직을 담고 있는 일반 오브젝트의 경우 객체지향적 설계의 장점을 적용하기가 어렵다는 점은 심각한 문제
- 싱글톤은 테스트하기 어렵거나 테스트 방법에 따라 아예 테스트가 불가능함.
- 싱글톤은 만들어지는 방식이 제한적이기 때문에 테스트에서 사용될 때 목 오브젝트 등으로 대체하기가 힘듦
- 싱글톤은 초기화 과정에서 생성자 등을 통해 사용할 오브젝트를 다이나믹하게 주입하기도 힘들기 때문에 필요한 오브젝트는 직접 오브젝트를 만들어 사용할 수 밖에 없음
- 테스트는 엔터프라이즈 개발의 핵심이기에 애플리케이션 코드를 싱글톤으로 만들면 테스트를 만드는 데 지장이 있다는 건 큰 단점임
- 서버에서 클래스 로더를 어떻게 구성하고 있느냐에 따라 싱글톤 클래스임에도 하나 이상의 오브젝트가 만들어질 수 있다
- 여러 JVM에 분산돼서 설치가 되는 경우에도 각각 독립적으로 오브젝트가 생기기 때문에 싱글톤으로서의 가치가 떨어짐
- 싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직하지 못하다
- 싱글톤은 사용하는 클라이언트가 정해져 있지 않음. 싱글톤의 스태틱 메소드를 이용해 언제든 싱글톤에 쉽게 접근할 수 있기 때문에 어플리케이션 어디에서나 사용될 수 있고 그러다 보면 자연스럽게 전역 상태로 사용되기 쉬움
- 아무 객체나 자유롭게 접근하고 수정하고 공유할 수 있는 전역 상태를 갖는 것은 객체지향 프로그래밍에서는 권장되지 않는 프로그래밍 모델임
private 생성자를 갖고 있기 때문에 상속할 수 없다
싱글톤은 테스트하기 힘들다
서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못함
싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직하지 못함
- Prototype
- Builder
팩토리 메서드 매턴
Factory Method추상 팩토리 패턴
Abstract Factory- Chaining
구조 패턴
프로그램 내의 자료구조나 인터페이스 구조 등 프로그램 구조를 설계하는데 활용 될 수 있는 패턴. 클래스, 객체들의 구성을 통해서 더 큰 구조를 만들 수 있게 해준다. 큰 규모의 시스템에서는 많은 클래스들이 서로 의존성을 가지게 되는데, 이런 복잡한 구조를 개발 하기 쉽게 만들어 주고, 유지 보수 하기 쉽게 만들어줌
Adapter
- Adapter는 실생활에서는 110v를 220v로 변경해주거나, 반대로 해주는 돼지코라고 불리는 변환기를 예로 들 수 있음. 호환성이 없는 기존 클래스의 인터페이스를 변환하여 재사용 할 수 있도록 한다. SOLID 중 개방 폐쇄 원칙(OCP)를 따름
public interface Electronic110V{ void powerOn(); } public interface Electronic220V { void connect(); } //110v 를 220v 로 바꿔주는 Adapter 패턴 public class SocketAdapter implements Electronic110V{ private Electronic220V electronic220V; public SocketAdapter(Electronic220V electronic220V){ this.electronic220V = electronic220V; } @Override public void powerOn(){ electronic220V.connect(); } } // 사용 시, public static void connect(Electronic110V electronic110V){ electronic110V.powerOn(); } public static main(){ HairDryer hairDryer = new HairDryer(); // Electronic110V interface 구현 connect(hairDryer); Cleaner cleaner = new Cleaner(); // Electronic220V interface 구현 Electronic110V adapter = new SocketAdapter(cleaner); // 이런식으로 220v를 110v로 바꿔줌 connect(adapter); }
- Composite
- Bridge
Decorator

- 예를 들어 소스코드를 출력하는 기능을 가진 핵심기능이 있을 때, 이 클래스에 데코레이터 개념을 부여해서
타깃
(핵심기능) 과 같은 인터페이스를 구현하는 프록시를 만들 수 있음
- 예로, 소스코드에 라인넘버를 붙여준다거나, 문법에 따라 색을 변경해주거나, 특정 폭으로 소스를 잘라주거나 하는 등의 부가기능들. 각각 프록시로 만들어 두고, 위 그림과 같이 런타임에 적절한 순서로 조합해서 사용
- 프록시로서 동작하는 각 데코레이터는 위임하는 대상에도 인터페이스로 접근하기에 자신이 최종 타깃으로 위임하는지, 다음 단계의 데코레이터 프록시로 위임하는지 알 수 없음
- 자바 IO 패키지의
InputStream
과OutputStream
구현 클래스가 데코레이터 패턴이 사용된 대표적인 예시임
데코레이터 패턴은 기존 뼈대(클래스)는 유지하되 이후 필요한 형태로 꾸밀 때 사용한다. 확장이 필요한 경우 상속의 대안으로도 활용 한다. SOLID 중에서 개방 폐쇄 원칙(OCP)과 의존 역전 원칙(DIP)를 따른다.
public interface ICar { int getPrice(); void showPrice(); } public class Audi implements ICar{ private int price; public Audi(int price){ this.price; } @Override public int getPrice(){ return price; } @Override public void showPrice(){ sout("audi의 가격은" + this.price+" 원 입니다."); } } public class AudiDecorator implements ICar{ protected ICar audi; protected String modelName; protected int modelPrice; public AudiDecorator(ICar audi, String modelName, int modelPrice){ this.audi = audi; this.modelName = modelName; this.modelPrice = modelPrice; } @Override public int getPrice(){ return audi.getPrice() + modelPrice; } @Override public void showPrice(){ sout(this.modelName +"의 가격은 " + this.price+" 원 입니다."); } } public class A3 extends AudiDecorator{ public A3(ICar audi, String modelName){super(audi, modelName, 2000);} }
Facade
Facade는 건물의 앞쪽 정면이라는 뜻을 가짐. 여러 개의 객체와 실제 사용하는 서브 객체의 사이에 복잡한 의존 관계가 있을 때 중간에 facade라는 객체를 두고, 여기서 제공하는 interface만을 활용하여 기능을 사용하는 방식이다. Facade는 자신이 가지고 있는 각 클래스의 기능을 명확히 알아야 한다.

public class SftpClient{ private Ftp ftp; private Reader reader; private Writer writer; public SftpClient(Ftp ftp, Reader reader, wWriter writer){ this.ftp = ftp; this.reader = reader; this.wrtiiter = writer; } public void connect(){ ftp.connect(); ftp.moveDirectory(); writer.fileConnect(); reader.fileConnect(); } public void disConnect(){ } } // 이렇게 아래 3개의 클래스를 한번에 묶어서 활용할 수 있도록 구성함 public class Ftp{ private String host; private int port; private String path; public Ftp(String host, int port, String path){ this.host = host; this.port = port; this.path = path; } public void connect(){ sout(String.format("FTP Host : %s Port : %s로 연결합니다.", host, port)); } public void moveDirectory(){ sout("path : "+path+ "로 이동 합니다."); } public void disConnect(){ sout("연결을 종료합니다."); } } public class Writer{ private String fileName; public Writer(String fileName){ this.fileName = fileName; } public void fileConnect(){ sout(String.format("Writer %s로 연결 합니다.", fileName)); String format = String.format("", } public void write(){ } public void fileDisconnect(){ } } public class Reader{ private String fileName; public Reeader(String fileName){ this.fileName = fileName; } public void fileConnect(){ sout(String.format("Reader %s로 연결 합니다.", fileName)); String format = String.format("", } public void fileRead(){ } public void fileDisconnect(){ } }
- 주의할 점
- Facade객체를 너무 단순화 해버리면 유용하지 않을 수 있음
- 너무 세세하게 해버리면 generalize를 할 수가 없음. 그냥 일일히 다 구현하는것과 차이가 없게됨
- Flyweight
Proxy
대리인이라는 뜻으로, 뭔가를 대신해서 처리하는 것. Proxy Class를 통해서 대신 전달 하는 형태로 설계되며, 실제 Client는 Proxy로 부터 결과를 받는다. Cache의 기능으로도 활용이 가능함. Spring에서는 AOP에서 이를 사용함. SOLID중 개방폐쇄원칙(OCP)와 의존 역전 원칙(DIP)를 따름
public class Html { private String url; public Html(String url){ this.url = url; } } public interface IBrowser{ Html show(); } public class Browser implements IBrowser{ private String url; public Browser(String url){ this.url = url; } @Override public Html show(){ sout("browser loading html from : " + url); return new Html(url); } } public class BrowserProxy implements IBrowser{ private String url; private Html html; public BrowserProxy(String url){ this.url = url; } @Override public Html show(){ if(html == null){ this.html = new Html(url); sout("BrowserProxy loading html from : "+url); } sout("BrowserProxy use cache html : "+url); return html; } }
Bridge 패턴
행위 패턴
반복적으로 사용하는 객체들의 상호작용을 패턴화 한 것. 클래스나 객체들이 상호 작용하는 방법과 책임을 분산하는 방법을 제공 한다. 행위 패턴은 행위 관련 패턴을 사용하여 독립적으로 일을 처리하고자 할 때 사용
- Template Method
- Interpreter
- Iterator
Observer
관찰자 패턴은 변화가 일어났을 때, 미리 등록된 다른 클래스에 통보해주는 패턴을 구현한 것이다. 많이 보이는 곳은 event listener에서 해당 패턴을 사용함
public interface IButtonListener { void clickEvent(String event); } public class Button{ private String name; private IButtonListener buttonListener; public Button(String name){ this.name = name; } public void click(String message){ buttonListener.clickEvent(message); } public void addListener(IButtonListener buttonListener){ this.buttonListener = buttonListener; } } public static main(){ Button button = new Button("버튼"); button.addListener(new IButtonListener(){ @Override public void clickEvent(String event){ sout(event); } } button.click("메시지 전달 : click 1"); button.click("메시지 전달 : click 2"); button.click("메시지 전달 : click 3"); button.click("메시지 전달 : click 4"); }
Strategy
전략 패턴으로 불리며, 객체지향의 꽃이다. 자신의 기능 맥락(context)에서 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴. SOLID 중에서 개방폐쇄 원칙(OCP)과 의존 역전 원칙(DIP)를 따름
- 전략 메서드를 가진 전략 객체(Normal Strategy, Base64 Strategy)
- 전략 객체를 사용하는 컨텍스트(Encoder)
- 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트
- 컨텍스트를 사용하는 클라이언트는 컨텍스트가 사용할 전략을 컨텍스트의 생성자 등을 통해 제공해주는 게 일반적임
public interface EncodingStrategy{ String encode(String text); } public class NormalStrategy implements EncodingStrategy{ } public class Base64Strategy implements EncodingStrategy{ @Override public String encode(String text){ return Base64.getEncoder().encodeToString(text.getBytes()); } } public class Encoder{ private EncodingStrategy encodingStrategy; public String getMessage(String message){ return this.encodingStrategy.encode(message); } public void setEncodingStrategy(EncodingStrategy encodingStrategy){ this.encodingStrategy = encodingStrategy; } } public static main(){ Encoder encoder = new Encoder(); NormalStrategy normal = new NormalStragtegy(); Base64Strategy base64 = new Base64Strategy(); String message = "hello java"; encoder.setEncodingStrategy(base64); encoder.getMessage(message); encoder.setEncodingStrategy(normal); encoder.getMessage(message); }
Template Method pattern
- 슈퍼클래스에 기본적인 로직의 흐름을 만들고, 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 protected 메소드 등으로 만든 뒤 서브클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하도록 하는 방법 — 스프링에서 애용되는 디자인 패턴임
- 상속을 통해 확장을 꾀하기 때문에 관계에 대한 유연성이 떨어짐
public abstract class Super{ public void templateMethod(){ // 기본 알고리즘 코드 /* 기본 알고리즘 골격을 담은 메소드를 템플릿 메소드라 함 템플릿 메소드는 서브클래스에서 오버라이드하거나 구현할 메소드를 사용함 */ hookMethod(); abstractMethod(); ... } protected void hookMethod(){} public abstract void abstractMethod(); }
- Visitor
- Chain of responsibility
- Command
- Mediator
- State
- Memento