安卓设计模式(一)面向对象六大设计原则

最近在工作之余探究设计模式的运用,看设计模式方面的书籍,在这里做一下学习的记录,一个是防止忘记过快,另一个也是给大家分享下自己学习设计模式的一些收获.

  • 这里所谈到的设计模式主要是针对面向对象语言
  • 内容有部分来自相关的书籍,比如《Android源码设计模式解析与实战》,《大话设计模式》等

该系列其他文章:

一 单一职责原则 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中应该是这样体现的:一个类应该对自己需要耦合或调用的类知道的最少,类的内部如何实现与调用者或者依赖者没有关系.

个人理解包含两个方面:

  1. 需要调用的类应该最少
  2. 对于调用的某个类,这个类内部,调用者应该知道的最少

举栗子:还是前面的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层拒绝通信,也是符合最少知识原则的,达到降低耦合效果,同时可扩展性会大大增加.

总结

面向对象的六大设计原则,最终可以化为这几个关键字:抽象,单一职责,最小化

这也是大家经常提到的面向接口编程的重点

应用开发,最难的不是完成开发工作,而是维护和升级.为了后续能够很好的维护和升级,我们的系统需要在满足稳定性的前提下保持以下三个特性:

  • 高可扩展性
  • 高内聚
  • 低耦合

以上是面向对象的六大设计原则.第一次写这么长的博客,想想还有些激动呢!

有些模式可能自己理解的不足,还请大家及时指出,大家一起讨论.

不说了,老司机周末要去开车了...

关于作者

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容