Spring Boot/디자인 패턴

디자인 패턴(프록시 패턴)

운동하는 주니어개발자 2023. 4. 20. 02:39

오늘도 어김없이 디자인 패턴에 대해 공부하였다.

실습과 병행하다 보니 시간이 빠르게 지나가는거 같다..

현재 정보처리산업기사 실기와 같이 준비하고 있어서 객체지향과 Spring공부의 진도가 빠르지는 않아 아쉽다.

오늘 공부한것에 대해 이야기를 해보겠다.

오늘은 저번시간에 이어서 디자인 패턴(Proxy, Decorator, Observer pattern)에 대해 공부하였다.

1. Proxy pattern

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

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

Cache의 기능으로도 활용이 가능 하다.

SOLID중에서 개방폐쇄 원칙과 의존 역전 원칙을 따른다.

Proxy pattern의 이해를 돕기 위한 사진

Browser(브라저)와 cache(캐쉬)를 사용하여 Proxy pattern의 예제를 실습하였다.

Browser.java

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

BrowserProxy.java

package com.company.design.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("BrowserProxy loading html from : "+url);
        }

        System.out.println("BrowserProxy use cache html : "+url);
        return html;
    }
}

Html.java

package com.company.design.proxy;

public class Html {
    private String url;

    public Html(String url) {
        this.url = url;
    }

}

IBrowser.java

package com.company.design.proxy;

public interface IBrowser {
    Html show();
}

Proxy_Main.java

package com.company.design;

import com.company.design.aop.AopBrowser;
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 Proxy_Main {
    public static void main(String[] args) {
        
        //프록시 패턴
        Browser browser = new Browser("www.naver.com");
        browser.show();
        browser.show();
        browser.show();
        browser.show();
    }
}

Proxy pattern예제 결과화면

Proxy pattern을 이용한 예제 결과화면

Proxy pattern의 포함하는 Aop를 사용한 예제도 실습하였다.

AopBrowser.java

package com.company.design.aop;

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

Proxy_Main.java

package com.company.design;

import com.company.design.aop.AopBrowser;
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 Proxy_Main {
    public static void main(String[] args) {

        //프록시 패턴에 속하는 aop패턴 예제 람다식 사용
        //시간 aop패턴을 사용하여 현재 시스템이 어디서 오래 걸리고 있는지 어떤 메소드가 오래걸려서 우리의 서버가 느린 상태인지 발견하는데 많이 사용
        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());
                }
        );

        // 처음 호출할 때는 약 1.5초 걸리는데 두번 째 호출할 때는 캐쉬를 사용하기 때문에 0초가 걸린다.
        aopBrowser.show();
        System.out.println("loading time : "+end.get());

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

Proxy pattern에 속하는 Aop패턴은 람다식을 사용하여 표현한다.

Aop패턴은 시간 Aop패턴을 많이 사용하는데 이것은 현재 시스템이 어디서 오래 걸리고 있는지 어떤 메소드가 오래 걸려서 우리의 서버가 느린 상태인지를 발견하는데 많이 사용한다.

람다식을 표현하는 코드는 다음과 같다.

//람다식 표현 방법
        IBrowser aopBrowser = new AopBrowser("www.naver.com",
                ()->{
                    System.out.println("before");
                    start.set(System.currentTimeMillis());
                },
                ()->{
                    long now = System.currentTimeMillis();
                    end.set(now - start.get());
                }
        );

Proxy pattern의 포함하는 Aop를 사용한 예제 결과화면

Proxy pattern의 포함하는 Aop를 사용한 예제 결과화면

Aop패턴은 기본 Proxy pattern과 달리 cache를 사용한다.

브라우저를 처음으로 호출할 때는 약 1.5초의 시간이 걸리지만 두번 째 호출할 때는 cache를 사용하기 때문에 로딩 시간은 0초가 걸리게 된다. 이것을 이용하여 나의 cache가 잘 동작하고 있는지를 확인할 수도 있다.

다음으로는 Decorator pattern에 대해 알아 보겠다.

Decorator pattern은 기존 뼈대(클래스)는 유지하되 이후 필요한 형태로 꾸밀 때 사용한다. 확장이 필요한 경우 상속의 대안으로도 활용 한다. SOLID중에서 개방폐쇄 원칙과 의존 역전 원칙을 따른다.

Decorator pattern의 예제로는 기본 뼈대가 있고 등급이 올라갈 때 마다 가격이 상승하는 예제를 실습하였다.

예로 아우디라는 자동차의 금액이 1000원으로 잡혀있을 때 a3, a4, a5 등 등급이 올라갈 때 마다 가격이 올라가는 예제로 설명하겠다.

a3.java

package com.company.design.decorator;

public class A3 extends AudiDecorator{
    public A3(ICar audi, String modelName) {
        super(audi, modelName, 1000);
    }
}

a4.java

package com.company.design.decorator;

public class A4 extends AudiDecorator{
    public A4(ICar audi, String modelName) {
        super(audi, modelName, 2000);
    }
}

a5.java

package com.company.design.decorator;

public class A5 extends AudiDecorator{
    public A5(ICar audi, String modelName) {
        super(audi, modelName, 3000);
    }
}

Audi.java

package com.company.design.decorator;

public class Audi implements ICar{

    private int price;

    public Audi(int cost) {
        this.price = cost;
    }

    @Override
    public int getPrice() {
        return price;
    }

    @Override
    public void showPrice() {
        System.out.println("audi 의 가격은 "+this.price+" 원 입니다.");
    }
}

AudiDecorator.java

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

ICar.java

package com.company.design.decorator;

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

Decorator_Main.java

package com.company.design;

import com.company.design.decorator.*;

public class Decorator_Main {
    public static void main(String[] arge) {
        // 등급에 따라 가격이 달라지는 예제
        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();

    }
}

Decorator pattern의 예제로는 기본 뼈대가 있고 등급이 올라갈 때 마다 가격이 상승하는 예제 결과 화면

Decorator pattern의 예제 등급이 올라갈 때 마다 가격이 상승하는 예제 결과 화면

각 등급의 java클래스를 생성하고 상속을 받는다.

기본 뼈대의 audi의 가격 1000원에서 등급이 올라갈 수록 추가 금액이 상승한다.

a3는 +1000원, a4는 +2000원, a5는 +3000원으로 금액이 상승한다.

이러한 패턴은 Decorator pattern이라고 한다.

다음으로는 Observer pattern에 대해 알아 보겠다.

Observer pattern은 관찰자 패턴은 변화가 일어 났을 때 미리 등록된 다른 클래스에 통보해주는 패턴을 구현한 것이다. 많이 보이는 곳은 event listener에서 해당 패턴을 사용 하고 있다.

예 로는 교실에서 선생님의 망을 보던 학생이 중간에 있는 학생에게 이벤트를 전달하고 멀리 있는 친구들에게 까지 이벤트를 전달 할 수 있도록 해주는 패턴을 말한다.

Observer pattern의 예제로는 버튼을 클릭했을때 이벤트가 어떻게 처리되는지 확인하는 예제를 실습하였다.

Button.java

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

IButtonListener.java

package com.company.design.observer;

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

Observer_Main.java

import com.company.design.observer.IButtonListener;

public class Observer_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("메시지 전달 : click 1");
        button.click("메시지 전달 : click 2");
        button.click("메시지 전달 : click 3");
        button.click("메시지 전달 : click 4");
    }
}

Observer pattern의 예제 결과 화면

Observer pattern의 예제 결과 화면

이상으로 오늘의 공부를 마치려고 한다. 수 많은 패턴 중 핵심적인 패턴을 공부하고 있다. 다음 시간에는 나머지 패턴을 공부할 예정이다. 정보처리산업기사 실기가 빨리 끝나고 객체지향과 Spring공부에 몰두하고 싶다..

코딩테스트도 해야하는데.. 매일 이렇게 꾸준히 달리다 보면 충분히 할 수 있다고 생각한다!!