Springコア原理TIL(3)


[参考講座]金英漢のスプリングコア原理-基本編

💡 サンプルの作成


📍 ビジネスニーズと設計

  • 会員
  • 会員の登録と照会ができます.
  • 会員には普通とVIPの2つの等級があります.
  • 会員データは、独自のDBを構築したり、外部システムと連動したりすることができます.(保留中)
  • オーダーおよび割引ポリシー
  • 会員は商品を注文することができます.
  • 会員等級によって割引政策が適用されます.
  • 割引政策は、すべてのVIPに1000ウォンの固定金額割引を要求した.
  • 割引政策が変わる可能性が高い.(保留中)
  • 📍 メンバードメイン設計


    メンバードメインのコラボレーション



    会員等級図



    メンバー・オブジェクトのグラフ

  • クライアント->メンバー・サービス->メモリ・メンバー・リポジトリ
  • 회원 서비스 : MemberServiceImplメンバー・サービス・インタフェースが実装されました.慣例に従ってImplを貼り付けます.

    📍 メンバードメインの開発


    会員等級

    package hello.core.member;
    
    public enum Grade {
         BASIC,
         VIP 
    }

    💡 Enumデータ型とは?


    列挙タイプと呼びます.
    つまり、限定された値があれば、このタイプを使うことができます.
    例えば月曜日火曜日水曜日木曜日のような日

    メンバーエンティティ

    public class Member {
    
          private Long id;
          private String name;
          private Grade grade;
          
          public Member(Long id, String name, Grade grade) {
              this.id = id;
              this.name = name;
              this.grade = grade;
    	}
        
        // 이후는 getter and setter code

    メンバー・リポジトリ・インタフェース

    public interface MemberRepository {
     
            void save(Member member);
            Member findById(Long memberId);
    }
    

    メモリメンバーストレージインプリメンテーション


    デバイスがまだ特定されていないため、メモリ・メンバー・リポジトリをテストします.
    public class MemoryMemberRepository implements MemberRepository {
    
            private static Map<Long, Member> store = new HashMap<>();
            
            @Override
            public void save(Member member) {
                store.put(member.getId(), member);
            }
            
            @Override
            public Member findById(Long memberId) {
                return store.get(memberId);
            }
    }

    会員サービスインタフェース

    public interface MemberService {
    
          void join(Member member);
          
          Member findMember(Long memberId);
    }

    会員サービス実施体

     public class MemberServiceImpl implements MemberService {
     
          private final MemberRepository memberRepository = new
      MemoryMemberRepository();
          public void join(Member member) {
              memberRepository.save(member);
    	}
          public Member findMember(Long memberId) {
              return memberRepository.findById(memberId);
    	} 
    }
     

    メンバードメインの実行とテスト


    会員登録ホームページ
    public class MemberApp {
    
          public static void main(String[] args) {
          
              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());
    	} 
    }
    JUnitテスト
    class MemberServiceTest {
      
     MemberService memberService = new MemberServiceImpl();
     
          @Test
          void join() {
    	//given
              Member member = new Member(1L, "memberA", Grade.VIP);
    	//when
              memberService.join(member);
              Member findMember = memberService.findMember(1L);
    	//then
              Assertions.assertThat(member).isEqualTo(findMember);
          }
    }
    

    📍メンバードメイン設計における問題


    前回の授業で学んだ内容と同じです.
    OCP、DIPの原則に合致しない
    😱 依存関係はインタフェースだけでなく,実装体にも依存する.

    📍 オーダーと割引ドメインの設計


    ドメインコラボレーション、ロール、ロールの管理



    1.クライアントは、受注サービスによって受注を生成します.
    2.注文サービスは会員リポジトリで会員を調べ、等級を知る.
    3.注文サービスは等級を調べるため、割引政策により割引を実施する
    4、注文サービスは割引を適用した結果とともに注文結果を返品する.

    オーダードメインクラス図



    上記の設計では、注文サービスを変更することなく、メンバーのメモリ内のクエリー方式をDBクエリー方式に変更しました.
    割引ポリシーを変更しても、オーダーサービスを変更する必要はありません.
    提携関係をそのまま再利用できます.😎

    📍 発注および割引ドメインの開発


    割引ポリシーインタフェース

       public interface DiscountPolicy {
          /**
    	* @return 할인 대상 금액
    	*/
          int discount(Member member, int price);
      }

    固定割引政策実施体

    public class FixDiscountPolicy implements DiscountPolicy {
    
          private int discountFixAmount = 1000; //1000원 할인
    
          @Override
          public int discount(Member member, int price) {
              if (member.getGrade() == Grade.VIP) {
                  return discountFixAmount;
              } else {
                  return 0;
    	} 
        }
    }
    等級VIPは1000元割引の政策です.

    受注エンティティ

     public class Order {
     
        private Long memberId;
        private String itemName;
        private int itemPrice;
        private int discountPrice;
        
        public Order(Long memberId, String itemName, int itemPrice, int
    discountPrice) {
            this.memberId = memberId;
            this.itemName = itemName;
            this.itemPrice = itemPrice;
            this.discountPrice = discountPrice;
        }
    //이후 getter setter code
    
        @Override
        public String toString() {
            return "Order{" +
             } }
            "memberId=" + memberId +
            ", itemName='" + itemName + '\'' +
            ", itemPrice=" + itemPrice +
            ", discountPrice=" + discountPrice +
            '}';
         }
    }
    
    TOString()を使用してオブジェクトを返す場合
    toString形式で出力!

    オーダーサービスインタフェース

    public interface OrderService {
          Order createOrder(Long memberId, String itemName, int itemPrice);
    }

    オーダーサービス実施機関

    public class OrderServiceImpl implements OrderService {
    
          private final MemberRepository memberRepository = new MemoryMemberRepository();
          private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
          
          @Override
          public Order createOrder(Long memberId, String itemName, int itemPrice) {
      
              Member member = memberRepository.findById(memberId);
              int discountPrice = discountPolicy.discount(member, itemPrice);
              
              return new Order(memberId, itemName, itemPrice, discountPrice);
          }
    
    }
    注文するときは、会員情報を照会し、等級を理解し、割引ポリシーを適用し、注文オブジェクトを生成して返します.

    発注および割引ドメインの実行

    public class OrderApp {
          public static void main(String[] args) {
              MemberService memberService = new MemberServiceImpl();
              OrderService orderService = new OrderServiceImpl();
              
     	  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);
          }
    }
    

    テスト

    class OrderServiceTest {
          MemberService memberService = new MemberServiceImpl();
          OrderService orderService = new OrderServiceImpl();
      
         @Test
         void createOrder() {
            long memberId = 1L;
            Member member = new Member(memberId, "memberA", Grade.VIP);
            memberService.join(member);
            
            Order order = orderService.createOrder(memberId, "itemA", 10000);
            Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
        }
    }
    すべてのテストで青信号が点灯しました😎

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


    新しい割引政策を拡張します.
    既存の政策がVIPクラスだと1000元の定額割引しかしないなら、
    金額割引の定率割引に変更します.

    RateDiscountPolicyの追加



    RateDiscountPolicyコードの追加

    public class RateDiscountPolicy implements DiscountPolicy { 
    
          private int discountPercent = 10; //10% 할인
          
          @Override
          public int discount(Member member, int price) {
          
              if (member.getGrade() == Grade.VIP) {
                   return price * discountPercent /  100;
              } else {
    	       return 0; 
              }
         } 
    }

    テスト

    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
              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
              assertThat(discount).isEqualTo(0);
          }
    }
    

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


    割引ポリシーをアプリケーションに適用すると問題が発生します.
    クライアントOrderServiceImplコードを修正する必要があります.
    public class OrderServiceImpl implements OrderService {
      //    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
          private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
      }

    に質問

  • のキャラクターを完全に分離します.
  • 多形性を用いて,インタフェースと実装オブジェクトを分離した.
  • しかし、このコードもOCP、DIPのオブジェクト向け設計の原則を守っていない.
  • DIPの問題:
    OrderServiceImplはDiscountPolicyインタフェースと実装クラスにも依存します.
  • OCPの問題:
    コードがクライアントコードに影響を及ぼし、機能を拡張および変更します.
  • ポリシーの変更


    次の図は、OCP、DIPに違反していることを明確に示しています.

    📍 トラブルシューティング方法


    DIPに違反しないように抽象のみに依存するように変更
    =設計をインタフェースのみに依存するように変更

    コードをインタフェースのみに依存するように変更

    public class OrderServiceImpl implements OrderService {
          //private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
          private DiscountPolicy discountPolicy;
  • 実際には、このコードを実行するとNull pointエラーが発生します.
    実装体がないため、
  • を実行することができない.
    ソリューション
    この問題を解決するには、OrderServiceImplにDiscountPolicyインプリメンテーションを作成して注入する必要があります.次のレッスンから、上記のソリューションの内容を詳しく説明します.😎