「Spring」オブジェクト向け原則の適用-新しい割引ポリシー&「DI」


新しい割引ポリシーの作成


パーセント割引ポリシーの変更


VIPの場合は10%割引になります
public class RateDiscountPolicy implements DiscountPolicy{

    private int discountPercent = 10;

    @Override
    public int discount(Member member, int price) {
        if (member.getGrade() == Grade.VIP){
            return price * discountPercent / 100;
        } else{
            return 0;
        }
    }
}
TestCaseを実行します(Ctrl+Shift+Tキーを押すとTest Classを作成するショートカットキーが得られます)
class RateDiscountPolicyTest {
    RateDiscountPolicy discountPolicy = new RateDiscountPolicy();

    @Test
    @DisplayName("VIP는 10%할인이 적용되어야 한다.")
    void vip_o(){
        //given
        Member member = new Member(1L, "memberVIP", Grade.VIP);
        //when
        int discount = discountPolicy.discount(member, 10000);
        //then
        Assertions.assertThat(discount).isEqualTo(1000);
    }

    @Test
    @DisplayName("VIP가 아니면 할인이 적용되지 않아야 한다.")
    void vip_x(){
        //given
        Member member = new Member(2L, "memberBASIC", Grade.BASIC);
        //when
        int discount = discountPolicy.discount(member, 10000);
        //then
        Assertions.assertThat(discount).isEqualTo(0);
    }
}

成功!

新しい割引ポリシーと問題の適用


新しい割引ポリシーを導入しようとしたり、変更しようとしたりすると、実際にDIP、OCPを守れないという問題が発生します!!

問題を発見する

  • のキャラクターと実装を忠実に分離した.
  • 多形性運用,インタフェースと体現対象を分離する.
    3.OCPやDIPなどのオブジェクト向けの設計原則を遵守していますか?X
  • DIP:受注サービスクライアント(OrderServiceImpl)はDiscountPolicyインタフェースに依存してDIPを保護しているようですか?
    ->クラスの依存関係を解析する場合は,抽象(インタフェース)だけでなく,特定の(実装)クラスにも依存する.
    -抽象(インタフェース)依存:DiscountPolicy
    -具体(実施)依存:FixDiscountPolicy、RateDiscountPolicy
  • OCP:変更せずに拡張できるって言ったでしょ?
    -機能を変更すると、クライアントコードに影響します.OCP違反

    DiscountPolicyインタフェースのみに依存していると思います.

  • よく見ると、OrderServiceImplはDiscountPolicyインタフェースだけではありません.
    FixDiscountPolicy実装クラスにも依存します.つまりDIP違反(DIP:常に抽象に依存)

    DIP違反のため、ポリシーが変更された場合
    FixDiscountPolicyをRateDiscountPolicyに変更する場合は、OrderServiceImplのソースコードも同時に変更する必要があります.OCP違反

    この問題はどのように解決しますか。

  • クライアントコードOrderServiceImplはDiscountPolicyのインタフェースだけでなく、特定のクラスにも依存します.
  • したがって、特定のクラスを変更する場合は、クライアントコードも一緒に変更する必要があります.
  • DIP違反->抽象(インタフェースのみ)
  • に変更
  • DIPに違反しないためには,依存インタフェースの依存関係を変えるだけでよい.
  • インタフェースに依存してデザインを変更しましょう!

    しかし、実装体はなく、コードをどのように実行しますか?
    実際に実行するとNPEが生成されます.

    ソリューション


    この問題を解決するには、クライアントOrderServiceImpleにDiscountPolicyのインプリメンテーションオブジェクトを作成して注入する必要があります!!

    注目点の分離



    AppConfigの出現


    アプリケーションの完全な動作を構成するには、実装オブジェクトの作成と接続を担当する個別の設定クラスを作成します.
    MemberServiceImpl
    public class MemberServiceImpl implements MemberService{
    
        private final MemberRepository memberRepository;
    
        public MemberServiceImpl(MemberRepository memberRepository) {
            this.memberRepository = memberRepository;
        }
    
        @Override
        public void join(Member member) {
            memberRepository.save(member);
        }
    
        @Override
        public Member findMember(Long memderId) {
            return memberRepository.findById(memderId);
        }
    }
    AppConfig
    public class AppConfig {
        public MemberService memberService(){
            // 생성자 주입; 생성자를 만들어놓고 구현체를 주입시킴.
            return new MemberServiceImpl(new MemoryMemberRepository());
        }
    
        public OrderService orderService(){
            return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
        }
    }
    
    説明:まずAppConfigでアクセスし、誰かがMemberServiceを呼び出したときにインプリメンテーションを作成し、ジェネレータ注入インプリメンテーションを実行します.
    つまり.
  • AppConfigアプリケーションの実際の操作に必要な実施対象を生成します.
  • MemberServiceImpl
  • MemeoryMemberRepository
  • OrderServiceImpl
  • FixDiscountPolicy
  • AppConfigは、作成されたオブジェクトインスタンスをジェネレータを介して参照します(参照).
  • MemberServiceImpl -> MemoryMemberRepository
  • OrderServiceImpl -> MemoryMemberRepository, FixDiscountPolicy
  • MmeberServiceImplはこれから依存関係の悩みを外部に任せ,実行に頼るだけでよい.


    依存注入(DI)-「注入依存関係」



    DI:抽象(インタフェース)は直接呼び出し実装ではなく,外部(AppConfig)から依存関係を注入する.

    AppConfigの実行


    MemberApp
    public class MemberApp {
        public static void main(String[] args) {
            AppConfig appConfig = new AppConfig();
    //      MemberServiceImpl을 직접 호출하지 않음. 구현체는 모두 appConfig가 결정
            MemberService memberService = appConfig.memberService();
    //        MemberService memberService = new MemberServiceImpl();
    
            Member member = new Member(1L, "memberA", Grade.VIP);
            memberService.join(member);
    
            Member findMember = memberService.findMember(1L);
            System.out.println("new member = "+ member.getName());
            System.out.println("find member = "+ findMember.getName());
        }
    }
    OrderApp
    public class OrderApp {
        public static void main(String[] args) {
            AppConfig appConfig = new AppConfig();
            MemberService memberService = appConfig.memberService();
            OrderService orderService = appConfig.orderService();
    //        MemberService memberService = new MemberServiceImpl(null);
    //        OrderService orderService = new OrderServiceImpl(null, null);
    
            Long memberId = 1L;
            Member member = new Member(memberId, "memberA", Grade.VIP);
            memberService.join(member);
    
            Order order = orderService.createOrder(memberId, "itemA", 10000);
            System.out.println("order = "+order);
            System.out.println("order = "+order.calculatePrice());
        }
    }
    MemberServiceTest
    public class MemberServiceTest {
    
        MemberService memberService;
    
        @BeforeEach
        public void beforeEach(){
            AppConfig appConfig = new AppConfig();
            memberService = appConfig.memberService();
        }
    	...
    }
    OrderServiceTest
    public class OrderServiceTest {
        MemberService memberService;
        OrderService orderService;
    
        @BeforeEach
        public void beforeEach(){
            AppConfig appConfig = new AppConfig();
            memberService = appConfig.memberService();
            orderService = appConfig.orderService();
        }
        ...
    }

    具体的なクラスに依存しない!!すべて外注に頼っている。


    整理する

  • AppConfigで興味点
  • を明確に区別した
  • 役、俳優を考えてみましょう
  • AppConfigは演出プランナー
  • AppConfig特定のクラスを選択します.役にふさわしい俳優を選ぶ.アプリケーションがどのように動作するかは、構成全体が担当します.
  • 現在、各俳優は担当職能を履行するだけでよい.
  • OrderServiceImplは、機能を実行するだけです.
  • 定額割引->パーセント割引
  • FixDiscountPolicy -> RateDiscountPolicy
  • AppConfig再編成


    改造前

    public class AppConfig {
        public MemberService memberService(){
            // 생성자 주입; 생성자를 만들어놓고 구현체를 주입시킴.
            return new MemberServiceImpl(new MemoryMemberRepository());
        }
    
        public OrderService orderService(){
            return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
        }
    }
    :キャラクターの体現が見えない.

    改造後

    public class AppConfig {
        public MemberService memberService(){
            // 생성자 주입; 생성자를 만들어놓고 구현체를 주입시킴.
            return new MemberServiceImpl(memberRepository());
        }
    
        private MemberRepository memberRepository() {
            return new MemoryMemberRepository();
        }
    
        public OrderService orderService(){
            return new OrderServiceImpl(memberRepository(), discountPolicy());
        }
    
        public DiscountPolicy discountPolicy(){
            return new FixDiscountPolicy();
        }
    }
    :役割の分業が明確で、一目瞭然です.
    :重複データは消去されました.

    新しい構造と割引ポリシーの適用


    AppConfigの出現は、アプリケーションを大量の使用領域とオブジェクトの作成と構成領域に分割します.

    構成領域を変更するだけです!!
    AppConfig

    ここを変更するだけで...
  • では、アプリケーションの構成ロールAppConfigを変更するだけで割引ポリシーを変更できます.使用領域のコードを変更する必要はありません!!
  • の構成領域はもちろん変更されます.構成を担当するAppConfigをアプリケーションのプランナーと見なしてみましょう.演出企画者は、演出参加者のすべての実施対象を理解しなければならない.