IoC 아이디어 및 구현

공식적인 모호한 정의를 먼저 붙여넣지 말고 예를 사용하여 IoC 아이디어를 단계별로 소개하고 IoC 아이디어가 우리 프로그래밍에 가져다주는 이점을 진정으로 감사하십시오.

자동차, 엔진, 타이어

자동차에는 엔진과 타이어가 필요하며 다음과 같은 모델이 있습니다.

/**
 * 东风汽车
 */
class NissanCar{
    
    
    private Engine engine;
    private Tyre tyre;

    public NissanCar() {
    
    
        this.engine = new EngineV1();
        this.tyre = new TyreV1();
    }
}

/**
 * 大众汽车
 */
class VolkswagenCar{
    
    
    private Engine engine;
    private Tyre tyre;

    public VolkswagenCar() {
    
    
        this.engine = new EngineV1();
        this.tyre = new TyreV1();
    }
}

// .... 还有几十种其他品牌的车。用的都是 V1 引擎

/**
 * 引擎接口,输出动力
 */
interface Engine {
    
    
    void outputPower();
}

class EngineV1 implements Engine {
    
    

    public void outputPower() {
    
    
    }
}

/**
 * 轮胎接口,可以转动
 */
interface Tyre {
    
    
    void turn();
}
class TyreV1 implements Tyre {
    
    

    public void turn() {
    
    

    }
}

어떤 자동차에 엔진과 타이어가 필요하더라도 자동차를 만들 때 엔진과 타이어라는 오브젝트를 동시에 구성해야 자동차가 정상적으로 작동할 수 있습니다. 아름다운. 그러던 어느 날까지만 해도 큰 교통사고가 났는데 누군가가 차를 몰고 가다가 갑자기 가속을 하여 제동을 못 하게 되었습니다. 그는 다른 차를 들이받을 때까지 멈추지 않았고 매우 심각한 부상을 입었습니다. 자동차 제조업체의 엔지니어들은 오랫동안 점검한 결과 엔진에 문제가 있음을 발견했습니다.어떤 경우에는 갑자기 통제 불능 상태가 되어 최대 마력으로 회전했습니다.

엔진 제조업체는 이 문제를 신속하게 수정하고 v2 버전의 엔진을 출시했습니다. 모든 차량이 최신 엔진으로 교체되는 한 이 문제는 완전히 해결될 것입니다. ... 이 현상은 위의 모델에 해당합니다.

class EngineV2 implements Engine {
    
    

    public void outputPower() {
    
    
    }
}

그런 다음 모든 자동차의 건설 방법에서 다음과 같이 수정해야 합니다 this.engine = new EngineV2(). 큰 문제는 아닌 것 같고 에디터를 사용하여 전역 대체를 합니다.

잠깐, 우리가 작성한 프로그램은 단지 예일 뿐이라는 것을 잊지 마세요. 매우 간단합니다. 실제 응용 프로그램에서는 그렇게 간단한 응용 프로그램을 가질 수 없습니다. 명확하고 명확한 종속성 관계, 우리 응용 프로그램의 사용자는 자동차에 의존합니다. , 자동차는 엔진에 달려있다, 타이어, 시트, 변속장치, 제동장치에 달려있다 변속장치는 엔진과 상호작용할 수 있고, 제동장치도 엔진과 상호작용할 수 있어 매우 복잡한 종속성이다. . 글로벌 교체 후 생산에 직접 제출하지 마십시오. 철저한 테스트를 거쳐야 하고,

우리의 프로그램은 작성한 후 그대로 유지되는 것은 불가능합니다.우리의 프로그램은 실생활의 문제를 해결하는 것이며 실생활에서 가장 필요한 것은 변화입니다.수요가 변하면 우리 프로그램은 이렇게 될 것입니다.큰 변화는 재앙입니다. 프로그래머용. 해결책을 찾아야 합니다.

공장 방법

재앙의 원인은 근본적인 엔진이 바뀌었고, 그 엔진이 많은 자동차에 의존하고 있기 때문에 자동차도 바뀌어야 한다. 엔진 공장에 말하십시오: 엔진을 주십시오. 미래에 엔진을 교체해야 하는 경우 많은 자동차 제조업체는 더 이상 변경하지 않고 여전히 엔진 공장에 다음과 같이 말합니다. 엔진 공장은 다음과 같이 적합한 엔진을 자동차 제조업체에 반환합니다. :

/**
 * 东风汽车
 */
class NissanCar{
    
    
    private Engine engine;
    private Tyre tyre;

    public NissanCar() {
    
    
        this.engine = EngineFactory.get();
        this.tyre = TyreFactory.get();
    }
}

/**
 * 大众汽车
 */
class VolkswagenCar{
    
    
    private Engine engine;
    private Tyre tyre;

    public VolkswagenCar() {
    
    
        this.engine = EngineFactory.get();
        this.tyre = TyreFactory.get();
    }
}

// .... 还有几十种其他品牌的车。用的都是 V1 引擎

/**
 * 引擎工厂
 */
class EngineFactory {
    
    
    public static Engine get(){
    
    
        return new EngineV2();
    }
}

/** 
 * 轮胎工厂
 */
class TyreFactory {
    
    
    public static Tyre get(){
    
    
        return new TyreV1();
    }
}

원래 자동차 제조업체가 자체적으로 엔진을 구성한 다음 자동차에 조립(할당 작업)하는 것에서 엔진을 직접 조립하는 것으로 단순화할 수 있으며 더 이상 엔진을 제조하는 방법에 신경을 쓰지 않아도 됩니다. 엔진 업그레이드는 엔진 공장의 책임이며 자동차 제조업체와는 아무런 관련이 없으며 엔진 공장이 하나 뿐이고 업그레이드 작업량이 상대적으로 적습니다. 코드 변경이 거의 없는 경우 전체 시스템에 미치는 영향은 상대적으로 적습니다. 세상은 다시 더 나은 곳입니다. 그러나 방금 말했듯이 이것은 단순한 샘플 프로그램일 뿐이며 종속성이 비교적 명확합니다.실제 세계에서 자동차는 엔진, 타이어, 시트, 스티어링 휠, 조명, 브레이크, 기어박스 등만 가질 수 없습니다. 생각하고 계속해서 공장을 추가하고 각 부분에 공장 메서드를 추가할 수 있습니다. 자동차를 조립하는 데 필요한 부품은 모두 공장 방식으로 얻습니다. 새 개체를 직접 만들지 마십시오. 뭐 당연히 가능하지만 자동차는 최소한 수백 개의 부품이 필요하고 우리 모두가 직접 공장 방식을 구축한 다음 조립해야 합니까? 이것은 피곤하다.

자동차를 구성하기 위해 어떤 작업이 수행되는지 살펴 보겠습니다.

  1. 필수 액세서리 선언(정의 속성: 개인 엔진 엔진 등)
  2. 액세서리 가져오기(EngineFactory.get()과 같이 생성 메서드에서 액세서리의 팩토리 메서드 호출)
  3. 조립 부품(this.engine = EngineFactory.get()과 같은 구성 방법에서 할당됨)

자동차 제조사가 자체적으로 부품을 구성하든, 팩토리 방식을 통해 구성하든 그것은 우리의 새로운 부품 객체(2단계)이며, 그 다음 우리가 부품 객체를 자동차에 할당하여 자동차와 부품 간의 종속 관계를 해결합니다(단계 3) 지능형 자동차 생산 작업장을 만드는 것이 가능한지 더 생각해 봅시다. 생산 작업장에 정의된 자동차 모델과 모든 액세서리 모델을 알려준 다음(1단계) 생산 작업장에서 모델이 액세서리를 생산합니다. 자체적으로(2단계) 액세서리를 자동차에 조립합니다(3단계).

실제로 이것은 완전히 가능합니다.프로그래머가 모든 클래스를 정의하고 클래스가 의존하는 다른 개체도 명확합니다.자동 새 개체 및 개체 속성 할당과 같은 기계적 작업은 특정 프로그램에 의해 자동으로 실행될 수 있습니다. 이 "작업장"을 만드는 방법을 보자

작업장

  1. 먼저 엔진 개체를 생성하고 타이어 개체를 풀에 넣습니다.
  2. 자동차에 필요한 모든 액세서리 분석
  3. 수영장으로 이동하여 액세서리를 찾고, 찾은 액세서리를 자동차의 해당 속성에 할당하고 새 자동차를 구성하십시오.
  4. 구성된 자동차 오브젝트를 풀에 넣습니다.

다음은 몇 가지 단순화입니다.

  1. 작업장은 엔진 오브젝트를 먼저 만들고 타이어 오브젝트를 만든 다음 자동차 오브젝트를 만드는 방법을 알고 있습니다.
  2. 엔진은 다른 액세서리에 의존하지 않습니다.

엔진이 다른 A 구성 요소에 종속되고 A 구성 요소가 다른 B 구성 요소에 종속되는 경우. B 액세서리를 먼저 만들고 A 액세서리를 만든 다음 엔진을 만들 건가요? 이 정해진 순서로만 정상적으로 작동할 수 있습니다. 그렇다면 이 공방은 너무 연약하고 경직된 것입니다. 작업장을 더 똑똑하게 만들 수 있습니다.자동차 오브젝트를 만들 때 자동차가 엔진에 의존하는 것을 발견하면 자동차를 기다리게 하고 엔진을 먼저 만드세요.액세서리를 만드세요. 완전한 액세서리를 구성할 때마다 풀에 넣고 다음에 필요할 때 사용하십시오.

위의 단계는 코드에 해당하며 클래스의 필드, 필드의 유형을 가져와서 리플렉션을 통해 실현해야 합니다. 완전한 예는 다음과 같습니다.

/**
 * 东风汽车
 */
class NissanCar{
    
    
    private Engine engine;
    private Tyre tyre;

    void run(){
    
    
        this.engine.outputPower();
        this.tyre.turn();
        System.out.println("东风汽车开跑");
    }
}

/**
 * 大众汽车
 */
class VolkswagenCar{
    
    
    private Engine engine;
    private Tyre tyre;

    void run(){
    
    
        this.engine.outputPower();
        this.tyre.turn();
        System.out.println("大众汽车开跑");
    }

}


// .... 还有几十种其他品牌的车

class EngineFactory {
    
    
    public static Engine get(){
    
    
        return new EngineV2();
    }
}

class TyreFactory {
    
    
    public static Tyre get(){
    
    
        return new TyreV1();
    }
}

/**
 * 引擎接口,输出动力
 */
interface Engine {
    
    
    void outputPower();
}

class EngineV1 implements Engine {
    
    

    @Override
    public void outputPower() {
    
    
        System.out.println("我是 Engine1");
    }
}

class EngineV2 implements Engine {
    
    

    @Override
    public void outputPower() {
    
    
        System.out.println("我是 Engine2");
    }
}

/**
 * 轮胎接口,可以转动
 */
interface Tyre {
    
    
    void turn();
}
class TyreV1 implements Tyre {
    
    

    @Override
    public void turn() {
    
    
        System.out.println("我是 Tyre1");
    }
}

public class IocDemo {
    
    

    /**
     * 存放实例化的对象,key 类类名,value 是对象
     */
    public static Map<String,Object> objectPool = new HashMap<>();

    public static void buildObjectPool() throws InstantiationException, IllegalAccessException {
    
    
        // 将待实例化的类添加到集合中
        // 这一步可以写一个自定义注解实现,把所有需要自动化实例的类上面添加我们自定义注解, 然后扫描所有类,把包含这个注解的类添加到集合中
        List<Class<?>> classList = new ArrayList<>();
        classList.add(NissanCar.class);
        classList.add(VolkswagenCar.class);
        classList.add(EngineV1.class);
        classList.add(EngineV2.class);
        classList.add(TyreV1.class);

        // 所有类是否都已经实例化
        boolean okFlag = false;

        // 遍历所有待实例化的集合,逐一实例化
        // 并不能一次循环就可以全部实例化完毕,假如A依赖B, 但是B还没有实例化,所以A暂时也不能实例化,等下一次循环,B 实例化后,再实例化A
        // 如果 A 依赖B, B 依赖A, 那么在这个循环永远不能结束。产生了循环依赖,这里仅做示例。不考虑循环依赖
        while(!okFlag){
    
    
            okFlag = true;
            for (Class<?> klass : classList) {
    
    
                // 如果还没有被实例化
                if(!objectPool.containsKey(klass.getName())){
    
    
                    // 利用反射,实例化该类。 如果klass有其他未实例化的依赖,o 等于 null
                    Object o = getInstance(klass);
                    if(o != null){
    
    
                        // 类名作为 key
                        objectPool.put(klass.getSimpleName(), o);
                        // 如果该类实现了一些接口,那么接口对应的实例也是该类的实例
                        for (Class<?> klassInterface : klass.getInterfaces()) {
    
    
                            objectPool.put(klassInterface.getSimpleName(), o);
                        }
                    }else{
    
    
                        // 还存在没有实例化的类,不能结束
                        okFlag  = false;
                    }
                }
            }
        }

    }

    private static Object getInstance(Class<?> klass) throws IllegalAccessException, InstantiationException {
    
    
        // new 一个空对象
        Object o =  klass.newInstance();
        String beanName = "";
        // 为对象中的所有字段赋值
        for (Field field : klass.getDeclaredFields()) {
    
    
            // 获取字段的类型
            beanName = field.getType().getSimpleName();
            // 所依赖的字段的实例暂时在对象池中找不到,暂不实例化
            if(!objectPool.containsKey(beanName)){
    
    
                return null;
            }
            // 设置私有变量可以访问
            field.setAccessible(true);
            // 从对象池中获取该字段的实例
            Object value = objectPool.get(beanName);
            // 为字段赋值
            field.set(o,value);
        }
        return o;
    }

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
    
    

        buildObjectPool();

        /**
         * 通过类型向容器中取对象,对象中的所有依赖已经自动处理完毕。
         */
        NissanCar nissanCar = (NissanCar)objectPool.get("NissanCar");
        nissanCar.run();

        VolkswagenCar volkswagenCar = (VolkswagenCar) objectPool.get("VolkswagenCar");
        volkswagenCar.run();
    }
}

결과:

我是 Engine2
我是 Tyre1
东风汽车开跑
我是 Engine2
我是 Tyre1
大众汽车开跑

main 메서드와 다양한 자동차 클래스에서 new 키워드를 사용하여 객체를 구성하는 것이 아니라, 리플렉션 기술을 사용하여 다양한 타입 간의 종속성을 자동으로 분석하고 값을 할당합니다. 따라서 완전한 개체를 개체 풀에 하나씩 넣습니다.

위의 코드는 단순한 예시일 뿐이며 순환 종속성 문제, 인스턴스 충돌 문제(여러 구현 클래스가 있는 하나의 인터페이스)와 같은 많은 문제가 해결되지 않았습니다. 비교적 조잡하지만 자동 구성의 아이디어를 보여주기에는 충분합니다.

IoC에 대해 다시 이야기

  1. 맨 처음에 모든 종속 액세서리는 자동차의 구성 방법에서 직접 새로운 것입니다.이것은 자동차의 활성 액세서리 구성입니다.특정 액세서리가 변경되면 모든 자동차가 변경됩니다.

  2. 이후 자동차용 악세서리를 적극적으로 제작하는 단계는 각 악세서리 공장으로 이관되어 특정 악세사리가 변경되면 해당 공장을 직접 변경하면 된다.

위의 두 가지 방법은 액세서리가 증가함에 따라 점점 더 많은 템플릿 코드로 이어질 것입니다. 수천 개의 부속품이 있는 경우 1: 구성 방법에서 부속품을 하나씩 구성한 다음 자동차의 해당 필드에 할당해야 합니다. 2의 경우: 코드를 대량으로 변경하는 문제를 피하기 위해 액세서리의 구성 단계를 공장 방식으로 이전하지만 많은 공장 방식을 작성해야 하며 공장 방식의 내용은 유사합니다. 다른 시스템을 개발하면 자동차 시스템의 모든 팩토리 메소드를 재사용할 수 없습니다.

  1. 마지막으로 스마트 작업장(buildObjectPool)을 만들었습니다. 작업장에 모든 자동차 및 액세서리의 클래스를 알려주기만 하면 작업장은 자동으로 종속성을 유지하고 각 개체를 생성하여 풀(objectPool)에 넣고 대기합니다. 우리가 사용합니다. 동시에 이 워크샵에는 유형 정보가 없으며 수정 없이 모든 시스템에 사용할 수 있습니다.

1, 2, 3을 비교해보면 가장 큰 차이점은 객체의 종속 관계가 외부 도구가 아닌 객체 자체에 의해 해결된다는 점입니다.

이것이 바로 IoC의 아이디어 입니다. 위에서 작성한 buildObjectPool 도구는 IoC 아이디어의 간단한 구현입니다. IoC 아이디어의 더 유명한 기술 구현은 종속성 주입(DI)입니다.

IoC는 기술이 아니라 느슨하게 결합된 더 나은 프로그램을 설계하는 방법을 안내할 수 있는 중요한 객체 지향 프로그래밍 규칙인 아이디어라는 점을 기억하십시오. 기존 애플리케이션에서는 클래스 내부에 종속 개체를 적극적으로 생성하므로 클래스 간의 결합도가 높고 테스트하기가 어렵습니다. 개체가 느슨하게 결합되어 테스트에 편리하고 함수 재사용에 도움이 되며 더 중요한 것은 프로그램의 전체 아키텍처를 매우 유연하게 만듭니다.

사실 IoC가 프로그래밍에 가져오는 가장 큰 변화는 코드에서가 아니라 이념적 관점에서 보면 "마스터-슬레이브 전치(master-slave transposition)" 변화가 일어났다. 원래는 애플리케이션이 보스이고 모든 리소스를 획득하기 위해 주도권을 잡았지만 IoC/DI 사고에서는 애플리케이션이 수동적이 되어 IoC 컨테이너가 필요한 리소스를 생성하고 주입하기를 수동적으로 기다립니다.

IoC는 객체 지향 설계 규칙 중 하나인 "우리를 찾지 말고 우리가 당신을 찾습니다"라는 할리우드 규칙을 잘 구현합니다. 객체가 적극적으로 찾고 있습니다.

더 많은 IoC 개념은 다음을 참조하십시오.

Guess you like

Origin blog.csdn.net/a141210104/article/details/127700520