자주 사용하는 설계 패턴을 정형화
해서 이를 유형별로 가장 최적의 방법으로 개발 할 수 있도록 정해둔 설계
알고리즘과 유사하지만, 명확하게 정답X -> 프로젝트의 상황에 맞게 적용
장점
: 개발자 간의 원활한 소통, 구조 파악 용이, 재사용을 통한 시간 단축, 설계 변경에 대한 유연한 대처
단점
: 초기 투자 비용
소프트웨어 설계시 기존에 경험 중요 -> but 모든 사람이 다양한 경험 X
이러한 지식 공유위해 나온 것이 Gang of Four의 디자인 패턴
총 23가지
객체를 생성
하는 것과 관련된 패턴, 객체의 생성과 변경이 전체 시스템에 미치는 영향을 최소화 하고, 코드의 유연성을 높여준다
종류 : Factory Method, Singleton, Prototype, Builder, Abstract Factory, Chaining
프로그램 내의 자료구조나 인터페이스 구조 등 프로그램 구조를 설계
하는데 활용 될 수 있는 패턴
클래스,객체들의 구성을 통해서 -> 더 큰 구조 만들 수 있게 해줌
큰 규모의 시스템에서는 많은 클래스들이 서로 의존성
가짐 -> 이러한 복잡한 구조를 개발하기 쉽게 만들어 주고, 유지 보수 하기 쉽게 해줌
종류 : Adapter, Composite, Bridge, Decorator, Facade, Flyweight, Proxy
반복적으로 사용되는
객체들의 상호작용을 패턴화
한 것
클래스나 객체들이 상호작용하는 방법과 책임을 분산하는 방법 제공
서로 독립적으로 일을 처리하고자 할 때 사용
종류 : Template Method, Interpreter, Iterator, Observer, Strategy, Visitor, Chain of responsibility, Command, Mediator, State, Memento
Singleton 패턴은 어떠한 클래스(객체)가 유일하게 1개만 존재해야 할 때 사용
서로 자원을 공유할 때 주로 사용함(ex : 프린터)
실제 프로그래밍에서는 TCP Socket 통신에서 서버와 연결된 connect 객체에 주로 사용
생성자는 private
, getInstance
메서드를 통해 생성되어 있는 객체를 가져오거나 없을 경우 생성
package singleton;
public class SocketClient {
private static SocketClient socketClient = null;
private SocketClient(){}
public static SocketClient getInstance(){
if(socketClient == null){
socketClient = new SocketClient();
}
return socketClient;
}
public void connect(){
System.out.println("connect");
}
}
package singleton;
// Bclass 또한 생성 방법 동일
public class Aclass {
private SocketClient socketClient;
public Aclass(){
this.socketClient = SocketClient.getInstance();
}
public SocketClient getSocketClient(){
return this.socketClient;
}
}
package singleton;
public class Main {
public static void main(String[] args) {
Aclass aclass = new Aclass();
Bclass bclass = new Bclass();
SocketClient aClient = aclass.getSocketClient();
SocketClient bClient = bclass.getSocketClient();
System.out.println("두개의 객체가 동일한가");
System.out.println(aClient.equals(bClient));
}
}
실생활에서 100v->220v 변경 혹은 반대로 해주는 변환기를 예로 들수 있다.
호환성이 없는 기존 클래스의 인터페이스를 변환하여 재사용 할 수 있도록 한다.
SOLID 중 OCP 원칙
따름
package Adapter;
public interface Electronic110V {
void powerOn();
}
package Adapter;
public interface Electronic220V {
void connect();
}
package Adapter;
public class HairDryer110V implements Electronic110V {
@Override
public void powerOn() {
System.out.println("헤어드라이기 110V");
}
}
package Adapter;
public class Cleaner220V implements Electronic220V{
@Override
public void connect() {
System.out.println("청소기 220V ON");
}
}
package Adapter;
public class AirConditioner220V implements Electronic220V{
@Override
public void connect() {
System.out.println("에어컨 220V on");
}
}
package Adapter;
public class SocketAdapter implements Electronic110V{
private Electronic220V electronic220V;
public SocketAdapter(Electronic220V electronic220V){
this.electronic220V = electronic220V;
}
@Override
public void powerOn() {
electronic220V.connect();
}
}
package Adapter;
public class Main {
public static void main(String[] args){
HairDryer110V hairDryer = new HairDryer110V();
connect(hairDryer);
Cleaner220V cleaner = new Cleaner220V();
Electronic110V adapter = new SocketAdapter(cleaner);
connect(adapter);
AirConditioner220V airConditioner220V = new AirConditioner220V();
Electronic110V airAdapter = new SocketAdapter(airConditioner220V);
connect(airAdapter);
}
// 콘센트 ( 일자 모양 110V)
public static void connect(Electronic110V electronic110V){
electronic110V.powerOn();
}
}
Proxy는 대리인 이라는 뜻으로 뭔가를 대신 처리하는 것
Proxy Class 를 통해서 대신 전달하는 형태
로 설계되며, 실제 Client는 Proxy로 부터 결과를 받음
Cache 기능으로도 활용 가능
하며 OCP
와 DIP
를 따른다
package Proxy;
public interface IBrowser {
Html show();
}
package Proxy;
public class Html {
private String url;
public Html(String url){
this.url = url;
}
}
package Proxy;
public class Browser implements IBrowser{
private String url;
public Browser(String url){
this.url = url;
}
@Override
public Html show() {
System.out.println("browser loading html from :" + url);
return new Html(url);
}
}
package Proxy;
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);
System.out.println("Browser loading html from:" + url);
}
System.out.println("BrowerProxy use cache html:" + url);
return html;
}
}
package Proxy;
public class Main {
public static void main(String[] args){
/**
Browser browser = new Browser("www.naver.com");
그냥 show 여러번 할 시 호출 할때마다 url 로딩
browser.show();**/
IBrowser browser = new BrowserProxy("www.naver.com");
browser.show();
browser.show();
browser.show();
browser.show();
browser.show();
}
}
AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍
이라고 불린다.
관점 지향은 쉽게 말해 어떤 로직을 기준으로 핵심적인 관점
, 부가적인 관점
으로 나누어서 보고 그 관점을 기준으로 각각 모듈화하겠다는 것
package AOP;
import Proxy.Html;
import Proxy.IBrowser;
public class AopBrowser implements IBrowser {
private String url;
private Html html;
private Runnable before;
private Runnable after;
public AopBrowser(String url, Runnable before, Runnable after){
this.url = url;
this.before = before;
this.after = after;
}
@Override
public Html show() {
before.run();
if(html == null){
this.html = new Html(url);
System.out.println("AOPBrowser html loading from" + url);
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
after.run();
System.out.println("AopBrower html cache:" + url);
return null;
}
}
package Proxy;
import AOP.AopBrowser;
import java.util.concurrent.atomic.AtomicLong;
public class Main {
public static void main(String[] args){
AtomicLong start = new AtomicLong();
AtomicLong end = new AtomicLong();
IBrowser aopBrowser = new AopBrowser("www.naver.com",
()->{
System.out.println("before");
start.set(System.currentTimeMillis());
},
()->{
long now = System.currentTimeMillis();
end.set(now - start.get());
});
aopBrowser.show();
System.out.println("Loading time:"+end.get());
aopBrowser.show();
System.out.println("Loading time:"+end.get());
}
}
기존 뼈대(클래스)는 유지하되, 이후 필요한 형태로 꾸밀 때 사용
하는 패턴
확장이 필요할 때 상속의 대안으로도 활용하며 OCP
, DIP
를 따른다
package Decorator;
public interface ICar {
int getPrice();
void showPrice();
}
package Decorator;
public class Audi implements ICar{
private int price;
public Audi(int price){
this.price = price;
}
@Override
public int getPrice() {
return price;
}
@Override
public void showPrice() {
System.out.println("audi의 가격은 " + this.price+"원 입니다.");
}
}
package Decorator;
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() {
System.out.println(modelName + "의 가격은" + getPrice() + "원 입니다");
}
}
package Decorator;
//A4 는 플러스 2000 , A5 -> 3000
public class A3 extends AudiDecorator {
public A3(ICar audi, String modelName) {
super(audi, modelName, 1000);
}
}
package Decorator;
public class Main {
public static void main(String[] args){
ICar audi = new Audi(1000);
audi.showPrice();
//a3
ICar audi3 = new A3(audi,"A3");
audi3.showPrice();
//a4
ICar audi4 = new A4(audi, "A4");
audi4.showPrice();
//a5
ICar audi5 = new A5(audi, "A5");
audi5.showPrice();
}
}
변화가 일어 났을 때
, 미리 등록된 다른 클래스에 통보
해주는 패턴을 구현
Event listener
에서 해당 패턴 사용
package Observer;
public interface IButtonListener {
void clickEvent(String event);
}
package Observer;
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 iButtonListener){
this.buttonListener = iButtonListener;
}
}
package Observer;
public class Main {
public static void main(String[] args){
Button button = new Button("버튼");
button.addListener(new IButtonListener() {
@Override
public void clickEvent(String event) {
System.out.println(event);
}
});
button.click("메세지 전달: click1");
button.click("메세지 전달: click2");
button.click("메세지 전달: click3");
button.click("메세지 전달: click4");
}
}
Facade : 건물의 앞쪽 정면 뜻함
여러 개의 객체
와 실제 사용하는 서브 객체
의 사이에 복잡한 의존관계
가 있을 때, 중간에 facade라는 객체
를 두고, 여기저 제공하는 interface만을 활용하여 기능을 사용
하는 방식
Facade는 자신이 가지고 있는 각 클래스의 기능을 명확히 알아야 함
package Facade;
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(){
System.out.println("FTp host:" + host + "Port :" + port+"로 연결 합니다");
}
public void moveDirectory(){
System.out.println("Path:" + path + "로 이동합니다.");
}
public void disConnect(){
System.out.println("FTP 연결을 종료합니다.");
}
}
package Facade;
public class Reader {
private String fileName;
public Reader(String fileName){
this.fileName = fileName;
}
public void fileConnect(){
String msg = String.format("Reader %s로 연결합니다",fileName);
System.out.println(msg);
}
public void fileRead(){
String msg = String.format("Reader %s의 내용을 읽어옵니다",fileName);
System.out.println(msg);
}
public void fileDisconnect(){
String msg = String.format("Reader %s로 연결 종료합니다",fileName);
System.out.println(msg);
}
}
package Facade;
public class Writer {
private String fileName;
public Writer(String fileName){
this.fileName = fileName;
}
public void fileConnect(){
String msg = String.format("Writer %s로 연결합니다",fileName);
System.out.println(msg);
}
public void fileDisConnect(){
String msg = String.format("Writer %s로 연결 종료합니다",fileName);
System.out.println(msg);
}
public void write(){
String msg = String.format("Writer %s로 파일을 쓰기를 합니다", fileName);
System.out.println(msg);
}
}
package Facade;
public class SFtpClient {
private Ftp ftp;
private Reader reader;
private Writer writer;
public SFtpClient(Ftp ftp, Reader reader, Writer writer){
this.ftp = ftp;
this.reader = reader;
this.writer = writer;
}
public SFtpClient(String host, int port, String path, String fileName){
this.ftp = new Ftp(host,port,path);
this.reader = new Reader(fileName);
this.writer = new Writer(fileName);
}
public void connect(){
ftp.connect();
ftp.moveDirectory();
writer.fileConnect();
reader.fileConnect();
}
public void disConnect(){
writer.fileDisConnect();
reader.fileDisconnect();
ftp.disConnect();
}
public void read(){
reader.fileRead();
}
public void write(){
writer.write();
}
}
package Facade;
public class Main {
public static void main(String[] args){
// Ftp ftpClient = new Ftp("www.foo.co.kr", 22, "/hone/etc");
// ftpClient.connect();
// ftpClient.moveDirectory();
//
// Writer writer = new Writer("text.tmp");
// writer.fileConnect();;
// writer.write();
//
// Reader reader = new Reader("text.tmp");
// reader.fileConnect();
// reader.fileRead();
//
// reader.fileDisconnect();
// writer.fileDisConnect();
// ftpClient.disConnect();
SFtpClient sFtpClient = new SFtpClient("www.foo.co.kr", 22, "/home/etc", "text.tmp");
sFtpClient.connect();
sFtpClient.write();
sFtpClient.read();
sFtpClient.disConnect();
}
}
전략 패턴이라고 불리며 객체 지향의 꽃
유사한 행위들을 캡슐화
하여, 객체의 행위를 바꾸고 싶은 경우 직접 변경하는 것이 아닌 전략만 변경
하여, 유연하게 확장 하는 패턴 SOLID 중에서 OCP
, DIP
전략 메서드를 가진 전략 객체(Normal Strategy, Base64 Strategy)
전략 객체를 사용하는 컨텍스트(Encoder)
전략 객체를 생성해 컨텍스트에 주입하는 클라이언트(main)
package Strategy;
public interface EncodingStrategy {
String encode(String text);
}
public class NormalStrategy implements EncodingStrategy{
@Override
public String encode(String text) {
return text;
}
}
package Strategy;
public class AppendStrategy implements EncodingStrategy{
@Override
public String encode(String text) {
return "ABCD"+ text;
}
}
package Strategy;
import java.util.Base64;
public class Base64Strategy implements EncodingStrategy{
@Override
public String encode(String text) {
return Base64.getEncoder().encodeToString(text.getBytes());
}
}
package Strategy;
public class Encoder {
private EncodingStrategy encodingStrategy;
public String getMessage(String message){
return this.encodingStrategy.encode(message);
}
public void setEncodingStrategy(EncodingStrategy encodingStrategy) {
this.encodingStrategy = encodingStrategy;
}
}
package Strategy;
public class Main {
public static void main(String[] args){
Encoder encoder = new Encoder();
// 전략 각각 생성
EncodingStrategy base64 = new Base64Strategy();
EncodingStrategy normal = new NormalStrategy();
String message = "hello java";
encoder.setEncodingStrategy(base64);
String base64Result = encoder.getMessage(message);
System.out.println(base64Result);
encoder.setEncodingStrategy(normal);
String normalResult = encoder.getMessage(message);
System.out.println(normalResult);
encoder.setEncodingStrategy(new AppendStrategy());
String appendResult = encoder.getMessage(message);
System.out.println(appendResult);
}
}