[Spring] 디자인 패턴

WOOK JONG KIM·2022년 10월 19일
0

패캠_java&Spring

목록 보기
32/103
post-thumbnail

디자인 패턴이란?

자주 사용하는 설계 패턴을 정형화해서 이를 유형별로 가장 최적의 방법으로 개발 할 수 있도록 정해둔 설계

알고리즘과 유사하지만, 명확하게 정답X -> 프로젝트의 상황에 맞게 적용

장점: 개발자 간의 원활한 소통, 구조 파악 용이, 재사용을 통한 시간 단축, 설계 변경에 대한 유연한 대처

단점 : 초기 투자 비용

GOF 디자인 패턴

소프트웨어 설계시 기존에 경험 중요 -> 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 패턴

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));
    }
}

Adapter 패턴

실생활에서 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 Pattern

Proxy는 대리인 이라는 뜻으로 뭔가를 대신 처리하는 것

Proxy Class 를 통해서 대신 전달하는 형태로 설계되며, 실제 Client는 Proxy로 부터 결과를 받음

Cache 기능으로도 활용 가능 하며 OCPDIP 를 따른다

cache

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();

    }
}

Spring AOP

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());

    }
}

Decorator 패턴

기존 뼈대(클래스)는 유지하되, 이후 필요한 형태로 꾸밀 때 사용하는 패턴

확장이 필요할 때 상속의 대안으로도 활용하며 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();
    }
}

Observer Pattern

변화가 일어 났을 때, 미리 등록된 다른 클래스에 통보해주는 패턴을 구현

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 Pattern

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();

    }
}

Strategy Pattern

전략 패턴이라고 불리며 객체 지향의 꽃

유사한 행위들을 캡슐화하여, 객체의 행위를 바꾸고 싶은 경우 직접 변경하는 것이 아닌 전략만 변경하여, 유연하게 확장 하는 패턴 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);
    }
}
profile
Journey for Backend Developer

0개의 댓글