본문 바로가기
디자인 패턴

데코레이터 패턴 + [F]

by oncerun 2022. 2. 19.
반응형

 

스프링에서는 빈 후처리기를 통해 스프링 컨테이너에 빈이 등록되기 전 다양한 조작을 할 수 있다.

빈이 등록되기 전 여러 작업을 하여 최종적으로 스프링 컨테이너에 등록한다.

또한  target의 메서드 호출 전 데코레이터 패턴을 추가하여 추가 기능을 확장할 수 있다.

 

함수형 프로그래밍에서는 Function Composition이라는 개념은 합성 함수를 만드는 개념입니다. 

이 개념 또한 함수에 함수를 합성해 기능을 추가하는 기능으로 데코레이터 패턴을 확장한 개념이라고 할 수 있습니다.

 

이렇게 객체에 기능을 확장이 필요할 때 우리는 확장된 서브 클래스를 만드는 대신 데코레이터 패턴을 고려할 수 있습니다. 자바에서는 이러한 확장을 하기 위해선 다형성이라는 개념을 사용합니다.

 

인터페이스를 생성하고 해당 인터페이스를 구현한 추상 클래스를 만들고 이 추상 클래스를  상속받아 구현한 클래스를 정의하고, 구현체에서는 target의 기능을 하기 전 확장된 기능을 실행한 후 최종적으로 target을 실행하도록 한다.

 

 

[예시]

 

만약 앞의 빌더 패턴에서 정의한 Car Class의 가격을 가공하는 일이 필요한데, 이 가격은 수많은 기능 확장이 일어날 수 있다고 가정하자. 

 

예를 들어 차량의 가격을 가져올 때 어떤 경우에는 할인 + 세율 + 배송비를 합친 최종 결과가 필요하거나, 기본 차량의 가격이 필요하거나, 기본 값 + 세율... 등 필요하다면 이를 어떻게 구현할 것인가??

 

@ToString
public class Car {

    private long id;
    private String name;
    private String brand;
    private BigDecimal distanceDriven;
    private boolean autopilot;
    private Price price;

    public Car(Builder builder) {
        this.id = builder.id;
        this.name = builder.name;
        this.brand = builder.brand;
        this.distanceDriven = builder.distanceDriven;
        this.autopilot = builder.autopilot;
        this.price = builder.price;
    }


    public static Builder builder(long id){
        return new Builder(id);
    }

    public static class Builder {
        private long id;
        public String name;
        public String brand;
        public BigDecimal distanceDriven;
        public boolean autopilot;
        public Price price;

        private Builder(long id) {
            this.id = id;
        }

        public Builder with(Consumer<Builder> consumer) {
            consumer.accept(this);
            return this;
        }

        public Car build() {
            return new Car(this);
        }

    }

}

 

price 필드를 추가했다. 이 Price Class는 단순하게 가격 필드만 있는 것으로 가정하고 진행하자. 

 

 

1.  Functional Interface 정의

@FunctionalInterface
public interface PriceProcessor {

    Price process(Price price);

    default PriceProcessor andThen(PriceProcessor next) {
        return price -> next.process(this.process(price));
    }

}

 

2. Price 객체 정의


@ToString
public class Price {
    private BigDecimal price;

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public BigDecimal getPrice() {
        return price;
    }
}

 

 

3. 함수형 인터페이스의 클래스를 생성하여 사용하는 것 대신 람다를 이용할 생각이다.

 

PriceProcessor priceProcessor = price -> {
    //BasicPrice Processor 차량의 기본 값 설정
    price.setPrice(BigDecimal.valueOf(161000000));
    return price;
};

PriceProcessor decoratedProcessor = priceProcessor
        .andThen( price -> {
            //9.76의 세금을 부과
            BigDecimal taxPrice = new BigDecimal("9.76")
                    .divide(new BigDecimal(100))
                    .add(BigDecimal.ONE)
                    .multiply(price.getPrice());
            price.setPrice(taxPrice);
            return price;
        })
        .andThen( price -> {
            //항공료를 추가
            price.setPrice(price.getPrice().add(BigDecimal.valueOf(300000)));
            return price;
        });

 

 

andThen을 통해 세금을 부과한 가격을 set 하는 Processor와 배송료를 추가하는 Processor를 추가해 

합쳐진 decoratedProcessor를 생성하였다.

 

 

Car car = Car.builder(1L)
        .with(builder -> {
            builder.name = "Model A";
            builder.autopilot = true;
            builder.distanceDriven = BigDecimal.valueOf(103.23);
            builder.brand = "Tesla";
            builder.price = decoratedProcessor.process(new Price());
        })
        .build();

System.out.println(car);

빈 Price객체를 decoratedProcessor에 넘기면 세금률을 부과한 가격과 배송료를 추가한 가격으로 값이 초기화된다.

 

이번엔 Function Composition을 통해 데코레이터를 구현해보았다. 

반응형

'디자인 패턴' 카테고리의 다른 글

GoF 구조적인 패턴 소개  (0) 2022.11.06
책임 연쇄 패턴 +[F]  (0) 2022.02.19
빌더 패턴 + [F]  (0) 2022.02.19
데코레이터 패턴  (0) 2022.01.21
프록시 패턴  (0) 2022.01.20

댓글