jsp网站访问万维网,wordpress 免费商业主题,深圳好的网站制作公司,外链查询网站为什么Spring中的IOC能够降低耦合性#xff1f; 前言1.传统方式2.使用接口3.工厂方法4.反射改造工厂类5.IOC总结参考 前言
本文目标#xff1a;本文旨在讲解为什么IOC能够降低耦合性。
情景#xff1a;假设你是一个爱摸鱼的程序员#xff0c;现在需要测试一个游戏#x… 为什么Spring中的IOC能够降低耦合性 前言1.传统方式2.使用接口3.工厂方法4.反射改造工厂类5.IOC总结参考 前言
本文目标本文旨在讲解为什么IOC能够降低耦合性。
情景假设你是一个爱摸鱼的程序员现在需要测试一个游戏该游戏有很多动物假设1万种每种动物的叫声都不相同现在需要测试动物的叫声是否能正常发出老板让你3天时间完成任务你如何在3天内完成任务并尽可能留出摸鱼时间。
1.传统方式
首先我们对可爱的Dog进行测试我们打开了Dog.java文件有如下代码
public class Dog(){public void bark(){System.out.println(Bark);}
}
接下来进行测试
public class TestGame {public static void main(String[] args) {Dog animal new Dog();animal.bark();//do something....}
}
然后我们打开Cat.java,代码如下
public class Cat(){public void meow(){System.out.println(Meow);}
}我们将 Dog 替换为 Cat但 Cat 没有 bark 方法传统方式下可能的修改如下
public class TestGame {public static void main(String[] args) {Cat animal new Cat();animal.meow();//do something....}
}
在我们的假设中有1万种动物且每种动物的叫声都不相同那我们需要逐一查看1万种动物的叫声实现方法即要修改1万次的测试代码假设修改代码和测试时间为1分钟假设每天工作八小时那是不可能的这是理想状态下那就是10000/60/818天不仅不能摸鱼还得加班。那我一天干24小时那就是10000/60/246天这只有3天时间这也没办法完成呀
这时候我们发现了如下问题: 硬编码依赖TestGame代码对特定的实现类如 Dog 或 Cat有直接依赖不具有灵活性。 高耦合度每次修改都需要手动调整代码(不同动物有不同的发声)增加了系统的维护成本。 不易扩展每当新增动物如 Bird时必须修改大量代码难以应对系统复杂度的增加。
2.使用接口
为了避免传统方式中的硬编码依赖、低耦合度以及不易扩展的问题我们引入面向接口编程。 爱摸鱼的程序员通过定义一个Animal接口使得每个动物类只需要实现该接口并提供自己的叫声实现方法。这样就不再需要在TestGame类中直接依赖具体的动物类而是依赖接口。
首先定义接口
public interface Animal {void makeSound();
}
实现接口的具体动物类
public class Dog implements Animal {Overridepublic void makeSound() {System.out.println(Bark);}
}public class Cat implements Animal {Overridepublic void makeSound() {System.out.println(Meow);}
}
修改测试类
public class TestGame {public static void main(String[] args) {Animal animal new Dog(); // 可以替换为其他动物类如Catanimal.makeSound(); // 通过接口调用不依赖具体类// do something....}
}
来对比下先后方法的不同
1.打开一个动物的java文件查看叫声方法。
2.复制类名和叫声方法。
3.修改类名和叫声方法。修改后 不用打开java文件查看叫声方法了我们有接口。 1.打开一个动物的java文件查看叫声方法。 2.复制类名和叫声方法 。 3.修改类名和叫声方法 。 方法变更如下
1.复制类名
2.修改类名通过上面的修改我们来计算看看节约了多少时间因为节约了打开文件查看叫声方法的步骤我们假设减少了50%的时间那18/29天也就是说每天八小时我们还需要9天才能完成要是一天24小时那就是6/23天。这啥也不干就是干活才能刚刚完成任务这可不行我们还得继续改。
优势 灵活性提高通过接口AnimalTestGame类不再依赖于具体的动物类而是依赖于一个抽象的接口。我们只需更换Dog为Cat等其他动物类而不必修改TestGame类。 易扩展性当新增动物类时只需要实现Animal接口并提供自己的makeSound()实现即可不需要修改TestGame类的代码系统更容易扩展。
缺点 1.需要创建接口和具体的实现类稍微增加了代码的复杂度。 2.在类的数量非常多如1万种动物的情况下仍然需要创建大量的具体类。
3.工厂方法
有new的地方需要具体类这就有硬编码有硬编码的地方就需要我们一个个修改代码文件如果没有new那就没有硬编码没有硬编码那我们就可以不用手动修改那如何取消硬编码不用new但是可以创建对象。 爱摸鱼的程序员尽管爱摸鱼但基本功还是蛮扎实嘛突然想起来了设计模式设计模式中好像有个工厂模式嘛那不是能创建对象嘛而且不需要指定要创建的具体类。 马上百度:工厂模式一搜就是暴击菜鸟教程虽然我菜但是不要这么直白嘛 菜鸟教程|工厂模式
工厂模式
工厂模式Factory Pattern是 Java 中最常用的设计模式之一它提供了一种创建对象的方式使得创建对象的过程与使用对象的过程分离。工厂模式提供了一种创建对象的方式而无需指定要创建的具体类。通过使用工厂模式可以将对象的创建逻辑封装在一个工厂类中而不是在客户端代码中直接实例化对象这样可以提高代码的可维护性和可扩展性。看了一眼我会了let me show you the code:
创建工厂类
public class AnimalFactory {public static Animal createAnimal(String animalType) {if (animalType.equals(Dog)) {return new Dog();} else if (animalType.equals(Cat)) {return new Cat();}// 可以继续添加更多动物类return null;}
}
那接下来如何简化呢,减少重复步骤呢唉重复重复就是循环那就来for循环呀
public class TestGame {public static void main(String[] args) {// 定义动物类型的数组String[] animalTypes {Dog, Cat, Bird};// 使用for循环遍历每种动物类型for (String animalType : animalTypes) {// 使用工厂方法创建动物对象Animal animal AnimalFactory.createAnimal(animalType);// 检查工厂是否成功创建了动物对象if (animal ! null) {System.out.println(Testing animalType :);animal.makeSound(); // 调用动物的发声方法System.out.println(); // 输出一个空行分隔每个测试结果} else {System.out.println(Unknown animal type: animalType);}}}
}
这一我们上述的步骤就改变了 1.复制类名 2.修改类名 步骤变为
1.复制类名加入列表
2.修改工厂类通过上面的修改我们减少了一个步骤但是增加了一些动作导致时间没有什么太多的变化。但是工厂类加入相应的判断可以通过程序遍历一万种动物的java文件来自动化生成。那假如我们建立好了工厂类和整个列表那测试起来就很快了。 但是我们现在假设只能获得整个列表而不能自动化修改工厂类仍然需要手动修改。那实际上我们只是把TestGame上的硬编码转移到了工厂类所以本次修改并未节约时间。
优点 简化代码通过工厂方法和循环我们可以用很少的代码测试多个动物类型避免重复编写类似的测试代码。 易于扩展如果需要新增动物类型只需要在 AnimalFactory 中添加相应的条件判断并确保每个动物类实现 Animal 接口无需修改测试代码。 自动化测试通过自动化地创建动物对象并调用其方法我们可以快速进行批量测试适应动物种类数量增加的需求。
缺点 1.需要创建工厂类增加了代码复杂度。 2.如果动物种类非常多工厂类的代码可能会变得庞大管理起来比较麻烦。如果工厂类过于庞大违反单一职责原则SRP包含1万条判断的工厂类几万行的代码这是一个多么恐怖的类。
4.反射改造工厂类
上面说了可以通过程序遍历一万种动物的java文件来自动化生成工厂类但是这种方法不够优雅。程序员的品味还是很高滴优雅是我们永恒滴追求 为了提高品味更优雅的使用我们使用反射进一步简化代码的创建过程尤其是在类的数量非常庞大的情况下。反射能够在运行时动态地加载类并创建对象而不需要预先在代码中硬编码类名。
在反射改造版本中我们将不再依赖硬编码的类名而是通过类名字符串来动态加载和实例化动物对象。通过反射我们可以通过类的全名包括包路径来加载和实例化对象从而使得扩展和测试变得更加灵活。
public class AnimalFactory {// 通过反射动态创建动物对象public static Animal createAnimal(String animalType) {try {// 根据动物类型字符串构建类名假设类都在同一包下String className com.example.animals. animalType;// 获取对应的Class对象Class? clazz Class.forName(className);// 创建该类的实例return (Animal) clazz.getDeclaredConstructor().newInstance();} catch (Exception e) {e.printStackTrace();}return null;}
}
public class TestGame {public static void main(String[] args) {// 定义动物类型的数组String[] animalTypes {Dog, Cat, Bird};// 使用for循环遍历每种动物类型for (String animalType : animalTypes) {// 使用工厂方法创建动物对象通过反射Animal animal AnimalFactory.createAnimal(animalType);// 检查工厂是否成功创建了动物对象if (animal ! null) {System.out.println(Testing animalType :);animal.makeSound(); // 调用动物的发声方法System.out.println(); // 输出一个空行分隔每个测试结果} else {System.out.println(Unknown animal type: animalType);}}}
}
反射改造的优势 灵活性通过反射类名是动态指定的不需要在代码中硬编码类名。这使得当动物种类增多时不需要修改代码只需要确保类的全名和接口实现一致即可。 扩展性如果新增动物类只需要将新类添加到对应的包下无需修改 TestGame 或 AnimalFactory 类只需将类名添加到动物类型数组中即可。 减少硬编码避免了每个类都需要在工厂类中手动添加判断条件简化了代码。 极高的灵活性可以动态地加载类和创建对象不需要在编译时就知道具体的实现类。 本次修改的步骤改造为
1.复制类名加入列表 2.修改工厂类
所以步骤变为
1.复制类名加入列表现在我们还有一处硬编码就是需要在TestGame加入列表每次修改都需要修改代码。聪明的小伙伴应该已经想到办法了那就是使用配置文件将该处的代码设置从外部读取。 先来一个配置读取类
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;public class ConfigReader {// 读取配置文件的方法public static String[] getAnimalTypes() {Properties properties new Properties();try (FileInputStream inputStream new FileInputStream(config.properties)) {properties.load(inputStream);String animalTypes properties.getProperty(animal.types);return animalTypes.split(,);} catch (IOException e) {e.printStackTrace();return new String[0]; // 返回空数组以防出错}}
}
修改TestGame
public class TestGame {public static void main(String[] args) {// 从配置文件读取动物类型String[] animalTypes ConfigReader.getAnimalTypes();// 使用for循环遍历每种动物类型for (String animalType : animalTypes) {// 使用工厂方法创建动物对象通过反射Animal animal AnimalFactory.createAnimal(animalType);// 检查工厂是否成功创建了动物对象if (animal ! null) {System.out.println(Testing animalType :);animal.makeSound(); // 调用动物的发声方法System.out.println(); // 输出一个空行分隔每个测试结果} else {System.out.println(Unknown animal type: animalType);}}}
}
本次修改的步骤改造为
1.复制类名加入列表
所以步骤变为
1.复制类名加入到配置文件到此为止我们已经完全消灭了硬编码也就是不需要修改任何代码只需要修改配置文件即可完成测试任务。
通过上面的修改我们来计算看看节约了多少时间因为不需要再修改任何的代码我们只要把配置文件完成就可以自动化测试了。此时假设整理配置文件1小时测试2小时。总时间是123小时。成功从18天-9天-3小时。我们本次共赢得摸鱼时间为 8*3-321小时。好了原神启动
5.IOC
好吧经过几轮奋战我们已经通过接口、工厂模式、反射甚至配置文件等一系列努力逐步消灭了代码中的硬编码简化了我们的开发过程成功为我们的摸鱼时间争取到了宝贵的小时数。可是这个世界上永远有更强大的魔法而这个魔法就是——控制反转IOC 上面的工厂反射配置文件只能针对特定的类进行假如我们要推广到任意类那就得使用IOC了。 1. 什么是 IOC IOC听起来好像一个神秘的魔法词汇实际上它的含义是把“控制”反转给了外部容器或框架。说得更直白一点就是你的代码再也不用自己创建和管理对象了这就像你不需要自己做饭了外卖小哥会按时送到你只要坐等美味。你只需要专心写业务逻辑其他的交给它
2. 如何使用 IOC 我们要干的就是通过 IOC 容器让我们所有的动物对象都交给容器管理容器负责从我们不关心的角落里提取出这些对象并把它们放到我们需要的地方。是不是听起来很神奇
步骤 1.配置容器告诉它我们有哪些类需要它管理。 2.自动注入让容器通过魔法把需要的对象送到我们手上。 3.提取对象从容器里取对象直接用。
3. 使用 Spring 框架实现 IOC
首先我们告诉 Spring 容器哪些类是要交给它管理的。这里的“告诉”就是通过注解或者 XML 配置文件来完成。
import org.springframework.stereotype.Component;public interface Animal {void makeSound();
}Component // 我告诉SpringDog是我托管的对象
public class Dog implements Animal {Overridepublic void makeSound() {System.out.println(Bark);}
}Component // 我告诉SpringCat也是我托管的对象
public class Cat implements Animal {Overridepublic void makeSound() {System.out.println(Meow);}
}
你看是不是有点意思我们通过 Component 告诉 Spring 哪些类是它需要管理的这样 Spring 就能在幕后默默为我们工作。 3.2 配置 Spring 容器 然后我们告诉 Spring 去哪里找这些类。可以使用注解也可以使用 XML 文件。如果你喜欢用注解那就直接 ComponentScan如果你喜欢老派一点的方式XML 也能给你安排得明明白白。
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;Configuration
ComponentScan(basePackages com.example.animals) // 让Spring去这个包里找类
public class AppConfig {
}
3.3 使用 IOC 获取对象 这时Spring 就会在后台准备好对象并且等着你去提取了。你再也不需要自己手动实例化 Dog 或 Cat直接从 Spring 容器里取出来就行了。
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class TestGame {public static void main(String[] args) {// 创建Spring容器ApplicationContext context new AnnotationConfigApplicationContext(AppConfig.class);// 从容器中获取动物对象Animal dog context.getBean(Dog.class);dog.makeSound(); // 调用方法Animal cat context.getBean(Cat.class);cat.makeSound(); // 调用方法}
}
看是不是很轻松你只需要和容器打个招呼剩下的事情交给它去做。你甚至可以在容器里大呼一声“嘿给我一个 Dog 和 Cat马上来”容器就像个万能的外卖小哥立马送到。
3.4 IOC 的优势 解耦合你不再需要直接实例化 Dog 或 Cat 类它们的创建由容器来完成。你就像个指挥官坐在指挥室里其他所有的事交给你的“魔法师”容器。
灵活性需要换动物了直接告诉容器换一个别的。它会默默地帮你换好换完了你还不用看一眼代码是不是有点神奇
自动化依赖管理容器会根据配置自动把依赖注入给你。你只需要在代码里声明容器会悄无声息地把合适的对象交到你手里免去自己创建对象的麻烦。
高扩展性当你想增加新动物时只需要让它实现 Animal 接口并把它交给容器管理容器就会自动照顾好它。完全不用担心手动修改 TestGame 类了完全不需要你增加一只动物TestGame 基本不需要动弹容器会自己照顾好所有细节。
总结
第四层就是IOC在做的事情 IOC 工厂模式反射配置文件读取
工厂模式提高内聚性创建对象的事情就由专门的类负责反射配置文件就是IOC生产bean的法宝。
所有的JavaBean都被IOC的工厂管理,你需要的时候就和工厂说一声我需要XXX就可以了,这样工厂就会返回给你一个正确的对象.在不需要的时候修改工厂的生产列表(配置文件),工厂就停止生产该JavaBean了。
我们一步一步的降低了代码的耦合性提高了内聚性。
参考
原创经典-为什么Spring中的IOC(控制反转)能够降低耦合性解耦