なぜSpring DIを使うのか


スプリングフレームの代表的な技術の一つである依存注入(DI)について説明する.
なぜDIという技術が現れたのか.そして、この記事では使用後に得られるメリットをまとめています.

DIとは何ですか。


注入
  • 依存関係、注入依存性
  • クライアントがどのサービスを使用するかを示すのではなく、クライアントがどのサービスを使用するかを指定します.
  • 「注入」は、使用するオブジェクト(クライアント)に依存性(サービス)を渡すことを意味する
  • DI出現背景



    理解を助けるために、各クラスの詳細な機能を実現するのではなく、上記のクラス図を参照してコードのコア部分を説明します.
    public class MemberServiceImpl implements MemberService {
    
        MemberRepository memberRepository = new JdbcMemberRepository();
    
        @Override
        public void join(Member member) {
            memberRepository.save(member);
        }
    
    }
  • MemberServiceImplは、格納メンバーのRepositoryをJDBCとして使用します.
  • が突然JDBC->JPAに変更されました.
  • はインタフェースを設計した自分自身に満足し、新しいJpaMemberRepository実装クラスを作成し、適用した.
  • public class MemberServiceImpl implements MemberService {
    
        MemberRepository memberRepository = new JpaMemberRepository();
    
        @Override
        public void join(Member member) {
            memberRepository.save(member);
        }
    
    }
    多形性を利用して、インプリメンテーションのみをJpaMemberRepositoryに変更して、何の問題もないようで、よく動いています!
    しかし、このコードには問題があります.
    オブジェクト向け設計の原則に反するコード
    オブジェクト向けの設計原則ではSRP,OCP,DIPに違反している.
    これらの原則を簡単に説明します.
  • SRP 단일 책임 원칙 (Single Responsibility Principle)
  • クラスは1つの責任しか負いません.
  • OCP 개방-폐쇄원칙 (Open/Closed Principle)
  • ソフトウェア要素は拡張時に開いていますが、変更時には閉じている必要があります.
  • DIP 의존관계 역전 원칙 (Dependency Inversion Principle)
  • の具体化ではなく抽象化に頼る.
  • クライアントコードは、実装クラスに依存せず、インタフェースに依存すべきである.
  • では、上記のコードのどの部分がOCP、DIP、SRPに違反しているかをよく見てみましょう.
    MemberRepository memberRepository = new JdbcMemberRepository();
    MemberRepository memberRepository = new JpaMemberRepository();

  • SRP違反
  • クライアントコードは、独自の論理を実行するだけでなく、依存関係の責任にも関心を持っています.

  • DIP違反
  • 抽象(インタフェース)とともに、特定の(実装)クラスにも依存する.
  • 抽象依存:MemberRepositor
  • :JdbcMemberRepository,JpaMemberRepositor
  • に依存する.

  • OCP違反
  • は、新しいニーズに対応して機能を拡張し、クライアントコードも変化しました.
  • new JdbcMemberRepository() -> new JpaMemberRepository()
  • まとめると、クライアントコードには多くの責任があり、インタフェースだけでなく、具体的なクラスも知っています.そのため、機能拡張が発生すると、ソースコードの変更も発生します.
    どうやって問題を解決しますか??
    正解は、インタフェースのみに依存するように設計を変更する必要があります.
    public class MemberServiceImpl implements MemberService {
    
        MemberRepository memberRepository;
        
        @Override
        public void join(Member member) {
            memberRepository.save(member);
        }
    }
    体現体new JpaMemberRepository()はキャンセルされ、界面のみに依存するようになった.
    コンパイルは成功しましたが、実行時にインプリメンテーションボディがないため、NPE(null pointerexception)が生成されます.
    では、インタフェースだけに依存するようにどのように設計すればいいのでしょうか.누군가 클라이언트 MemberServiceImpl에 MemberRepository 구현체를 대신 생성하고 주입にします.
    このため依存注入(DI)概念が出現した.

    興味の分離-(AppConfig)


    DIの目的は관심사를 분리を作ることです.
    すなわち,クライアントコード内部に依存関係を設けず,関心事項を外部に分離する.
    前の問題を解決するために新しいクラスを作成します.
    アプリケーション全体の動作を構成するために、実装オブジェクトを作成および接続する責任と役割を持つクラス.
    public class AppConfig {
    
        public MemberService memberService() {
            return new MemberServiceImpl(memberRepository());
        }
    
        public MemberRepository memberRepository() {
            return new JdbcMemberRepository();
        }
    
    }
    AppConfigというクラスが作成されました.
  • インプリメンテーションオブジェクトの作成
  • MemberServiceImpl
  • JdbcMemberRepository
  • 作成者注入
  • によって作成されたオブジェクトインスタンス参照
  • MemberServiceImpl -> JdbcMemberRepository
  • 次に、クライアントコード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);
        }
    }
  • MemberServiceImpl(クライアントコード)では、どのようなインプリメンテーションが注入されるか分かりません.
  • 生成器を介してどのインプリメンテーションボディを注入するかはAppConfigによって決定される.
  • MBERServiceImpl(クライアントコード)は、リレーションシップに依存せず、実行に専念します(ビジネスロジック)
    注意:finalキーワードは、宣言とともに初期化されたポイントと再割り当てを阻止するために使用されます.
  • class MemberServiceTest {
    
        MemberService memberService;
    
        @BeforeEach
        public void beforeEach() {
            AppConfig appConfig = new AppConfig();
            memberService = appConfig.memberService();
        }
    
        @Test
        void join() {
            Member member = new Member(1L, "lee");
            memberService.join(member);
            ...
        }
    }
    テストコードは次のとおりです.AppConfigで依存性を得ることができます.
    以前のようにJDBC->JPAに変更した場合?
    AppConfigを変更するだけです.
    public class AppConfig {
    
        public MemberService memberService() {
            return new MemberServiceImpl(memberRepository());
        }
    
        public MemberRepository memberRepository() {
            return new JpaMemberRepository();
        }
    
    }

    References

  • https://ko.wikipedia.org/wiki/%EC%9D%98%EC%A1%B4%EC%84%B1_%EC%A3%BC%EC%9E%85
  • https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8