DI 용기를 작성해서 의존 주입을 이해합니다. 처음부터!(첫 부분)

의존 주입(Dependency Injection, DI)은'매직'이 많은 것처럼 보이기 때문에 이해하기 어려운 주제일 수 있다.보통, 그것은 곳곳에 흩어져 있는 주석과 관련이 있는데, 대상은 어디에서 오는지 알 수 없는 것 같다.나는 당연히 내가 이 개념을 진정으로 이해하는 데 매우 오랜 시간이 걸렸다는 것을 안다.스프링과 자바이가 배후에서 무슨 짓을 했는지 이해하기 어려우면(그리고 왜!)읽어라!
이 강좌에서, 우리는 자바로 아주 간단하지만 기능이 완비된 의존항 주입 용기를 처음부터 구축할 것이다.다음은 업무를 쉽게 관리할 수 있는 몇 가지 규칙이다.
  • JDK 자체를 제외하고는 어떤 라이브러리도 사용할 수 없습니다.
  • 미리 준비된 코드 덤프가 없습니다.우리는 매사를 꼼꼼히 고려하고 추리를 진행할 것이다.
  • 방울과 호루라기는 없고 기본적인 것만 있다.
  • 성능은 중요하지 않으며 최적화되지 않았습니다.
  • 각 단계의 실행 가능main() 방법.
  • 다른 글과 비교했을 때, 나는 DI가 무엇인지, 왜 유용한지 미리 설명하지 않을 것이다.반대로 우리는 간단한 예로부터 그것을 변화시킬 것이다.

    DI 단계 0: 기본 예

    Find the source code of this section on github


    우리는 매우 기본적인 예로부터 시작한다.우리는 두 종류를 원한다. 우리는 그것들ServiceAServiceB이라고 부르고, ServiceA는 그것의 일을 완성하기 위해 ServiceB가 필요하다.좋아, 너는 static 방법으로 그것을 실현하고 그것을 완성할 수 있어!다음은 다음과 같습니다.
    public class ServiceA {
    
        public static String jobA(){
            return "jobA(" + ServiceB.jobB() + ")";
        }
    
    }
    
    public class ServiceB {
    
        public static String jobB(){
            return "jobB()";
        }
    
    }
    
    public class Main {
    
        public static void main(String[] args) {
            System.out.println(ServiceA.jobA());
        }
    
    }
    
    이 코드를 실행하면 다음과 같이 인쇄됩니다.
    jobA(jobB())
    
    쿨!그럼, 왜 더 많은 일을 해야 합니까?좋아요.
  • 이 코드는 OO원칙에 있어서 매우 나쁘다.서비스 A 및 ServiceB는 최소한 객체여야 합니다.
  • 코드가 밀접하게 결합되어 단독으로 테스트하기 어렵다.
  • 서비스 A와 ServiceB를 다른 방식으로 교환할 기회는 절대 없습니다.그중 한 명이 신용카드 계산서를 만들고 있다고 상상해 보세요.너는 이런 상황이 너의 테스트 세트에서 발생하는 것을 원하지 않는다.

  • DI 단계 1: 정적 참조에서 벗어나기

    Find the source code of this section on github


    우리가 단계 0에서 발견한 주요 문제는 정적 방법만 있어 매우 긴밀한 결합을 초래한다는 것이다.우리는 우리의 서비스가 서로 이야기하는 대상이 되기를 바란다. 그러면 우리는 필요에 따라 그것들을 교체할 수 있기를 바란다.이제 질문이 왔습니다. ServiceA에서 어떤 ServiceB와 대화하는지 어떻게 알 수 있습니까?가장 기본적인 생각은 ServiceB 인스턴스를 ServiceA의 구조 함수에 간단하게 전달하는 것입니다.
    public class ServiceA {
    
        private ServiceB serviceB;
    
        public ServiceA(ServiceB serviceB){
            this.serviceB = serviceB;
        }
    
        public String jobA(){
            return "jobA(" + this.serviceB.jobB() + ")";
        }
    
    }
    
    서비스 B 는 크게 달라지지 않았습니다.
    public class ServiceB {
    
        public String jobB() {
            return "jobB()";
        }
    
    }
    
    ... Main 현재 대상을 조립한 다음에 jobA 방법을 호출할 수 있습니다.
       public static void main(String[] args) {
            ServiceB serviceB = new ServiceB();
            ServiceA serviceA = new ServiceA(serviceB);
    
            System.out.println(serviceA.jobA());
        }
    
    별거 아니야, 그렇지?우리는 확실히 몇 가지를 개선했다.
  • 우리는 현재 다른 대상을 제공하여 ServiceB에서 사용한 ServiceA의 실현을 대체할 수 있으며, 심지어는 다른 하위 클래스의 실현일 수도 있다.
  • ServiceA의 적절한 테스트 시뮬레이션을 사용하여 두 서비스를 개별적으로 테스트할 수 있습니다.
  • a. 좋아요?전혀 그렇지 않습니다.
  • 시뮬레이션을 만들기가 매우 어렵다. 왜냐하면 우리는 하나의 종류가 필요하기 때문이다.
  • 인터페이스가 필요하면 더 좋아요.이 밖에 이것은 결합을 더욱 줄일 것이다.

  • DI단계 2: 인터페이스 사용

    Find the source code of this section on github


    따라서 모든 서비스에 인터페이스를 사용하자.그것들은 매우 간단하다. (나는 실제 클래스를 각각 ServiceAImplServiceBImpl 로 이름을 바꾸었다.
    public interface ServiceA {
    
        public String jobA();
    
    }
    
    public interface ServiceB {
    
        public String jobB();
    
    }
    
    현재 ServiceAImpl에서 우리는 실제 인터페이스를 사용할 수 있다.
    public class ServiceAImpl implements ServiceA {
    
        private final ServiceB serviceB;
    
        public ServiceAImpl(ServiceB serviceB){
            this.serviceB = serviceB;
        }
    
        // jobA() is the same as before
    }
    
    또한 Dell의 main() 접근 방식도 향상되었습니다.
      public static void main(String[] args) {
            ServiceB serviceB = new ServiceBImpl();
            ServiceA serviceA = new ServiceAImpl(serviceB);
            System.out.println(serviceA.jobA());
        }
    
    대상을 향한 측면에서 볼 때 이것은 간단한 용례로 볼 때 충분히 받아들일 수 있다.만약 네가 벌을 면할 수 있다면, 그것은 여기까지다.하지만, 당신의 프로젝트가 점점 커지면서...
  • main() 방법에서 서비스 네트워크를 만드는 것은 갈수록 복잡해질 것이다.
  • 귀하는 서비스 의존 관계에서 구조 함수를 사용하여 해결할 수 없는 순환을 만나게 됩니다. 예를 들어 저희의 예시와 같습니다.

  • DI단계 3: 세터 사이클 깨기

    Find the source code of this section on github

    ServiceA가 필요할 뿐만 아니라 반대로 우리는 순환이 있다고 가정합시다.물론 우리는 ServiceB류의 구조 함수에서 다음과 같은 매개 변수를 설명할 수 있다.
    
     // constructor examples
     public ServiceAImpl(ServiceB serviceB) { ... }
     public ServiceBImpl(ServiceA serviceA) { ... }
    
    
    ... 그러나 이것은 우리에게 이롭지 않다. 우리는 이 두 종류 중 어떤 실제 실례도 만들 수 없다.*Impl의 실례를 만들려면 ServiceAImpl의 실례를 먼저 필요로 하고 ServiceB의 실례를 만들려면 ServiceBImpl의 실례를 먼저 필요로 한다.우리는 교착 상태에 빠졌다.

    Side note: there is actually a way out of this by using proxies. However, we're not going to take that route here.


    그럼 우리 어떻게 해야 되지?순환 의존 관계를 처리해야 하기 때문에, 우리는 먼저 서비스를 만들고 그것을 연결해야 한다.우리는 세터로 이 점을 해냈다.
    public class ServiceAImpl implements ServiceA {
    
        private ServiceB serviceB;
    
        // no constructor anymore here!
    
        @Override // <- added getter to ServiceA interface
        public ServiceB getServiceB() { return serviceB; }
    
        @Override // <- added setter to ServiceA interface
        public void setServiceB(final ServiceB serviceB) { this.serviceB = serviceB; }
    
        // jobA() same as before
    }
    
    DellServiceA 접근 방식은 다음과 같습니다.
        public static void main(String[] args) {
            // create instances
            ServiceB serviceB = new ServiceBImpl();
            ServiceA serviceA = new ServiceAImpl();
    
            // wire them together
            serviceA.setServiceB(serviceB);
            serviceB.setServiceA(serviceA);
    
            // call business logic
            System.out.println(serviceA.jobA());
        }
    
    너는 도안을 볼 수 있니?먼저 대상을 만들고 그것들을 연결해서 서비스 맵(즉 서비스 네트워크)을 형성한다.
    근데 왜 여기 안 멈춰요?
  • 수동으로 연결 부분을 실행하면 오류가 발생하기 쉽다.너는 아마도 세터에게 전화하는 것을 잊어버렸을 것이다. 그리고 그것은 main() 아주 많아졌다.
  • 사용자가 무의식중에 구축 중인 서비스 실례를 사용했을 수도 있기 때문에 어떤 방식으로 네트워크 구축을 봉인하는 것이 유익할 것입니다.

  • 다음
    다음 블로그에서 우리는 현재 수동으로 진행되고 있는 배선을 어떻게 자동화하는지 토론할 것이다.

    좋은 웹페이지 즐겨찾기