「设计模式」六大原则之五:依赖倒置原则小结
Posted 小羊子说
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「设计模式」六大原则之五:依赖倒置原则小结相关的知识,希望对你有一定的参考价值。
文章目录
「设计模式」六大原则系列链接:
「设计模式」六大原则之一:单一职责小结
「设计模式」六大原则之二:开闭职责小结
「设计模式」六大原则之三:里氏替换原则小结
「设计模式」六大原则之四:接口隔离原则小结
「设计模式」六大原则之五:依赖倒置原则小结
「设计模式」六大原则之六:最小知识原则小结
六大原则体现很多编程的底层逻辑:高内聚、低耦合、面向对象编程、面向接口编程、面向抽象编程,最终实现可读、可复用、可维护性。
设计模式的六大原则有:
- Single Responsibility Principle:单一职责原则
- Open Closed Principle:开闭原则
- Liskov Substitution Principle:里氏替换原则
- Law of Demeter:迪米特法则(最少知道原则)
- Interface Segregation Principle:接口隔离原则
- Dependence Inversion Principle:依赖倒置原则
把这六个原则的首字母联合起来( L 算做一个)就是 SOLID (solid,稳定的),其代表的含义就是这六个原则结合使用的好处:建立稳定、灵活、健壮的设计。
本文介绍 SOLID 中的第五个原则:依赖倒置原则。
1.依赖倒置原则(DIP)定义
依赖倒置原则的英文翻译是 Dependency Inversion Principle,缩写为 DIP。
中文翻译有时候也叫依赖反转原则。
为了追本溯源,我先给出这条原则最原汁原味的英文描述:
High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn’t depend on details. Details depend on abstractions.
我们将它翻译成中文,大概意思就是:
- 高层模块(high-level modules)不要依赖低层模块(low-level)。
- 高层模块和低层模块应该通过抽象(abstractions)来互相依赖。
- 除此之外,抽象(abstractions)不要依赖具体实现细节(details),具体实现细节(details)依赖抽象(abstractions)。
所谓高层模块和低层模块的划分,简单来说就是,在调用链上,调用者属于高层,被调用者属于低层。
在 Java 语言中,抽象指的是接口或抽象类,两都都是不能直接实例被化的,细节就是实现类,实现接口或继承抽象类而产生的类就是细节。其特点就是可以被直接实例化,也就是可以加上 new 关键字。
高层模块就是调用端,低层模块就是具体实现类。
依赖倒置原则在 Java中的体现就是:
模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系通过接口或抽象产生的。
简单点理解,我们要面向接口编程,或者是面向抽象编程。
4.DIP举例说明
在项目中如何灵活的更换第三方图片加载库,或者同时共存,用户根据手机选择性使用三方库。
第一个版本,只使用一种 Picasso 加载库
private void loadImage(String url, ImageView imageView)
Picasso.get().load(url).resize(50, 50).centerCrop().into(imageView)
第二个版本图片加载库,需要变化, Picasso 换成 Glide, 只需要修改这一个工具类即可
public class PicassoUtils
//从网络加载
public static void loadByUrl(String url, IamgeView imageView)
Picasso.get().load(url).resize(50, 50).centerCrop().into(imageView)
//从文件加载
public static void loadByPath(String path,ImageView imageView)
第三个版本,需求变化,要求两种加载库都需要,根据不同手机适配。(类似的:根据不同的手机选择不同的推送服务,方便提高消息的到达率)
此时就需要应用好面向接口编程思想了。
interface ImageLoader
//从网络加载
void loadByUrl(String url, ImageView imageView);
//从文件加载
void loadByPath(String path, ImageView imageView);
//使用 Picasso
public class PicassoLoader implements ImageLoader
...
//使用 Glide
public class GlideLoader implements ImageLoader
...
//业务层使用,imageLoader是什么就看需求,随便创建
//也可以定义一个全局管理类,里面视需求自定义全局 PicassoLoader 或 GlideLoader
// 调用方法
ImageLoader imageLoader = new PicassoLoader()
//ImageLoader imageLoader = new GlideLoader()
imageLoader.loadByUrl(url, imageView);
可以看到面向接口的好处: 能够减少耦合,代码容易维护,容易拓展,因为接口是抽象的,所以不用纠结于具体的逻辑,想怎么实现都可以;又因为接口是顶层的,所以下层就多,就更容易扩展,就更灵活。
2. 如何理解控制反转(IOC)
控制反转的英文翻译是 Inversion Of Control,缩写为 IOC。
控制反转是一个比较笼统的设计思想,并不是一种具体的实现方法,一般用来指导框架层面的设计。这里所说的“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程通过框架来控制。流程的控制权从程序员“反转”给了框架。
3. 如何理解依赖注入(DI)
依赖注入跟控制反转恰恰相反,它是一种具体的编码技巧。
依赖注入的英文翻译是 Dependency Injection,缩写为 DI。
那到底什么是依赖注入呢?
我们用一句话来概括就是:不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。
在这个例子中,Notification 类负责消息推送,依赖 MessageSender 类实现推送商品促销、验证码等消息给用户。我们分别用依赖注入和非依赖注入两种方式来实现一下。具体的实现代码如下所示:
// 非依赖注入实现方式
public class Notification
private MessageSender messageSender;
public Notification()
this.messageSender = new MessageSender(); //此处有点像hardcode
public void sendMessage(String cellphone, String message)
//...省略校验逻辑等...
this.messageSender.send(cellphone, message);
public class MessageSender
public void send(String cellphone, String message)
//....
// 使用Notification
Notification notification = new Notification();
// 依赖注入的实现方式
public class Notification
private MessageSender messageSender;
// 通过构造函数将messageSender传递进来
public Notification(MessageSender messageSender)
this.messageSender = messageSender;
public void sendMessage(String cellphone, String message)
//...省略校验逻辑等...
this.messageSender.send(cellphone, message);
//使用Notification
MessageSender messageSender = new MessageSender();
Notification notification = new Notification(messageSender);
通过依赖注入的方式来将依赖的类对象传递进来,这样就提高了代码的扩展性,我们可以灵活地替换依赖的类。这一点在我们之前讲“开闭原则”的时候也提到过。
当然,上面代码还有继续优化的空间,我们还可以把 MessageSender 定义成接口,基于接口而非实现编程。改造后的代码如下所示:
public class Notification
private MessageSender messageSender;
public Notification(MessageSender messageSender)
this.messageSender = messageSender;
public void sendMessage(String cellphone, String message)
this.messageSender.send(cellphone, message);
public interface MessageSender
void send(String cellphone, String message);
// 短信发送类
public class SmsSender implements MessageSender
@Override
public void send(String cellphone, String message)
//....
// 站内信发送类
public class InboxSender implements MessageSender
@Override
public void send(String cellphone, String message)
//....
//使用Notification
MessageSender messageSender = new SmsSender();
Notification notification = new Notification(messageSender);
上面例子是依赖注入的典型用法。
4. 小结
“基于接口而非实现编程”与“依赖注入”的联系是二者都是从外部传入依赖对象而不是在内部去new一个出来。
区别是“基于接口而非实现编程”强调的是“接口”,强调依赖的对象是接口,而不是具体的实现类;
而“依赖注入”不强调这个,类或接口都可以,只要是从外部传入不是在内部new出来都可以称为依赖注入。
参考:
《设计模式之美》
《重学 Java 设计模式》
《android 源码设计模式解析与实战》
以上是关于「设计模式」六大原则之五:依赖倒置原则小结的主要内容,如果未能解决你的问题,请参考以下文章