最近在工作之余探究设计模式的运用,看设计模式方面的书籍,在这里做一下学习的记录,一个是防止忘记过快,另一个也是给大家分享下自己学习设计模式的一些收获.
- 这里所谈到的设计模式主要是针对面向对象语言
- 内容有部分来自相关的书籍,比如《Android源码设计模式解析与实战》,《大话设计模式》等
该系列其他文章:
- 安卓设计模式(一)面向对象六大设计原则
- 安卓设计模式(二)单例模式
- 安卓设计模式(三)Builder模式
- 安卓设计模式(四)装饰者模式
- 安卓设计模式(五)代理模式
- 安卓设计模式(六)策略模式
- 安卓设计模式(七)模板方法模式
- 安卓设计模式(八)工厂方法模式
一 单一职责原则 SRP
就一个类而言,应该仅有一个引起它变化的原因.
ok,简单点就是说一个类的功能和职责应该是单一的,是一组相关性很高的函数和数据的封装.
- 要尽量清楚职责的划分,单一职责的划分根据每个人的经验可能都不一样
- 超出自己职责范围的功能提出来交给其他类
- 将一个很复杂的功能封装在一个类中是不好的,正确的是封装在一组类中(以前老大经常跟我说一个类不要超过x百行代码)
比如我们自己封装一个log工具类,包括可以控制全局是否打印log,自动获取类名作为TAG,在log信息后附加方法名丶线程丶机型丶网络环境信息,这样方便调试.
这里的附加信息的获取就应该单独提出来,因为这些信息的获取不属于打印log的职责范围内,并且其他地方也可能需要获取这些信息.
二 开闭原则 OCP
软件中的对象(类,模块,函数等)应该对于扩展是开放的,但是,对于修改是封闭的.
简单说就是,你现在写的代码在面对需求变更时能够这样轻松应对:通过继承来实现新需求,不修改内部代码
- 这里的继承包括继承+实现
- 内部代码只因错误修改,而不能是因为新增的需求
例如我们自己实现ImageLoader,其中肯定包括缓存的实现,根据上一节的单一职责原则,我肯定知道将缓存提出来为ImageCache类,然后在ImageLoder中
ImageCache mImageCache=new ImageCache();//实例化一个缓存,内部通过内存缓存LruCache实现
下个星期产品说每次app重新打开都需要重新下载图片,这样太费流量了,于是我到ImageCache类中将缓存改为LruCache+DiskCache双缓存.
很明显这是违反开闭原则的,优雅的代码应该是这样的
缓存接口:
public interface ImageCache {
public Bitmap get(String url);
public void put(String url, Bitmap bitmap);
}
实现:
public class DoubleCache implements ImageCache {
@Override
public Bitmap get(String url) {
return null;
}
@Override
public void put(String url, Bitmap bitmap) {
}
}
产品说要双缓存:
setImageCache(new DoubleCaChe)
产品说还是换回内存缓存吧(可恶):
setImageCache(new memoryCache)
三 里氏替换原则 LSP
所有引用基类的地方必须能够透明的使用其子类对象.
ok,简单点说,父类能出现的地方,子类就能出现,并且替换为子类也不会产生任何异常
例如上面的ImageLoder,产品说在设置里要能够清除缓存,根据开闭原则我肯定不会去修改我的DoubleCaChe,我可以重新写一个带清除的双缓存DoubleCacheWithClear(实现ImageCache);同样我也可以这样做:
public class DoubleCacheWithClear extends DoubleCache {
public void clear() {
//清除
}
}
然后
setImageCache(new DoubleCaChe)
替换为
setImageCache(new DoubleCacheWithClear)
- 以上子类DoubleCacheWithClear在Imageloder中替换父类DoubleCaChe不会出现任何问题
四 依赖倒置原则 DIP
依赖倒置原则指代了一种特定的解耦形式,使得高层次的模块不依赖于低层次模块的实现细节,依赖模块被颠倒了.
简单说就是样的:
- 高层模块不应该依赖对方的低层模块,双方都应该依赖对方的抽象
- 抽象不依赖细节
- 细节应该依赖抽象
对于"倒置"这个关键字我的理解是这样的,老板只跟老板谈项目,至于怎么去实现,那是你员工的事情,但是员工必须按照老板的安排做.
在java语言中的具体表述就是:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过抽象产生的.
- 这里的抽象包括抽象类和接口
举个栗子,在MVP模式下,这三个层次之间往往是通过接口交流的,Presenter持有View和Model的接口(IView,IModel),Persenter和实现类Viewiml丶Modeliml不会有任何关联(抽象不依赖细节),但是实现类Viewiml丶Modeliml必须实现IView,IModel中的方法(细节依赖抽象)
- 依赖倒置原则可以让你的项目拥有变化的能力!
五 接口隔离原则 ISP##
类间的依赖关系应该建立在最小的接口上.
简单说就是,让客户端依赖的接口尽可能的小
这个原则是比较好理解的,抽象应该尽可能的小,没有必然联系的方法应该分别在不同的抽象中
举个栗子:在RecycleView时,需要我们自己实现onItemClick()的回调,并且有时候我们还需要获取item的长按事件,ok,接口回调即可搞定,于是我很快写了这样的代码:
interface itemCallBack {//回调
void onItemClick(int position);
void onItemLongClick(int position);
}
在View层中:
mMyAdapter = new MyAdapter();
mMyAdapter.setCallBack(new MyAdapter.ItemCallBack() {
@Override
public void onItemClick(int position) {
//do
}
@Override
public void onItemLongClick(int position) {
//do
}
});
以上其实是不符合接口隔离原则的,试想一下,ItimClcik和ItemLongClick是没有必然联系的,我只想监听单击事件,却也必须实现长按事件,这很不科学.
根据接口隔离原则,抽象或者接口应当尽量小,所以好的做法是这样的:
public class MyAdapter {//...继承
private ItemClcikCallBack mClcikCallBack;
private ItemLongClickCallBack mLongClickCallBack;
public void setClcikCallBack(ItemClcikCallBack clcikCallBack) {
mClcikCallBack = clcikCallBack;
}
public void setLongClickCallBack(ItemLongClickCallBack longClickCallBack) {
mLongClickCallBack = longClickCallBack;
}
//...调用mClcikCallBack和mLongClickCallBack
interface ItemClcikCallBack {//单击回调
void onItemClick(int position);
}
interface ItemLongClickCallBack {//长按回调
void onItemLongClick(int position);
}
}`
在View层中:
mMyAdapter = new MyAdapter();
mMyAdapter.setClcikCallBack(new MyAdapter.ItemClcikCallBack() {
@Override
public void onItemClick(int position) {
}
});
mMyAdapter.setLongClickCallBack(new MyAdapter.ItemLongClickCallBack() {
@Override
public void onItemLongClick(int position) {
}
});
- 接口隔离原则能让你的系统拥有更高的灵活性!
六 迪米特原则(最少知识原则) LOD
迪米特原则又称为最少知识原则
一个对象应该对其他对象有最少的了解
在java中应该是这样体现的:一个类应该对自己需要耦合或调用的类知道的最少,类的内部如何实现与调用者或者依赖者没有关系.
个人理解包含两个方面:
- 需要调用的类应该最少
- 对于调用的某个类,这个类内部,调用者应该知道的最少
举栗子:还是前面的ImageLoder,缓存这块是已经搞定了.假如在某次加载图片中,缓存没找到就需要联网去服务器拿图片,并且需要存到缓存中以备下次直接从缓存加载,ok,很快可以写出这样的代码:
public class ImageLoder {
private ImageCache mImageCache = new DoubleCache();
//...
public void dispalyImage(String url, ImageView imageView) {
Bitmap bitmap = mImageCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
HttpImage4Service.down4Service(url, imageView, mImageCache);
}
}
HttpImage4Service下载类中从网络中加载图片方法:
public static void down4Service(String url, ImageView imageView, ImageCache imageCache) {
//...从网络拉取图片
//回调↓
imageView.setImageBitmap(bitmap4Service);//显示图片
imageCache.put(url, bitmap4Service);//存到缓存中
}
分析下这样设计的耦合情况,
- ImageLoder调用ImageCache和HttpImage4Service
- HttpImage4Service调用ImageCache
三个类之间是否知道的最少?试想一下,从网络拉取图片跟缓存这样两个类应该有关联吗?实际上是没必要的,根据最少知识原则,改进之后应该是下面这样的:
public class ImageLoder {
private ImageCache mImageCache = new DoubleCache();
//...
public void dispalyImage(String url, ImageView imageView) {
Bitmap bitmap = mImageCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
bitmap=HttpImage4Service.down4Service(url);//只负责下载图片
imageView.setImageBitmap(bitmap);
imageCache.put(url, bitmap);//存到缓存中
}
}
改进后三个类中只有ImageLoder调用HttpImage4Service和ImageCache中的方法,其余没有任何调用关系,耦合度降低.
在MVP中,View层和Model层拒绝通信,也是符合最少知识原则的,达到降低耦合效果,同时可扩展性会大大增加.
总结
面向对象的六大设计原则,最终可以化为这几个关键字:抽象,单一职责,最小化
这也是大家经常提到的面向接口编程的重点
应用开发,最难的不是完成开发工作,而是维护和升级.为了后续能够很好的维护和升级,我们的系统需要在满足稳定性的前提下保持以下三个特性:
- 高可扩展性
- 高内聚
- 低耦合
以上是面向对象的六大设计原则.第一次写这么长的博客,想想还有些激动呢!
有些模式可能自己理解的不足,还请大家及时指出,大家一起讨论.
不说了,老司机周末要去开车了...
关于作者
- 简 书:uncochen
- github:ChenZhen
- 新浪微博:@Chen丶振
- Email:18620156376@163.com