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

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

  • 这里所谈到的设计模式主要是针对面向对象语言
  • 内容有部分来自相关的书籍,比如《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层拒绝通信,也是符合最少知识原则的,达到降低耦合效果,同时可扩展性会大大增加.

总结

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

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

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

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

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

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

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

你可能感兴趣的:(Android,android,面向对象,设计模式)