spring 解耦的原因探究

    上一篇介绍了spring容器使用的方法,但是在使用spring框架之前,我们需要知道我们为什么要用spring框架。  否则就变成了为学而学了。 由于优先级的关系,我就先写了使用的方法,这一篇写一下为什么要用。

     我们知道,spring提供的两大核心: ioc  以及 aop。   这是从它的角度而言,那么从我们开发者的角度而言呢?  不可能说你很优秀,特别优秀,于是所有人都要臣服你吧!   只能说你很优秀,特别优秀,能够给我们带来积极的影响,于是我们愿意臣服你。  他对我们的终极意义就一个:  解耦!  正如java开发最终的目标是开闭原则一样,我们需要这个优秀的特性。

      根据老师的思路,自己再整理整理,然后看看它是如何解耦的。先根据我们通常使用的开发步骤而言:

    定义一个抽象武器类:

package com.automannn;

/**
 * @author [email protected]
 * @time 2018/9/12 9:20
 */
public interface Weapon {

   void attack();
}

  定义三个具体的武器类:

package com.automannn.impl;

import com.automannn.Weapon;

/**
 * @author [email protected]
 * @time 2018/9/12 9:26
 */
public class Dex implements Weapon {

    public void attack() {
        System.out.println("用斧子攻击了!");
    }
}
package com.automannn.impl;

import com.automannn.Weapon;

/**
 * @author [email protected]
 * @time 2018/9/12 9:25
 */
public class Gun implements Weapon {

    public void attack() {
        System.out.println("用枪攻击了!");
    }
}
package com.automannn.impl;

import com.automannn.Weapon;

/**
 * @author [email protected]
 * @time 2018/9/12 9:26
 */
public class Knife implements Weapon {
    @Override
    public void attack() {
        System.out.println("用刀攻击了!");
    }
}

   定义持有武器的路人甲:

package com.automannn.entity;

import com.automannn.Weapon;

/**
 * @author [email protected]
 * @time 2018/10/9 13:36
 */
public class RudeMan {
    public static final String NAME ="龙哥";

    Weapon weapon;


    public Weapon getWeapon() {
        return weapon;
    }

    public void setWeapon(Weapon weapon) {
        this.weapon = weapon;
    }

    public void attack(){
        System.out.print(NAME+":");
        weapon.attack();
    }
}

   启动测试类:

package com.automannn.test;

import com.automannn.entity.RudeMan;
import com.automannn.impl.Knife;

/**
 * @author [email protected]
 * @time 2018/10/9 13:42
 */
public class Main {
    public static void main(String[] args) {
        RudeMan rudeMan = new RudeMan();
        rudeMan.setWeapon(new Knife());
        rudeMan.attack();
    }
}

   运行结果:

   何谓耦合呢?   

      比如,这个路人甲他带了有武器,这个武器可以是刀,但是不一定非得是刀。   但是其实这里代码的设计上已经不存在结构上的耦合了!  因为有本优秀的逐月工程师在!  哈哈。   那么这个时候存在另外一种耦合,就是运行时耦合!  什么意思呢?  就是当程序运行起来的时候,这个刀实际上与路人甲已经绑定在一起了。  称之为死耦合哈哈哈。  因为我们实际上是有这样的需求的,当项目发布上线了之后,需要根据某种策略动态的选择实现类,如果没有解耦的特性,那么框架不仅不能带来什么优势,反而降低程序效率,变成一个累赘。

   第一种解耦方式:   工厂方法。

       

package com.automannn.factory;

import com.automannn.Weapon;
import com.automannn.impl.Dex;
import com.automannn.impl.Gun;
import com.automannn.impl.Knife;

/**
 * @author [email protected]
 * @time 2018/10/9 13:50
 */
public class WeaponFactory {
    private Weapon weapon;

    WeaponFactory(){}
    
    public Weapon getInstance(int order) {
        switch (order){
            case 0:
                weapon= new Dex();
                break;
            case 1:
                weapon= new Knife();
                break;
                default:
                    weapon =new Gun();
        }
        return weapon;
    }
}

   这个时候,我在运行时,可以通过接收命令的方式动态的改变路人甲使用的武器了。

package com.automannn.test;

import com.automannn.entity.RudeMan;
import com.automannn.factory.WeaponFactory;
import com.automannn.impl.Knife;

/**
 * @author [email protected]
 * @time 2018/10/9 13:42
 */
public class Main {
    public static void main(String[] args) {
        RudeMan rudeMan = new RudeMan();
        //rudeMan.setWeapon(new Knife());
        rudeMan.setWeapon(WeaponFactory.getInstance(2));
        rudeMan.attack();
    }
}

     但是这个时候我们需要注意,这种方式是实现类已经知道,但是我们保不定以后还要增加实现类。  因此这里仍然存在着较高的耦合性。

第二种解耦方式:配置文件的方式

      由于工厂方式存在较告的耦合性,因此我们下面采用配置文件的方式解耦;

      

package com.automannn.property;

import com.automannn.Weapon;

import java.util.ResourceBundle;

/**
 * @author [email protected]
 * @time 2018/10/9 14:02
 */
public class PropertyHandler {

   static Weapon weapon;

    //读取配置文件,核心类为 Properties
    //有三种实现方式,分别是:  基于ClassLoader,InputStream,和  java.util.ResouceBundle
    public static Weapon getWeapon() {
        //Properties properties = new Properties();
        ResourceBundle rb = ResourceBundle.getBundle("test");
        String weaponName= rb.getString("weapon");
        try {
            Object object =  Class.forName(weaponName).newInstance();
            weapon = (Weapon) object;

        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return weapon;

    }
}

  配置文件:

weapon = com.automannn.impl.Dex

使用:

package com.automannn.factory;

import com.automannn.Weapon;
import com.automannn.impl.Dex;
import com.automannn.impl.Gun;
import com.automannn.impl.Knife;
import com.automannn.property.PropertyHandler;

/**
 * @author [email protected]
 * @time 2018/10/9 13:50
 */
public class WeaponFactory {
    private static Weapon weapon;

    WeaponFactory(){}

    public static Weapon getInstance() {
       weapon = PropertyHandler.getWeapon();
        return weapon;
    }
    
}
package com.automannn.test;

import com.automannn.entity.RudeMan;
import com.automannn.factory.WeaponFactory;
import com.automannn.impl.Knife;

/**
 * @author [email protected]
 * @time 2018/10/9 13:42
 */
public class Main {
    public static void main(String[] args) {
        RudeMan rudeMan = new RudeMan();
        //rudeMan.setWeapon(new Knife());
        rudeMan.setWeapon(WeaponFactory.getInstance());
        rudeMan.attack();
    }
}

    就目前而言,我们应该是已经完全解除了它的耦合性,为什么说呢?

       1,武器的实现类可以自己配置,自己配置包含两个含义:  配置系统种原有的其它实现类;   增加新增的实现类,不一定在包内部,只要能够被类加载器加载,哪怕放在rt.jar包里也行。

       2,可以在程序外部配置,也就是在程序运行过程中,修改配置文件达到效果。 

第三种解耦方式: 注解方式解耦,对配置文件的一种改进

    在上面,我们增加一个实现类需要经过这样的步骤:  定义实现类---> 配置实现类。无形中我们需要做两个步骤才能达到我们的要求。关键是我们需要来回切换,在繁杂的配置文件中定位到想要的位置进行配置。  那么使用注解的方式好处就来了。

    定义注解类:

package com.automannn.annotation;

import java.lang.annotation.*;

/**
 * @author [email protected]
 * @time 2018/9/12 10:12
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Inject {
    String value() default "";
}

   定义注解处理类:,它是基于反射实现的。  但是实际上我们时时在用反射,就比如new User(),  那么它也是通过反射实现的。

package com.automannn.entity;

import com.automannn.Weapon;
import com.automannn.annotation.Inject;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author [email protected]
 * @time 2018/9/12 10:17
 */
public class AnnotationHolder {
    public AnnotationHolder() throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {
        Method[] methods = Hero.class.getMethods();
        for (Method m: methods){
            Annotation annotation;
            if ((annotation= m.getDeclaredAnnotation(Inject.class))!=null){
                Inject inject = (Inject) annotation;
                String weaponName= inject.value();
                Weapon wp = (Weapon) Class.forName(weaponName).newInstance();
                 m.invoke(this,wp);
            }
        }

    }
}

使用:

package com.automannn.entity;

import com.automannn.annotation.Inject;
import com.automannn.Weapon;

import java.lang.reflect.InvocationTargetException;

/**
 * @author [email protected]
 * @time 2018/9/12 9:28
 */
public class Hero  extends AnnotationHolder{
    Weapon weapon;
    private String name;
    public Hero(String name, Weapon weapon) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
        super();
        this.name = name;
        this.weapon = weapon;
    }
    public Hero() throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
        super();
    }
    public void attack(){
        System.out.print(name+"   ");
        weapon.attack();
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Weapon getWeapon() {
        return weapon;
    }
    @Inject(value = "com.automannn.impl.Gun")
    public void setWeapon(Weapon weapon) {
        this.weapon = weapon;
    }


}

  到目前为止,我们就已经做到了完全解耦!  实际上在配置文件那里就已经完全解耦,这里更多的是提高了设计的内聚度更恰当一点吧!

-----------------------------------------------------------------------------------------

   最后补充一下读取配置文件的几种方式,因为学了这么久的java,还没有自己去度过配置文件,我还是有点汗颜的。

   第一种读取配置文件的方式:ClassLoader.getResourceAsStream,由官方提供的工具包中的工具,java.util.Properties解析。

Properties properties = new Properties();
        // 使用ClassLoader加载properties配置文件生成对应的输入流
        InputStream in = Base.class.getClassLoader().getResourceAsStream("test.properties");
        // 使用properties对象加载输入流
        try {
            properties.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //获取key对应的value值
        properties.getProperty("weapon");

第二种读取配置文件的方式: new FileReader("")的方式读取,由官方提供的工具包中的工具,java.util.Properties解析。

Properties properties = new Properties();
        // 使用InPutStream流读取properties文件
        BufferedReader bufferedReader = null;
        try {
            bufferedReader = new BufferedReader(new FileReader("D:\\mydev\\demo\\src\\main\\resources\\test.properties"));
            properties.load(bufferedReader);
            properties.getProperty("weapon");
        } catch (Exception e) {
            e.printStackTrace();
        }

第三种读取配置文件的方式: 由官方提供的工具包中的工具,java.util.ResourceBundle.getBundle()处理,并解析。

ResourceBundle rb = ResourceBundle.getBundle("test");
        String weaponName= rb.getString("weapon");
        try {
            Object object =  Class.forName(weaponName).newInstance();
            this.weapon = (Weapon) object;

        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

  需要注意,以上三种方式都由java官方提供支持,不需要再依赖第三方的依赖包。

猜你喜欢

转载自blog.csdn.net/qq_36285943/article/details/82980977