minlog
article thumbnail
Published 2023. 2. 23. 22:17
디자인 패턴 BackEnd/JavaProgram

 

디자인패턴

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

 

💡 Gof

소프트웨어 설계를 할때 기존 경험이 매우 중요한데, 이러한 지식을 공유하기 위해 나온 디자인 패턴.

 

💡 디자인패턴 장점 / 단점

장점 단점
개발자 간의 원활한 소통 객체 지향 설계 / 구현
소프트웨어 구조파악 용이 초기 투자 비용 부담
재 사용을 통한 개발 시간 단축  
설계 변경 요청에 대한 유연한 대처  

 

 

💡 디자인 패턴 분류 

생성패턴  /  구조패턴  /  행위패턴

* CLICK!! 단어를 클릭하면  바로 해당설명으로 갈 수 있습니다.

 

 


 

1. 생성 패턴

객체를 생성하는 것과 관련된 패턴

객체의 생성과 변경이 전체 시스템에 미치는 영향을 최소화 하고 코드의 유연성을 높인다.

 

🍫 종류 : Factory Method, Singleton, Prototype, Builder, Abstract Factory, Chaning

 

1) 싱글톤 패턴 ( Singleton pattern )

어떤 클래스(객체) 가 유일하게 1개만 존재해야 할때

ex) 프린트

 

🍫 예제

package com.company.design.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");
    }
}
 

[ JAVA ] 싱글톤 패턴 (Singleton pattern)

싱그톤 패턴이란? 한 번의 메모리만 할당하고 그 할당한 메모리에 대해 객체로 관리하기 위함입니다. 프로그램에서 인스턴스가 단 한 개만 생성되어야 하는 경우 사용하는 디자인 패턴 ex ) Calend

jimin-log.tistory.com

 

 

2) 어댑터 패턴 ( Adapter pattern )

호환성이 없는 기존 클래스의 인터페이스를 변환하여 재사용할 수 있도록 한다.

SOLID중에서 개방폐쇄 원칙(OCP)을 따른다.

ex) 돼지코 ( 220v를 110v로 변경)

 

💡 필요 

(1) 각 필요한 기능을 정의한 인터페이스

(2) 각 기능을 필요한 기능을 받아서 사용이되는 객체 (HairDryer,AirConditioner)

(3) 기본 인터페이스를 받은 객체로, 호환성이 없는 클래스를 변환하여 실행 시키는 객체 (SocketAdapter)

(4) 기능을 받는 클라이언트 (Main)

 

 

🍫 예제

인터페이스 

(1) 각 필요한 기능을 정의한 인터페이스

package com.company.design.adapter;

public interface Electronic110V {
    void powerOn();
}
package com.company.design.adapter;

public interface Electronic220V {
    void connect();
}

 

클래스

(2) 각 기능을 필요한 기능을 받아서 사용이되는 객체 (HairDryer,AirConditioner)

- 110v를 사용하는 헤어드라이기

package com.company.design.adapter;

import java.sql.SQLOutput;

public class HairDryer implements Electronic110V{

    @Override
    public void powerOn() {
        System.out.println("헤어드라이기 110v on");
    }
}

- 220 볼트를 사용하는 에어컨

package com.company.design.adapter;

public class AirConditioner implements Electronic220V{
    @Override
    public void connect() {
        System.out.println("에어컨 220v on");
    }
}

 

(3) 기본 인터페이스를 받은 객체로, 호환성이 없는 클래스를 변환하여 실행 시키는 객체 (SocketAdapter)

기본 110v를 받는 어댑터에서 220v를 선언하여 생성자에 220v를 넣어준다.

110v에서 오버라이딩 가져온 전원을 켜주는 메서드를에  220v의 전원을 켜주는 메서드를 추가한다. connect()

package com.company.design.adapter;

public class SocketAdapter implements Electronic110V{
    private  Electronic220V electronic220V;
    public SocketAdapter(Electronic220V electronic220V){
        this.electronic220V = electronic220V;
    }
    @Override
    public void powerOn() {
        electronic220V.connect();
    }
}

 

메인

(4) 기능을 받는 클라이언트 (Main)

집에서 사용하는 기본 콘텐츠는 110v 이다.

하지만 220v를 사용해야할때 어댑터를 사용.  220v를 110v로 변환하여 connect가 가능하다.

package com.company.design;

import com.company.design.adapter.*;
import com.company.design.singleton.AClazz;
import com.company.design.singleton.BClazz;
import com.company.design.singleton.SocketClient;

public class Main {
    public static void main(String[] args) {
        //110v
        HairDryer hairDryer = new HairDryer();
        connect(hairDryer);
        //220v
        Cleaner cleaner = new Cleaner();
        //어뎁터를 통해 연결
        Electronic110V adapter = new SocketAdapter(cleaner);
        connect(adapter);

        AirConditioner airConditioner = new AirConditioner();
        Electronic110V airAdapter = new SocketAdapter(airConditioner);
        connect(airAdapter);
    }

    //콘텐츠
    public static void connect(Electronic110V electronic110V){
        electronic110V.powerOn();
    }

}

 


 

2. 구조 패턴

프로그램 내의 자료구조나 인터페이스 구조 등 프로그램 구조 설계하는데 사용

 

🍫 종류 : Adapter, Composite, Bridge, Decorator, Facde, Flyweight, Proxy

 

 

1) 프록시 패턴 ( Proxy pattern )

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

프록시 클래스를 통해 대신 전달하는 형태로 설계되며, 실제 클라이언트는 프록시로부터 결과를 받는다.

SOLID 중에서 개방폐쇄 원칙 (OCP)과 의존 역전 원칙 (DIP)를 따른다.

ex) Cache 기능으로도 활용이 가능

 

 

💡 필요 

(1) 필요한 기능을 정의한 인터페이스

(2) 각 기능을 하는 객체 (Html ,Browser)

(3) 대신 전달해주는 기능을 가진 객체 (BrowserProxy)

(4) 기능을 받는 클라이언트 (Main)

 

 

🍫 예제

인터페이스 

(1) 필요한 기능을 정의한 인터페이스

브라우저는 html을 보여주는 기능

package com.company.design.proxy;

public interface IBrower {
    Html show();
}

 

클래스

(2) 각 기능을 하는 객체 (Html ,Browser)

package com.company.design.proxy;

public class Html {
    private String url;
    public Html(String url){
        this.url = url;
    }
}
package com.company.design.proxy;

public class Browser implements IBrower{
    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);
    }
}

 

클래스

(3) 대신 전달해주는 기능을 가진 객체 (BrowserProxy)

캐시 기능을 가진 브라우저

package com.company.design.proxy;

public class BrowserProxy implements IBrower{
    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("BrowserProxy loading html from : " + url);
        }
        System.out.println("BrowserProxy use cache html : " + url);
        return html;
    }
}

 

메인

(4) 기능을 받는 클라이언트 (Main)

- 기본 브라우저 사용

package com.company.design;

import com.company.design.proxy.Browser;
import com.company.design.proxy.BrowserProxy;

public class MainProxy {
    public static void main(String[] args){
        
        Browser browser = new Browser("www.naver.com");
        browser.show();
        browser.show();
        browser.show();
    }
}

실행 결과

 

- Proxy 브라우저 사용 : 캐쉬기능이 가능 하다.  처음에만 로딩 , 그 후에는 이미 받은 html이 있다면 자기 자신을 다시 리턴한다.

package com.company.design;

import com.company.design.proxy.Browser;
import com.company.design.proxy.BrowserProxy;

public class MainProxy {
    public static void main(String[] args){
        BrowserProxy browserProxy = new BrowserProxy("www.naver.com");
        browserProxy.show();
        browserProxy.show();
        browserProxy.show();
    }
}

실행 결과

 

 

 

※ AOP 

- 실행시간

- 전후로 작업하고 싶은 부분

- 요청에대한 리퀘스트 리스펀스 정보를 남길때 코드에 개별적으로 하는것이 아니라 일괄적으로 전후 기능을 넣는다...?

 

=>  프록시 패턴으로 유사한 기능을 구현가능

package com.company.design.aop;

import com.company.design.proxy.Html;
import com.company.design.proxy.IBrowser;

public class AppBrowser implements IBrowser {
    private String url;
    private Html html;
    //aop는 관점지향으로 앞과 뒤를 체크
    private Runnable before;
    private Runnable after;

    public AppBrowser(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("AopBrowser html cache : " + url );
        return html;
    }
}
package com.company.design;

import com.company.design.aop.AppBrowser;
import com.company.design.proxy.Browser;
import com.company.design.proxy.BrowserProxy;
import com.company.design.proxy.IBrowser;

import java.util.concurrent.atomic.AtomicLong;

public class MainProxy {
    public static void main(String[] args){
        AtomicLong start = new AtomicLong();
        AtomicLong end = new AtomicLong();
        IBrowser aopBrowser = new AppBrowser("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("loding time : " + end.get());

        aopBrowser.show();
        System.out.println("loding time : " + end.get());

    }
}

실행 결과

 

 

 

 

2) 데코레이터 패턴 ( Decorator pattern )

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

확장이 필요한 경우 상속의 대안으로도 활용된다.

 SOLID중 개방폐쇠 원칙(OCP)과 의존 역전 원칙(DIP)를 따른다.

 

ex) 커피 =  커피 원액  >   +물  = 아이스 아메리카노 , + 우유 카페라떼

 

 

💡 필요 

(1) 필요한 기능을 정의한 인터페이스

(2) 인터페이스를 받아 내용을 정의한 기본 객체 (Audi)

(3) 인터페이스를 받아 기본 객체에서 확장된 내용을 정의하는 객체 (AudiDecorator)

(4) 확장된 내용의 객체를 상속 받는 객체 (A3,A4,A5)

(5) 기능을 받는 클라이언트 (Main)

 

 

🍫 예제

인터페이스

(1) 필요한 기능을 정의한 인터페이스

package com.company.design.decorator;

public interface Icar {
        int getPrice();
        void showPrice();
}

 

클래스

(2) 인터페이스를 받아 내용을 정의한 기본 객체 (Audi)

package com.company.design.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("아우디의 가격은 " + this.price + " 입니다.");

    }
}

 

클래스

(3) 인터페이스를 받아 기본 객체에서 확장된 내용을 정의하는 객체 (AudiDecorator)

package com.company.design.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() + "원 입니다.");
    }
}

 

클래스

(4) 확장된 내용의 객체를 상속 받는 객체 (A3,A4,A5)

package com.company.design.decorator;

public class A3 extends AudiDecorator{

    public A3(Icar audi, String modelName) {
        super(audi, modelName, 1000);
    }
}
package com.company.design.decorator;

public class A4 extends AudiDecorator{

    public A4(Icar audi, String modelName) {
        super(audi, modelName, 2000);
    }
}
package com.company.design.decorator;

public class A5 extends AudiDecorator{

    public A5(Icar audi, String modelName) {
        super(audi, modelName, 3000);
    }
}

 

메인

(5) 기능을 받는 클라이언트 (Main)

package com.company.design;

import com.company.design.decorator.*;

public class MainDecorator {
    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();
    }
}

실행결과

 

 


 

3. 행위 패턴

반복적으로 사용되는 객체들의 상호작용을 패턴화한 것

클래스나 객체들의 상호작용하는 방법과 책임을 분산하는 방법을 제공

 

🍫 종류 : Template Method, Interpereter, Iterator, Observer, Strategy, Visitor, Chain of responex sibilit, Command, Mediator, State, Memento

 

1) 옵저버 패턴 ( Observer pattern )

관찰하는 것이 변화했을때 , 미리 등록된 다른 클래스에서 통보해주는 패턴을 구현

ex) event listener

 

🍫 예제

인터페이스

package com.company.design.observer;

public interface IButtonListener {
    void clickEvent(String event);
}

 

클래스 

package com.company.design.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 buttonListener){
        this.buttonListener = buttonListener;
    }
}

 

메인

package com.company.design;

import com.company.design.observer.Button;
import com.company.design.observer.IButtonListener;

public class MainObserver {
    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");

    }
}

 

실행결과

 

 

 

 

 

2) 파사드 패턴 ( Facade pattern )

건물의 앞쪽 정면이라는 뜻을 가진다.

여러개의 객체와 실제 사용하는 서브 객체의 사이에 복잡한 의존관계가 있을 때,

중간에 facade 라는 객체를 두고 여기서 제공하는 인터페이스만을 활용하여 기능을 사용하는 방식.

facade는 자신이 가지고 있는 각 클래스의 기능을 명확히 알아야한다.

 

 

💡 필요 

(1) 각 기능을 수행하는 객체 (Ftp , Writer, Reader)

(2) 각 객체의 의존성을 모두 받는 파일 (sftpClient)

(3) 기능을 받는 클라이언트 (Main)

 

 

🍫 예제

클래스

(1) 각 기능을 수행하는 객체 (Ftp , Writer, Reader)

package com.company.design.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 com.company.design.facade;

public class Writer {
    private String fileName;

    public Writer(String fileName) {
        this.fileName = fileName;
    }

    public void fileConnect(){
        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);
    }
    public void write(){
        String msg = String.format("Reader %s 파일 쓰기를 합니다.",fileName);
        System.out.println(msg);
    }
}

 

package com.company.design.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);   
    }
}

 

클래스

(2) 각 객체의 의존성을 모두 받는 파일 (sftpClient)

package com.company.design.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(){
        reader.fileDisconnect();
        writer.fileDisconnect();
        ftp.disConnect();
    }

    //읽고 쓰는 기능 추가
    public void read(){
        reader.fileRead();
    }

    public void writer(){
        writer.write();
    }


}

 

메인

(3) 기능을 받는 클라이언트 (Main)

- 클라이언트가 모든 객체의 의존성을 받을때

package com.company.design;

import com.company.design.facade.Ftp;
import com.company.design.facade.Reader;
import com.company.design.facade.SftpClient;
import com.company.design.facade.Writer;

public class MainFacade {
    public static void main(String[] args) {
        //클라이언트가 다 의존성을 가지고 만들고 있다.
        Ftp ftpClient = new Ftp("www.ji.co.kr",22,"/home/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로 실행될때

package com.company.design;

import com.company.design.facade.Ftp;
import com.company.design.facade.Reader;
import com.company.design.facade.SftpClient;
import com.company.design.facade.Writer;

public class MainFacade {
    public static void main(String[] args) {
        SftpClient sftpClient = new SftpClient("www.ji.co.kr",22,"/home/etc","text.tmp");
        sftpClient.connect(); //모든 객체 연결
        sftpClient.writer(); //파일 쓰기
        sftpClient.read(); //파일 읽기
        sftpClient.disConnect(); //모든 객체 연결 종료
    }
}

 

실행결과

 

 

 

 

 

 

3) 전략 패턴 ( Strategy pattern )

전략 패턴으로 불리며,객체지향의 꽃이다.

유사한 행위들을 캡슐화하여, 객체의 행위를 바꾸고 싶은 경우 직접 변경하는 것이 아닌 전략만 변경하여 유연하게 확장하는 패턴.

SOLID 중에서 개방폐쇄 원칙(OCP)과 의존 역전 원직 (DIP)를 따른다.

 

💡 필요 

(1) 전략 메서드를 가진 전략 객체 (Normal Strategy , Base64 Strategy)

(2) 전략 객체를 사용하는 컨텍스트 (Encoder)

(3) 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트 (Main)

 

 

🍫 예제

인터페이스 

인코딩하려는 텍스트를 넣어주고 있다.

package com.company.design.strategy;

public interface EncodingStrategy {
    String encode(String text);
}

 

클래스

(2) 전략 객체를 사용하는 컨텍스트 (Encoder)

원본 객체에서 실행하려는 내용 정의 

package com.company.design.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;
    }
}

 

클래스

(1) 전략 메서드를 가진 전략 객체 (Normal Strategy , Base64 Strategy)

package com.company.design.strategy;

public class NormalStrategy implements EncodingStrategy{
    @Override
    public String encode(String text) {
        return text;
    }
}
package com.company.design.strategy;

import java.util.Base64;

public class Base64Strategy implements EncodingStrategy{
    @Override
    public String encode(String text) {
        return Base64.getEncoder().encodeToString(text.getBytes()); //java.util.Base64
    }
}
package com.company.design.strategy;

public class AppendStrategy implements EncodingStrategy {

    @Override
    public String encode(String text) {
        return "ABCD" + text;
    }
}

 

메인

(3) 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트 (Main)

package com.company.design;

import com.company.design.strategy.*;

public class MainStrategy {
    public static void main(String[] args) {
        //사용하기 위한 기본객체
        Encoder encoder = new Encoder();

        //전략
        //base64
        EncodingStrategy base64 = new Base64Strategy();
        //normal
        EncodingStrategy normal = new NormalStrategy();
        //append
        EncodingStrategy append = new AppendStrategy();

        String message = "hello java";

        //사용시 전략 세팅
        //base64
        encoder.setEncodingStrategy(base64);
        String base64Result = encoder.getMessage(message);
        System.out.println(base64Result);
        //normal
        encoder.setEncodingStrategy(normal);
        String normalResult = encoder.getMessage(message);
        System.out.println(normalResult);
        //append
        encoder.setEncodingStrategy(append);
        String appendResult = encoder.getMessage(message);
        System.out.println(appendResult);
    }
}

실행결과

 

 

 

 

profile

minlog

@jimin-log

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!