说说我理解的ioc
摘要:本文介绍了自己对于IoC的概念理解,同时对于目前Spring中IoC的实现:依赖注入(DI)方式做了一定的讨论。并且使用了实际案例来阐述使用IoC所带来的好处。
🏃在介绍之前的约定(假设)🏃
- 我们在这里引入IoC的前提是,要设计的系统足够复杂,虽然引入的例子看起来比较简单,那只是抽象之后,实际上各模块的实现无论在依赖还是设计实现都非常复杂。
- 我们说某个模块A依赖某个模块B,无特殊说明,模块A都无修改模块B代码的权限,这里的依赖分为两种情况,面向继承的白盒复用和面向黑盒的组合复用,在IoC的讨论语境中,我们一般指组合复用。
- 由于整个待设计系统足够复杂,所以我们采用了模块化组织,并且每个模块之间的设计团队仅仅了解和自己模块的相关的接口约定,且在基于碳基结构的条件下,人的精力和脑力总是有限的,对于庞大的系统,考虑效率问题,不可能全部清楚所有细节。
😄什么是IoC😄
IoC是一种面向对象中解耦模块之间依赖的一种策略,在Java实现IoC的手段中,大部分采用的依赖注入的这种手段。所谓的解耦,实际上就是尽量降低各相邻模块的强关联性,来提高各模块的复用性和灵活性。单纯这样讲有些抽象,看下面的伪代码:
//Module A impl by depent Module B service,
//then we just new(create) instance B
public class A{
public void aImportMethod(Args[] args){
//some logic code
B b = new B();
b.method();
//some logic code;
}
}
上面的代码在未使用IoC之前可以说再平常不过了,面向组合式复用的编程哲学告诉我们,需要什么样的服务,创建什么样的类,假如我们的需求一成不变,那这样的编程方式丝毫没有问题,但是,要注意的是,一开始我们引入的几条假设中,第一条便是我们的系统足够复杂,并且由于A模块总的方法是一个重要的方法,势必会被很多模块引用,而我们系统中需求又比较复杂多变,比如说这里模块A是商城系统,而模块B是支付结算系统,商城肯定需要支持多种支付方式,微信扫码、支付宝扫码、银行卡的个各种支付手段,显然如果还是仅仅去创建单一的支付对象实现,那么是无法满足上述要求的,提高灵活性的首要手段就是抽象,要解决上述问题,面向接口编程加策略模式了解一下,伪代码如下:
//in module Pay
public interface PayService{
public void pay();
}
public clas WxPayService implements PayService;
public clas AliPayService implements PaySerive;
//in module Mall
public Class Mall{
public PayRespone pay(Order order,PAY_TYEP payType){
PayService payService = null;
if(payType == PAY_TYPE.WX) {
payService = new WxPayService();
}
else if(payType == PAY_TYPE.ALI) {
payService = new AliPayService();
}
PayResponse payResponse payService.pay();
//some logic code,like to handle exception
//or execute persitance logic
return payResponse;
}
}
通过接口抽象加策略模式,看似解决了上述不灵活的弊端,但是,假如系统足够复杂,Pay系统中服务需要添加一下配置才能使用,这种策略方式便失效了,可以发现,造成上述根本的原因是我们无法控制服务Pay的生成及配置,对于Mall系统的使用者而言(注意不是Mall系统的实现者)或者说,我们当需要某个模块的某项服务时,我们必须自己去生成对象。
💻依赖注入💻
经过上述讨论,我们可以得出结论,如果在我们发布的系统中对于关键方法,需要手动新建对象这样的操作,势必会造成系统的耦合度增高,随着整个系统的复杂度上升,代码会变得越来越难维护,因为,我们采用将接口开发的方式,来允许使用该模块的人能够进行定制化操作。转化成伪代码如下:
//in constructor way;
public class A{
private B b;
public A(B b){
this.b = b;
}
public void aMethodUseB(){
}
}
public class Main{
public static void main(String[] args){
B b = new BImpl();
A a = new A(b);
}
}
通过上述例子,我们也能看出什么叫做控制翻转,本来A服务需要B对象完成某项任务,不是A自己去生成对象,而是使用A对象的模块为其进行依赖的注入,通过这样的方式实现了控制翻转(对B的控制),而spring ioc是通过统一的第三方对象容器来进行所有对象的托管。连在Main对象新建对象的步骤都省略了,对象的自动生成只是表象,真正的目的是进行这种依赖的注入,之所以选择对象的集中托管,是因为软件设计是要遵循模块化,统一管理便于系统的可维护性。
下面再简单用伪代码讲下通过setter方式来实现依赖注入的方式:
public class A{
private B b;
public void aMethodUseB(){
}
public void setB(B b){
this.b = b;
}
}
public class Main{
public static void main(String[] args){
B b = new BImpl();
A a = new A();
a.set(b);
}
}
当然完整的例子请参照如下(此伪代码摘自引用2,强烈推荐阅读这篇博客)
class MovieLister...
private MovieFinder finder;
public void setFinder(MovieFinder finder) {
this.finder = finder;
}
Similarly I define a setter for the filename.
class ColonMovieFinder...
public void setFilename(String filename) {
this.filename = filename;
}
<beans>
<bean id="MovieLister" class="spring.MovieLister">
<property name="finder">
<ref local="MovieFinder"/>
</property>
</bean>
<bean id="MovieFinder" class="spring.ColonMovieFinder">
<property name="filename">
<value>movies1.txt</value>
</property>
</bean>
</beans>
public void testWithSpring() throws Exception {
ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml");
MovieLister lister = (MovieLister) ctx.getBean("MovieLister");
Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
assertEquals("Once Upon a Time in the West", movies[0].getTitle());
}
上述便是spring通过setter这样的方式,结合配置文件和反射来实现IoC的基本用法。
📝所以IoC的好处都有啥📝
先来看张图吧,放这张图并没有攻击其他人的意思,仅仅想表达,当一项技术当前你并未那他解决你现有的痛点时,也就是你对他的需求并不是十分强烈,但是其他人又在神乎其神的宣扬他的重要性,我们是会对技术产生抵触心理的。
简单结合wiki和个人感受来总结一下吧:
- 降低了模块间的耦合性,尽最大可能的提高代码的复用性
- 实现了资源的集中管理,降低系统的复杂度(当然ioc的实现复杂度这里我们不考虑),同时将一些配置性质的代码完全从业务逻辑剥离出来,集中到统一的配置文件管理
- 实现和接口完全分离,代码程度抽象程度进一步提高,为以后的迭代和需求变动提供空间