반응형

데코레이터 패턴(Decorator Pattern)

  - 데코레이터 패턴은 프록시 기법을 사용하는 디자인 패턴 중에 하나입니다.

  - GOF 디자인 패턴에서는 의도에 따라 두 가지 패턴으로 구분하였습니다. 

 

프록시의 주요 기능

프록시를 통해서 할 수 있는 일은 크게 2가지로 나뉜다.

1. 접근 제어 (권한에 따른 접근 차단, 캐싱 등)

2. 부가 기능 추가 (다른 로직을 추가하는 등의 부가 기능을 수행, 예) 실행 시간을 측정해서 로그를 남긴다.)

 

* GOF 디자인 패턴에서는 의도에 따라서 프록시 패턴과, 데코레이터 패턴으로 구분한다.

  - 프록시 패턴 : 접근 제어가 목적

  - 데코레이터 패턴 : 새로운 기능 추가가 목적

 

프록시 패턴의 장점

  - 부가 기능을 추가하여서 원하는 흐름에 맞게 조정할 수 있습니다.

 

코드

 * 메시지를 출력하는 기능에 메시지를 꾸며주는 기능을 데코레이터 패턴을 이용해서 적용해 보겠습니다.

 

Component.java

  - interface로 Component 생성

 

public interface Component {
    String operation();

}

 

RealComponent.java

  - 실제 Component 구현체

 

@Slf4j
public class RealComponent implements Component{
    @Override
    public String operation() {
        log.info("RealComponent 실행");
        return "data";
    }
}

 

DecoratorPatternClient.java

  - Component를 실행하는 클라이언트 클래스

 

@Slf4j
public class DecoratorPatternClient {

    private Component component;

    public DecoratorPatternClient(Component component) {
        this.component = component;
    }

    public void execute() {
        String result = component.operation();
        log.info("result={}", result);
    }

}

 

DecoratorPatternTest.java

  - 기본 메시지 출력 기능 테스트

@Slf4j
public class DecoratorPatternTest {

    @Test
    void noDecorator() {
        Component realComponent = new RealComponent();
        DecoratorPatternClient client = new DecoratorPatternClient(realComponent);
        client.execute();
    }
}

결과 : RealComponent 실행

          result=data

 

MessageDecorator.java

  - 데코레이터 패턴을 이용해서 메시지 꾸며주는 기능 추가

  - MessageDecorator는 Component 인터페이스를 구현한다.

 

@Slf4j
public class MessageDecorator implements Component{

    private Component component;

    public MessageDecorator(Component component) {
        this.component = component;
    }

    @Override
    public String operation() {
        log.info("MessageDecorator 실행");

        String result = component.operation();
        String decoResult = "<< =====" + result + " ===== >>";
        log.info("MessageDecorator 꾸미기 적용 전={}, 적용 후 ={}",result, decoResult);
        return decoResult;
    }
}

 

DecoratorPatterTest.java

  - 클라이언트가 호출하기 전에 MessageDecorator 클래스를 이용해서 부가 기능을 추가 후 클라이언트에게 messageDecorator를 넘겨준다. messageDecorator도 Component를 구현하고 있기 때문에 클라이언트 입장에서는 전혀 문제가 발생하지 않는다.

 

@Slf4j
public class DecoratorPatternTest {

    @Test
    void decorator1() {
        Component realComponent = new RealComponent();
        Component messageDecorator = new MessageDecorator(realComponent);
        DecoratorPatternClient client = new DecoratorPatternClient(messageDecorator);
        client.execute();
    }
}

 

결과 :

 

정리 :

 

데코레이터 패턴의 의도 : 객체에 추가 책임(기능)을 동적으로 추가하고, 기능 확장을 위한 유연한 대안 제공

프록시 패턴의 의도 : 다른 객체에 대한 접근을 제어하기 위해 대리자를 제공

728x90
반응형
반응형

전략 패턴

탬플릿 메서드 패턴은 부모 클래스에 변하지 않는 템플릿을 두고, 변하는 부분을 자식 클래스에 두어서 상속을 사용해서 문제를 해결하였습니다.
전략 패턴은 변하지 않는 부분을 Context 라는 곳에 두고, 변하는 부분을 Strategy 라는 인터페이스를 만들고 해당 인터페이스를 구현하도록 해서 문제를 해결합니다.

상속이 아니라 위임으로 문제를 해결하는 것이다.

전략 패턴에서 Context 는 변하지 않는 템플릿 역할을 하고, Strategy 는 변하는 알고리즘 역할을 합니다.

 

전략 패턴 의도

알고리즘 제품군을 정의하고 각각을 캡슐화하여 상호 교환 가능하게 만들자.

전략을 사용하면 알고리즘을 사용하는 클라이언트와 독립적으로 알고리즘을 변경할 수 있습니다.

 

코드

방식1. 필드에 전략을 보관하는 방식

 ContextV1.java

 

@Slf4j
public class ContextV1 {

    private Strategy strategy; // 전략을 필드 변수로 선언

    public ContextV1(Strategy strategy) {
        this.strategy = strategy;
    }

    public void execute() {
        long startTime = System.currentTimeMillis();
        //비지니스 로직 실행
        strategy.call(); //위임
        //비즈니스 로직 종료
        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("resultTime={}", resultTime);
    }
}

 

변하는 알고리즘

 StrategyLogin1.java

 

@Slf4j
public class StrategyLogin1 implements Strategy {

    @Override
    public void call() {
        log.info("비즈니스 로직1 실행");
    }
}

 

 StrategyLogin2.java

 

@Slf4j
public class StrategyLogin2 implements Strategy {

    @Override
    public void call() {
        log.info("비즈니스 로직2 실행");
    }
}

 

테스트 코드

ContextV1Test.java

 

@Slf4j
public class ContextV1Test {

    @Test
    void strategyV1() {
        StrategyLogin1 strategyLogin1 = new StrategyLogin1();
        ContextV1 contextV1 = new ContextV1(strategyLogin1);
        contextV1.execute();

        StrategyLogin2 strategyLogin2 = new StrategyLogin2();
        ContextV1 contextV2 = new ContextV1(strategyLogin2);
        contextV2.execute();
    }

}

 

전략 패턴 사용은 선 조립, 후 실행으로 생각하면 보다 간단합니다.

변하지 않는 코드 ContextV1 객체에 변하는 알고리즘인 StrategyLogin1, StrategyLogin2를 생성자에 넣어서 조립하고, 호출이 필요한 시점에 execute()를 이용해서 실행합니다.
스프링 애플리케이션 개발 할 때 애플리케이션 로딩 시점에 의존관계 주입을 통해 필요한 의존관계를 모두 맺어두고
다음에 실제 요청을 처리하는 것과 같은 원리이다.

 

위와 같이 사용하게 되면 변하는 알고리즘인 전략 class가(StrategyLogin1, StrategyLogin2) 계속 생겨날 수밖에 없습니다.

이러한 점을 보완하기 위해서 익명 내부 클래스를 사용합니다.

 

익명 내부 클래스 사용

 

@Slf4j
public class ContextV1Test {

    @Test
    void strategyV2() {
        Strategy strategyLogic1 = new Strategy() {

            @Override
            public void call() {
                log.info("비즈니스 로직1 실행");
            }
        };
        ContextV1 contextV1 = new ContextV1(strategyLogic1);
        contextV1.execute();

        Strategy strategyLogic2 = new Strategy() {

            @Override
            public void call() {
                log.info("비즈니스 로직2 실행");
            }
        };
        ContextV1 contextV2 = new ContextV1(strategyLogic2);
        contextV2.execute();

    }

}

 

람다를 이용해서 보다 더 코드 수를 줄일 수 있습니다.

 

@Slf4j
public class ContextV1Test {

    @Test
    void strategyV3() {
        ContextV1 contextV1 = new ContextV1(() -> log.info("비즈니스 로직1 실행"));
        contextV1.execute();

        ContextV1 contextV2 = new ContextV1(() -> log.info("비즈니스 로직2 실행"));
        contextV2.execute();

    }

}

 

방식2. 필드에 전략을 보관하는 방식

 

전략을 파라미터로 전달 받는 방식, 실행할 때 마다 전략을 유연하게 변경할 수 있다.
단점 역시 실행할 때 마다 전략을 계속 지정해주어야 한다.

 

ContextV2.java

 

@Slf4j
public class ContextV2 {

    public void execute(Strategy strategy) {
        long startTime = System.currentTimeMillis();
        //비지니스 로직 실행
        strategy.call(); //위임
        //비즈니스 로직 종료
        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("resultTime={}", resultTime);
    }
}

 

테스트 코드

ContextV2Test.java

 

@Slf4j
public class ContextV2Test {


    /**
     * 전략 패턴 적용
     */
    @Test
    void strategyV1() {
        ContextV2 context = new ContextV2();
        context.execute(new StrategyLogin1());
        context.execute(new StrategyLogin2());
    }

    /**
     * 전략 패턴 익명 내부 클래스
     */
    @Test
    void strategyV2() {
        ContextV2 context = new ContextV2();
        context.execute(new Strategy() {
            @Override
            public void call() {
                log.info("비즈니스 로직 1 실행");
            }
        });
        context.execute(new Strategy() {
            @Override
            public void call() {
                log.info("비즈니스 로직 2 실행");
            }
        });
    }

    /**
     * 전략 패턴 익명 내부 클래스2, 람다
     */
    @Test
    void strategyV3() {
        ContextV2 context = new ContextV2();
        context.execute(() -> log.info("비즈니스 로직 1 실행"));
        context.execute(() -> log.info("비즈니스 로직 2 실행"));
    }

}

 

참고로 ContextV2 형식의 전략 패턴은 스프링 내부에서 템플릿 콜백 패턴이라고 부른다.

스프링에서 jdbcTemplate, RestTemplate, ~Template 처럼 다양한 템플릿 콜백 패턴이 사용된다.

 

 

참조

 - inflearn(스프링 핵심 원리 - 고급편, 김영한)

728x90
반응형
반응형

프로토타입 패턴(Prototype Pattern)

  - 프로토타입 패턴은 생성패턴(Creational Pattern) 중 하나이다.
  - 생성 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴이다.
  - 생성 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현방법을 시스템과 분리해준다.

  - 기존 인스턴스를 복제하여 새로운 인스턴스를 만드는 방법이다.

  - 프로토타입 패턴은 원형이 되는 인스턴스를 사용해 새롭게 생성할 객체의 종류를 명시하여 새로운 객체가 생성될 시점에 인스턴스의 타입이 결정되도록 하는 패턴입니다

  - 생성하고자 하는 객체의 클래스에서 Cloneable의 clone() 메서드를 재정의되어야 한다.

 

장점

  - 구현 클래스에 직접 연결하지 않고 객체를 복사할 수 있습니다.(새 인스턴스를 만드는 것보다 비용적인 면에서 효율적일 수 있다.)

  - 복잡한 객체를 만드는 과정을 숨길 수 있다.

  - 추상적인 타입을 리턴할 수 있다.

단점

  - 복제한 객체를 만드는 과정 자체가 복잡할 수 있다.(특히, 순환 참조가 있는 경우)

코드

 - Mountain.class

package com.bumblebee.designpattern.creational.prototype;

import java.util.ArrayList;
import java.util.List;

public class Mountain implements Cloneable{

    private List<String> mountainList;

    public Mountain() {
        this.mountainList = new ArrayList<>();
    }

    public Mountain(List<String> mountainList) {
        this.mountainList = mountainList;
    }

    public List<String> getMountainList() {
        return mountainList;
    }

    public void mountainListAll() {
        this.mountainList.add("구룡산(306M)");
        this.mountainList.add("북악산(342M)");
        this.mountainList.add("아차산(295M)");
        this.mountainList.add("응봉산(95M)");
        this.mountainList.add("서대문 안산(295M)");
        this.mountainList.add("용마산(348M)");
        this.mountainList.add("인왕산(339M)");
        this.mountainList.add("남산(279M)");
        this.mountainList.add("불암산(509M)");
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
    	//return super.clone(); 얕은 복사(shallow copy)
        return new Mountain(new ArrayList<>(this.mountainList)); //깊은 복사(Deep Copy)
    }
}

 

  - MountainTest.class

  - Cloneable 인터페이스를 구현하여 Mountain 클래스를 생성하였고 clone() 메소드를 통해 깊은 복사 방식으로 인스턴스를 복제 하였습니다. java에서는 기본으로 얕은 복사를 제공한다.

package com.bumblebee.designpattern.creational.prototype;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
class MountainTest {

    @Test
    void prototypePattern() throws CloneNotSupportedException {

        Mountain mountain = new Mountain();
        mountain.mountainListAll();

        Mountain seoulMountain = (Mountain) mountain.clone();
        Mountain seoulMountain2 = (Mountain) mountain.clone();
        List<String> mountainList = seoulMountain.getMountainList();
        List<String> mountainList2 = seoulMountain2.getMountainList();

        mountainList.add("관악산(632M)");
        mountainList2.remove("북악산(342M)");

        System.out.println(mountain.getMountainList());
        System.out.println(mountainList);
        System.out.println(mountainList2);

    }

}

 

  - 결과

 

정리

 - DB에 접근해서 데이터를 핸들링하는 비용이 크므로 매번 DB에 접근이 필요하지 않는 데이터라면 프로토타입 패턴을 이용해서 객체를 복사해서 사용하는 방법이 유용할 것 같다.

728x90
반응형
반응형

커맨드 패턴(Command Pattern)

  - 커맨드 패턴은 행위패턴(Behavioral Pattern) 중 하나이다.
  - 행위 패턴은 객체 사이에 알고리즘이나 책임 분배에 관련된 패턴이다.

  - 행위 패턴은 결합도를 최소화하는 것에 목표로 한다.

 

장점

  - 커맨드 패턴은 실행될 기능을 캡슐화함으로써 주어진 여러 기능을 실행할 수 있는 재사용성이 높은 클래스를 설계한다.

  - 실행될 기능을 캡슐화함으로써 기능의 실행을 요구하는 호출자 클래스와 실제 기능을 실행하는 수신자 클래스 사이의 의존성을 제거한다.

 

코드

 

  - Command Interface :

    실제 기능을 수행하는 클래스들을 캡슐화 하기위한 Interface

 

public interface Command {

    public String run();

}

 

  - 디지니 구현 Class :

    실제 구현 클래스

 

public class DisneyCommand implements Command {

    @Override
    public String run() {
        return "Disney";
    }
}

 

  - 넷플릭스 구현 Class

 

public class NexflixCommand implements Command{

    @Override
    public String run() {
        return "Netflix";
    }
}

 

  - 호출자 Class

 

public class OttService {

    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public String displayOtt() {
        return command.run();
    }

}

 

  - 커맨더 패턴 Test Class

 

@SpringBootTest
class OttServiceTest {

    @Test
    void CommandPatternTest() {

        Command disneyCommand = new DisneyCommand();
        Command netflixCommand = new NexflixCommand();

        OttService ottService = new OttService();
        ottService.setCommand(disneyCommand);
        assertEquals(ottService.displayOtt(), "Disney");

        ottService.setCommand(netflixCommand);
        assertEquals(ottService.displayOtt(), "Netflix");

    }

}

 

 

정리

어떠한 객체를 공용으로 사용하는데 동작만을 객체마다 분리해서 사용하고 싶을 경우에 적합한 패턴인 것 같다.

1. 공용(위 예제의 OttService.java)으로 사용하고 있는 객체에 command라는 인터페이스를 구현한다.

2. 객체의 동작(DisneyCommand.java, NetflixCommand.java)을 호출하는 클래스에 각각 기능에 맞추어 command를 구현한다.

 

Java의 다형성을 이용해서 Command Interface를 구현한 클래스는 호출자 수정 없이 기능 추가가 가능하다.

이는 OCP(Open Close Principle) 원칙에 위배되지 않게 기능을 추가할 수 있게 하는 패턴이다.

 

참조 

  - https://gmlwjd9405.github.io/2018/07/07/command-pattern.html

728x90
반응형
반응형

책임 사슬 패턴(Chain of Responsibility Pattern)

  - 템플릿 메서드 패턴은 행위패턴(Behavioral Pattern) 중 하나이다.
  - 행위 패턴은 객체 사이에 알고리즘이나 책임 분배에 관련된 패턴이다.

  - 행위 패턴은 결합도를 최소화하는 것에 목표로 한다.

  - 책임 사슬 패턴은 특정한 객체가 담당하는 일반적인 방법과 다르게 객체를 연결리스트와 같은 사슬 방식으로 연결한 후에 요청을 수행하지 못하는 객체라면 다음 객체로 책임을 넘기는 형태의 패턴이다.

 

장점

  - 여러 클래스간에 걸쳐 이루어지는 일이기 때문에 구조가 다른 클래스에 대해서 낮은 결합도로 동일한 이벤트 핸들링이 가능하다. 

  - 사슬에 들어가는 객체를 바꾸거나 순서를 바꿈으로써 역할을 동적으로 추가/제거 할 수 있다. 상황에 따라 동적으로 핸들러를 추가하거나 제거할 수 있으며, 이러한 변화가 전체구조에 아무런 영향을 주지 않는다는 점에서 객체지향적인 목적을 달성한다고 볼 수 있다.

 

코드

  * 한라봉의 무게에 따라 다른 박스로 분리되는 예시

 

  - 한라봉 Chain Interface :

    책임 사슬 패턴의 핵심 Interface

    setNextChain 메소드를 통해서 다른 체인을 이어준다.

public interface HanrabongChain {

    void setNextChain(HanrabongChain nextChain);
    void weightFilter(int weight);

}

 

  - 한라봉 Class

 

public class Hanrabong {

    private int weight;

    public Hanrabong(int weight) {
        this.weight = weight;
    }

    public int getWeight() {
        return this.weight;
    }
}

 

  - 한라봉 10kg Filter Class

 

public class Hanrabong10k implements HanrabongChain{

    private HanrabongChain hanrabongChain;

    @Override
    public void setNextChain(HanrabongChain nextChain) {
        this.hanrabongChain = nextChain;
    }

    @Override
    public void weightFilter(int weight) {
        if(weight >= 10) {
            System.out.println("IN 10kg box!");
        } else {
            this.hanrabongChain.weightFilter(weight);
        }
    }
}

 

  - 한라봉 5kg Filter Class

 

public class Hanrabong5k implements HanrabongChain{

    private HanrabongChain hanrabongChain;

    @Override
    public void setNextChain(HanrabongChain nextChain) {
        this.hanrabongChain = nextChain;
    }

    @Override
    public void weightFilter(int weight) {
        if(weight >= 5) {
            System.out.println("IN 5kg box!");
        } else {
            this.hanrabongChain.weightFilter(weight);
        }
    }
}

 

  - 한라봉 3kg Filter Class

 

public class Hanrabong3k implements HanrabongChain{

    private HanrabongChain hanrabongChain;

    @Override
    public void setNextChain(HanrabongChain nextChain) {
        this.hanrabongChain = nextChain;
    }

    @Override
    public void weightFilter(int weight) {
        if(weight >= 3) {
            System.out.println("IN 3kg box!");
        } else {
            this.hanrabongChain.weightFilter(weight);
        }
    }
}

 

  - 책임 사슬 패턴 Test Class

@SpringBootTest
class HanrabongChainTest {

    @Test
    void hanrabongFilterTest() {

        HanrabongChain hanrabongChain = getHanrabongChain();

        Hanrabong hanrabong5 = new Hanrabong(5);
        Hanrabong hanrabong12 = new Hanrabong(12);
        Hanrabong hanrabong7 = new Hanrabong(7);
        Hanrabong hanrabong4 = new Hanrabong(4);
        Hanrabong hanrabong1 = new Hanrabong(1);

        List<Hanrabong> rabongList = Arrays.asList(hanrabong5, hanrabong12, hanrabong7, hanrabong4, hanrabong1);

        for (Hanrabong hanrabong : rabongList) {
            hanrabongChain.weightFilter(hanrabong.getWeight());
        }


    }
    
    // 다음 객체 연결
    private HanrabongChain getHanrabongChain() {
        HanrabongChain hanrabong10k = new Hanrabong10k();
        HanrabongChain hanrabong5k = new Hanrabong5k();
        HanrabongChain hanrabong3k = new Hanrabong3k();

        hanrabong10k.setNextChain(hanrabong5k);
        hanrabong5k.setNextChain(hanrabong3k);
        return hanrabong10k;
    }

}

 

정리

* 책임 사슬 패턴의 주의점

위 테스트 예제에서 무게가 1kg인 한라봉이 들어온다면 에러가 발생되거나 필터에 걸리지 않는다.

책임 사슬 패턴은 요청이 반드시 수행된다는 보장이 없기 때문에, 체인이 적절하게 구성되어 프로세스가 처리 될 수 있도록 구성하여야 한다. 추가적으로 요청을 처리하는데 걸리는 시간을 예측하기 힘들다는 단점도 있어 시간예측이 중요한 경우 해당 패턴의 적용을 검토해봐야 한다.

 

참조 

  - https://www.nextree.co.kr/p2533/

728x90
반응형
반응형

템플릿 메서드 패턴(Template Method Pattern)

  - 템플릿 메서드 패턴은 행위패턴(Behavioral Pattern) 중 하나이다.
  - 행위 패턴은 객체 사이에 알고리즘이나 책임 분배에 관련된 패턴이다.

  - 행위 패턴은 결합도를 최소화하는 것에 목표로 한다.

 

목적

"작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기합니다.

템플릿 메서드를 사용하면 하위 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재정의할 수 있습니다." [GOF]

 

쉽게 이야기하면 부모 클래스에 알고리즘의 골격인 템플릿을 정의하고, 일부 변경되는 로직은 자식 클래스에 정의하는 것이다.

이렇게 하면 자식 클래스가 알고리즘의 전체 구조를 변경하지 않고, 특정 부분만 재정의할 수 있다.

결국 상속오버라이딩을 통한 다형성으로 문제를 해결하는 것이다.

 

장점

  - 알고리즘의 구조를 메소드에 정의하고, 하위 클래스에서 알고리즘 구조의 변경없이 알고리즘을 재정의하는데 유용하다.

  - 알고리즘이 단계별로 나누어 지거나, 같은 역할을 하는 메소드이지만 여러곳에서 다른 형태로 사용이 필요한 경우 유용하다.

 

코드

  * FactoryTemplate이라는 공장 템플릿 추상화 클래스를 이용해서 자동차, 폰 공장에서는 상품생산 프로세스만 다르게 구성한다고 설정

 

  - FactoryTemplate.java (추상화 클래스)

 

public abstract class FactoryTemplate {

    public final void buildProduct() {
        baseMaterial();
        productProcess();
        finishProduct();
    }

    private void baseMaterial() {
        System.out.println("원/부자재 수입");
    }

    public abstract void productProcess();

    private void finishProduct() {
        System.out.println("제품 완성");
    }

}

 

  - 자동차 공장 Class

 

public class CarFactory extends FactoryTemplate{

    @Override
    public void productProcess() {
        System.out.println("자동차 공장 프로세스 가동!");
    }

}

 

  - 핸드폰 공장 Class

 

public class PhoneFactory extends FactoryTemplate {

    @Override
    public void productProcess() {
        System.out.println("핸드폰 공장 프로세스 가동!");
    }
}

 

  - 테스트 Class

 

@SpringBootTest
class FactoryTemplateTest {

    @Test
    void templatePatternTest() {

        FactoryTemplate factory;
        factory = new CarFactory();
        factory.buildProduct();

        System.out.println("=======================");

        factory= new PhoneFactory();
        factory.buildProduct();

    }


}

 

결과 :

원 부자재 수입
자동차 공장 프로세스 가동!
제품 완성
=======================
원 부자재 수입
핸드폰 공장 프로세스 가동!
제품 완성

 

 

정리

 - 추상화 클래스에서 메서드에 알고리즘 골격을 정의하고, 소스코드의 중복을 줄이기 위해서 하위 클래스에서 공통적으로 나타나는 부분에 대해서 추상클래스에 정의해서 사용한다.

 * 핵심 템플릿 클래스abstract 추상 클래스임을 인지하자

 

참조 

  - https://niceman.tistory.com/142

  - https://yaboong.github.io/design-pattern/2018/09/27/template-method-pattern/

728x90
반응형
반응형

프록시 패턴(Proxy Pattern)

  - Proxy는 대리자, 대변인 이라는 뜻이다. 

  - 프록시 패턴은 구조패턴(Structural Pattern) 중 하나이다.
  - 구조 패턴은 작은 클래스들을 상속과 합성을 이용하여 더 큰 클래스를 생성하는 방법을 제공하는 패턴이다.

  - 인터페이스나 구현을 복합하는 것이 아니라 객체를 합성하는 방법을 제공한다.

    이는 컴파일 단계에서가 아닌 런타임 단계에서 복합 방법이나 대상을 변경할 수 있다는 점에서 유연성을 갖는다.

 

프록시의 주요 기능

프록시를 통해서 할 수 있는 일은 크게 2가지로 나뉜다.

1. 접근 제어 (권한에 따른 접근 차단, 캐싱 등)

2. 부가 기능 추가 (다른 로직을 추가하는 등의 부가 기능을 수행, 예) 실행 시간을 측정해서 로그를 남긴다.)

 

* GOF 디자인 패턴에서는 의도에 따라서 프록시 패턴과, 데코레이터 패턴으로 구분한다.

  - 프록시 패턴 : 접근 제어가 목적

  - 데코레이터 패턴 : 새로운 기능 추가가 목적

 

프록시 패턴의 장점

  - 제어의 흐름을 변경하거나 다른 로직을 수행하기 위해 사용한다.

  - 객체에 대하여 접근할 때에 Wrapper Class를 두어 접근에 대한 통제를 위해 사용된다.

 

코드

  * 더하기 빼기 계산기 로직을 구현 예제 (프록시 패턴을 이용해서 계산 시간을 출력)

 

  - 계산기 interface

 

public interface Calculator {

    public int plus(int left, int right);
    public int subtract(int left, int right);

}

 

  - 계산기 interface 구현 service

 

public class CalculatorService implements Calculator{

    private int result;

    @Override
    public int plus(int left, int right) {
        result = left + right;
        return result;
    }

    @Override
    public int subtract(int left, int right) {
        result = left - right;
        return result;
    }
}

 

  - 프록시 패턴을 사용하지 않은 테스트

 

@SpringBootTest
class CalculatorServiceTest {

    @Test
    void calculatorService() {

        Calculator calculatorService = new CalculatorService();

        int left = 10000;
        int right = 5000;

        int plusResult = calculatorService.plus(left, right);
        int subtractResult = calculatorService.subtract(left, right);

        assertEquals(plusResult, left+right);
        assertEquals(subtractResult, left-right);

    }

}

 

  - 프록시 패턴 클래스

 

public class CalculatorServiceProxy implements Calculator{

    private int result;

    @Override
    public int plus(int left, int right) {
        long beforeTime = System.currentTimeMillis();

        result = left + right;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long afterTime = System.currentTimeMillis();
        long secDiffTime = (afterTime - beforeTime)/1000;
        System.out.println(" plus 계산 시간(m) : "+secDiffTime);

        return result;
    }

    @Override
    public int subtract(int left, int right) {
        long beforeTime = System.currentTimeMillis();

        result = left - right;
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long afterTime = System.currentTimeMillis();
        long secDiffTime = (afterTime - beforeTime)/1000;
        System.out.println("subtract 계산 시간(m) : "+secDiffTime);

        return result;
    }
}

 

- 프록시 패턴을 사용한 테스트

 

@SpringBootTest
class CalculatorServiceTest {

    @Test
    void calculatorServiceProxy() {

        Calculator calculatorServiceProxy = new CalculatorServiceProxy();

        int left = 10000;
        int right = 5000;

        int plusResult = calculatorServiceProxy.plus(left, right);
        int subtractResult = calculatorServiceProxy.subtract(left, right);

        assertEquals(plusResult, left+right);
        assertEquals(subtractResult, left-right);

    }


}

 

결과 : plus 계산 시간(m) : 1
        subtract 계산 시간(m) : 2

 

 

정리

 - 프록시 패턴의 의도 : 다른 객체에 접근을 제어하기 위해 대리자를 제공

 - 테스트 코드를 보면 생성 객체만 바꿈으로서 Calculator interface는 영향을 받지 않게 Proxy 클래스를 통해서 원하는 목적에 맞게 흐름을 조정할 수 있었다. 해당 패턴은 Spring 프레임워크에 AOP 동작원리에 사용되어지고 있다.

 * 중요한 점은 흐름제어만 할 뿐 결과값을 조작하거나 변경시키면 안된다.

 

참조 

 - https://readystory.tistory.com/132?category=822867

728x90
반응형
반응형

추상 팩토리 패턴(Abstract Factory Pattern)

  - 추상 팩토리 패턴은 생성패턴(Creational Pattern) 중 하나이다.
  - 생성 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴이다.
  - 생성 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현방법을 시스템과 분리해준다.

  - 생성 패턴을 이용하면 무엇이 생성되고, 누가 이것을 생성하며, 이것이 어떻게 생성되는지, 언제 생성할 것인지 결정하는 데 유연성을 화      보할 수 있습니다.

 

장점

  - 추상 팩토리 패턴은 클라이언트 코드로부터 서브 클래스의 인스턴스화를 제거하여 서로 간의 종속성을 낮추고, 결합도를 느슨하게 하며(Loosely Coupled), 확장을 쉽게 한다.

  - 추상 팩토리 패턴은 클라이언트와 구현 객체들 사이에 추상화를 제공한다.

  - 추상 팩토리 패턴에서는 팩토리 클래스에서 서브 클래스를 생성하는 데에 있어서 필요한 if-else, switch 문을 걷어냅니다.

 

팩토리 메소드 패턴 vs 추상 팩토리 패턴

- 둘 다 구체적인 객체 생성 과정을 추상화한 인터페이스를 제공한다.

 

팩토리 메소드 패턴 

추상 팩토리 패턴

관점 "팩토리를 구현하는 방법"에 초점 "팩토리를 사용하는 방법"에 초점
목적 구체적인 객체 생성 과정을 하위 또는 구체적인 클래스로 옮기는 것이 목적 관련있는 여러 객체를 구체적인 클래스에 의존하지 않고 만들 수 있게 해주는 것이 목적

 

코드

  * 부동산 중개 수수료 계산 프로그램 가정(매매/임대차)

 

  - 중개 정책 interface 생성 :

     interface를 구현하는 하위 클래스에서 수수료 계산 로직을 구현한다.

     java8 이후로 default 메소드를 사용하면 interface 내부에서 로직 작성이 가능하다. (default 명시)

public interface BrokeragePolicy {

    BrokerageRule createBrokerageRule(Long price);

     default Long calculate(Long price) {
        BrokerageRule rule = createBrokerageRule(price);
        return rule.calcMaxBrokerage(price);
    }
}

 

  - 매매 중개 수수료 class :

    java7 이후부터 _는 숫자 리너털 어디에도 사용할 수 있다. 가독성 향상

 

public class PurchaseBrokeragePolicy implements BrokeragePolicy{

    public BrokerageRule createBrokerageRule(Long price) {
        BrokerageRule rule;
        if(price < 50_000_000) {
            rule = new BrokerageRule(0.6, 250_000L);
        } else if(price < 200_000_000) {
            rule = new BrokerageRule(0.5, 800_000L);
        }else if(price < 600_000_000) {
            rule = new BrokerageRule(0.4, null);
        }else if(price < 900_000_000) {
            rule = new BrokerageRule(0.5, null);
        }else{
            rule = new BrokerageRule(0.9, null);
        }
        return rule;
    }


}

 

  - 임대차 중개 수수료 class

 

public class RentBrokeragePolicy implements BrokeragePolicy{

    public BrokerageRule createBrokerageRule(Long price) {
        BrokerageRule rule;
        if(price < 50_000_000) {
            rule = new BrokerageRule(0.5, 200_000L);
        } else if(price < 100_000_000) {
            rule = new BrokerageRule(0.4, 300_000L);
        }else if(price < 300_000_000) {
            rule = new BrokerageRule(0.3, null);
        }else if(price < 600_000_000) {
            rule = new BrokerageRule(0.4, null);
        }else{
            rule = new BrokerageRule(0.8, null);
        }
        return rule;
    }


}

 

  - 추상 팩토리 Interface Class :

     추상 팩토리 역할을 하는 인터페이스 생성

 

public interface BrokeragePolicyAbstractFactory {

    public BrokeragePolicy createBrokerageRule();

}

 

- 팩토리 Interface를 구현하는 클래스(매매 중개 수수료) :

  작성한 팩토리 인터페이스의 createBrokerageRule() 메소드의 리턴 타입이 super class인 BrokeragePolicy이다.

  이는 자바의 다형성을 잘 활용한 방식이다.

public class PurchaseBrokerageFactory implements BrokeragePolicyAbstractFactory{

    @Override
    public BrokeragePolicy createBrokerageRule() {
        return new PurchaseBrokeragePolicy();
    }
}

 

- 팩토리 Interface를 구현하는 클래스(임대차 중개 수수료)

 

public class RentBrokerageFactory implements BrokeragePolicyAbstractFactory{

    @Override
    public BrokeragePolicy createBrokerageRule() {
        return new RentBrokeragePolicy();
    }
}

 

- 서브 클래스들을 생성하기 위해서 if-else 없이 분개 처리해주는 클래스

 

public class BrokeragePolicyFactory {


    public static BrokeragePolicy getBrokeragePolicy(BrokeragePolicyAbstractFactory policyAbstractFactory) {
        return policyAbstractFactory.createBrokerageRule();
    }

}

 

- 중개 수수료 계산 api

 

  // 매매 중계수수료 계산 메소드
    @GetMapping("/api/calc/brokeragepur")
    public Long calcBrokeragePurchase(@RequestParam ActionType actionType,
                              @RequestParam Long price) {
        BrokeragePolicy policy = BrokeragePolicyFactory.getBrokeragePolicy(new PurchaseBrokerageFactory());
        return policy.calculate(price);
    }
  // 임대차 중계수수료 계산 메소드
    @GetMapping("/api/calc/brokeragerent")
    public Long calcBrokerageRent(@RequestParam ActionType actionType,
                              @RequestParam Long price) {
        BrokeragePolicy policy = BrokeragePolicyFactory.getBrokeragePolicy(new RentBrokerageFactory());
        return policy.calculate(price);
    }

 

 

실무 사용 사례

Spring에서 FactoryBean과 그 구현체

 

정리

 - 객체 생성을 담당 및 처리하는 팩토리 클래스를 생성하여서 객체 생성에 관한 확장도 쉽게 구성할 수 있다. 

    팩토리 패턴과 다르게 분개 처리하는 부분이 사라진다.

 

728x90
반응형

'JAVA > Design Pettern' 카테고리의 다른 글

[Design Pattern] 템플릿 메서드 패턴  (0) 2022.03.23
[Design Pattern] 프록시 패턴  (0) 2022.03.22
[Design Pattern] 어댑터 패턴  (0) 2022.03.16
[Design Pattern] 싱글톤 패턴  (0) 2022.03.15
[Design Pattern] 빌더 패턴  (0) 2022.03.14

+ Recent posts