본문 바로가기
ComputerScience/Design Pattern

Strategy Pattern

by 규난 2023. 2. 20.
728x90

Strategy Pattern 이란?

객체를 독립적으로 선택하고 사용할 수 있도록 하는 디자인 패턴입니다.

이 패턴은 전략을 인터페이스로 정의하고, 인터페이스의 구현체를 만들어 이를 상황에 맞게 사용하는 방법입니다.

전략 패턴을 사용하면 런타임 시점에 알고리즘을(실 서비스에서 쿠폰이나 할인율 같은 경우) 선택할 수 있으므로 유연하게 동작할 수 있습니다.

 

런타임 시점에 알고리즘을 선택할 수 있다는 말은

실행 시간에(런타임 시점) 코드에서 사용할 객체를 선택할 수 있다는 것을 의미합니다.

일반적으로 객체는 컴파일 시점에 결정됩니다. 즉, 코드가 컴파일되는 시점에 사용할 객체가 결정되고, 이후에는 변결할 수 없게되는데 전략 패턴을 사용하면 런타임 시점에 다른 객체를 선택하여 사용할 수 있습니다.

 

전략 패턴의 장점

  1. 런타임 시점에 알고리즘을 교체할 수 있습니다.
  2. SOILD 원칙 중 OCP 원칙을 깨지 않으면서 개발을 할 수 있습니다.

 

Strategy Pattern 예제

Coupon Interface

package strategy;

public interface Coupon {
    int calc(ProductEntity product);
}

 

고정 할인율을 적용할 쿠폰 interface의 구현체

package strategy;

public class FixedDiscountCoupon implements Coupon {
    private int discount;

    public FixedDiscountCoupon(int discount) {
        this.discount = discount;
    }

    @Override
    public int calc(ProductEntity product) {
        return Math.max(product.getPrice() - discount, 0);
    }
}

 

백분율 할인율을 적용할 쿠폰 interface의 구현체

package strategy;

public class PercentageCoupon implements Coupon {
    private int ratio;

    public PercentageCoupon(int ratio) {
        this.ratio = ratio;
    }

    @Override
    public int calc(ProductEntity product) {
        int discountAmount = (int) (product.getPrice() * (double) ratio / 100);

        return Math.max(product.getPrice() - discountAmount, 0);
    }
}

 

쿠폰의 정보를 가져오는 레포지토리

실제 데이터 베이스와 연동하지 않고 mockRepository를 만들어서 진행하였습니다.

package strategy;

import java.util.Optional;

public interface CouponRepository {
    Optional<CouponEntity> findById(Long id);
}
package strategy;

import java.util.Optional;

public class MockCouponRepository implements CouponRepository {

    @Override
    public Optional<CouponEntity> findById(Long id) {

        if (Objects.equals(id, 1L)) {
            return Optional.of(new CouponEntity(1L, CouponType.FIX));
        }

        if (Objects.equals(id, 2L)) {
            return Optional.of(new CouponEntity(2L, CouponType.PERCENTAGE));
        }

        return Optional.empty();
    }
}

 

쿠폰 타입

데이터 베이스에 저장된 쿠폰 타입에 따라 위에서 쿠폰 인터페이스를 구현한 구현체를 반환하기 위한 enum class

package strategy;


public enum CouponType {
    FIX(new FixedDiscountCoupon(1000)),
    PERCENTAGE(new PercentageCoupon(10));

    private Coupon coupon;

    CouponType(Coupon coupon) {
        this.coupon = coupon;
    }

    public Coupon getCoupon() {
        return coupon;
    }
}

 

Coupon Entity

package strategy;

public class CouponEntity {

    private Long id;
    private CouponType couponType;

    public CouponEntity(Long id, CouponType couponType) {
        this.id = id;
        this.couponType = couponType;
    }

    public Long getId() {
        return id;
    }

    public CouponType getCouponType() {
        return couponType;
    }
}

 

ProductEntity

package strategy;

public class ProductEntity {

    private String name;
    private int price;

    public ProductEntity(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public int discount(Coupon coupon) {
        return coupon.calc(this);
    }

    public String getName() {
        return name;
    }

    public int getPrice() {
        return price;
    }
}

 

쿠폰을 반환해 줄 CouponService

package strategy;

public class CouponService {

    private CouponRepository couponRepository = new MockCouponRepository();

    public Coupon getCoupon(long couponId) {
        CouponEntity coupon = couponRepository.findById(couponId)
                .orElseThrow(() -> new RuntimeException("없는 쿠폰번호 아이디입니다."));

        return coupon.getCouponType().getCoupon();
    }
}

 

동작 확인을 위한 Main code

package strategy;

public class Main {

    private static CouponService couponService = new CouponService();
    public static void main(String[] args) {

        ProductEntity synthaProtein = new ProductEntity("신타 6 아이솔레이트", 77800);

        Coupon fixedDiscountCoupon = couponService.getCoupon(1L);
        System.out.println("신타 6 고정 1000 쿠폰 사용 : " + synthaProtein.discount(fixedDiscountCoupon));

        Coupon percentageDiscountCoupon = couponService.getCoupon(2L);
        System.out.println("신타 6 10프로 할인 가격 : " + synthaProtein.discount(percentageDiscountCoupon));
    }
}

위 코드를 실행하면 고정 할인 쿠폰을 사용 시 1000원이 할인된 가격 76800원이 콘솔 창에 찍히고

백분율 할인율 쿠폰을 사용 시 10프로 할인된 가격인 70200원이 콘솔 창에 찍히는 것을 확인할 수 있습니다.

 

실 서비스에서는 이렇게 간단하게 쿠폰을 적용할 일이 없겠지만 상품에 적용할 수 있는 쿠폰이 여러 가지 있을 경우 전략 패턴을 사용하여 실행 시점에 동적으로 구현체를 변경하여 다른 할인율을 적용할 수 있게 됩니다. 이 패턴은 실무에서 굉장히 많이 쓰이는 패턴이라서 꼭 알아두면 좋을 거 같다고 생각합니다.

728x90

'ComputerScience > Design Pattern' 카테고리의 다른 글

Adaptor Pattern  (0) 2023.03.06
Facade Pattern  (0) 2023.02.26
디자인 패턴 - 디자인 원칙  (0) 2023.02.14
Builder Pattern  (0) 2023.02.01
Singleton Pattern  (0) 2023.01.30