Android程序员,你应该知道的设计模式
2017-09-14 本文已影响1127人
黄俊彬
前言
图片来自百度搜索设计模式这个东西,听起来很玄乎。有时候看起来似懂非懂。平时编码中可能大部分也没考虑应该用怎样的设计来编码。
如果你是一个外包的人员,干完就撤。有可能确实不需要用到什么设计模式,满足功能则可。如果你是一个有产品迭代的开发人员或者代码维护人员。也许你有过背后吐槽这代码真是乱得可以,干不动了的经验。
在应用的开发过程中,最难的不是完成应用的开发工作,而是在后续的升级、维护中让应用系统能够拥抱变化。拥抱变化也就是意味着在满足需求且不破坏系统稳定性的前提下保持高可扩展性、高内聚、低耦合,在经历了各版本的变更之后依旧保持清晰、灵活、稳定的系统架构。
虽然在实际的应该开发中,要饱受产品的折磨和摧残、需要在极其有限的研发时间里完成功能、上线。为了赶工,这里Copy一份代码,哪里黏贴一个类。如果有多个项目,甚至经常在这个项目挪点功能,那个项目移植个模块,到最后干不动了,苦的还是自己。
排除一切不可抗力因素,我们还是需要保持良好的编码习惯,在动手编码前多思考,尽量让结构更加灵活、低耦合。本文主要是对一些编码的设计原则及设计模式进行整理,便于学习及复习。
面向对象的设计原则
原则 | 定义 | 说明 |
---|---|---|
单一责任原则 | 就一个类而言,应该只有一个引起变化的原因 | 责任界限比较难划分,尽量满足单一责任原则。接口一定要做到单一责任原则,类的设计尽量做到只有一个引起变化的原因 |
里氏替换原则 | 所有引用父类的地方,必要能透明的使用其子类 | 在项目中,采用里氏替换原则,应尽量避免子类的个性(拥有自己的业务实现方法) |
依赖倒置原则 | 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的 | 每个类尽量都有接口或抽象类,或者抽象类和两者都具备,任何类尽量都不应该从具体的类派生 |
依赖倒置原则 | 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的 | 每个类尽量都有接口或抽象类,或者抽象类和两者都具备,任何类尽量都不应该从具体的类派生 |
接口隔离原则 | 客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上 | 接口隔离原则是对接口的定义,同时也是对类的定义,接口和类尽量使用原子接口和原子类来组织 |
迪米特原则 | 一个对象应该对自己需要耦合或调用的类知道最少,尽量不要对外公布太多的public方法和非静态的public变量 | 迪米特法则的核心观念就是类间解耦,提高类的复用率,可能产生大量的中转或者跳转类,导致系统的复杂性提高 |
开放封闭原则 | 软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是,对于修改是封闭的 | 通过接口或者抽象类约束扩展,封装变化,对扩展进行边界开放 |
详细可参考:设计模式-面向对象的六大设计原则
设计模式
代码中常用的设计模式
设计模式 | 速记 | 例子 | 详解 |
---|---|---|---|
单例模式 | 确保某一个类只有一个实例,而且自行示例化并向整个系统提供这个实例。确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源 | 实际开发中,好多数据库Dao操作、或者Service都是直接采用单例的方式,应注意线程安全及内存泄漏问题 | 单例模式 |
建造者模式 | Builder模式是一步一步创建一个复制对象的创建型模式,他允许用户在不知道内部构建细节的情况下,可以更精细地控制对象的构造流程 | new AlertDialog.Builder(self).setTitle("列表框").setItems(new String[] {"列表项1","列表项2","列表项3"}, null).setNegativeButton("确定", null) .show();有时候Activity的启动构造参数复杂,也可使用Builder模式进行控制 | 建造者模式 |
适配器模式 | 适配器模式把一个类在接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法再一起工作的两个类能够在一起工作 | ListView作为重要的控件,它需要能够显示各种各样的视图(ItemView),每个人需要的显示效果各不同。Android的做法是增加一个Adapter层来隔离变化,将ListView需要的关于Item View接口抽象到Adapter对象中,并且在ListView内部调用Adapter这些接口完成布局操作。这样只用用户实现了Adapter的接口,并且将该Adapter设置给ListView,ListView就可以按照用户设定的UI效果、数量、数据来显示每一项数据 | 适配器模式 |
观察者模式 | 观察者模式是一个使用率非常高的模式,它最常用的地方是GUI系统,订阅-发布系统。因为这个模式的一个重要作用就是解耦,将被观察者和观察者解耦,使得它们之间的依赖性更小 | 广播、EventBus、Rxjava,一些上传下载进度回调监听 | 观察者模式 |
外观模式 | 外观模式(Facade)在开发过程中的运用频率非常高,尤其是在现阶段各个第三方SDK充斥在我们的周边,而这些SDK很大概率会使用外观模式,通过一个外观类使得整个系统的接口只有一个统一的高层接口,这样能够降低用户的使用成本,也对用户屏蔽了很多实现细节 | 例如Context统一了很多系统资源的范围,实际开发过程中使用的图片库、网络库等,提供统一的Facade,避免更换影响全局 | 外观模式 |
经常用但却不知道的设计模式
设计模式 | 速记 | 例子 | 详解 |
---|---|---|---|
模板模式 | 定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤 | AsyncTask,在使用AsyncTask时,我们都知道把耗时的方法放在doInBackground(Params… params)中,在doInBackground之前,如果还想做一些类似初始化的操作,可以把实现卸载onPreExecutre方法中,当doInBackground方法执行完成后,会执行onPostExecutre方法 | 模板模式 |
中介者模式 | 该模式将对象之间的多对多关系变成一对多关系 | Android的Activity实际就是一个中介者模式,形形式式的View交互都在Activity中统一执行,View之间彼此不交互(你有可能点一个Button,让一个ImageView变化图片,但这个逻辑不会写在Button中) | 中介者模式 |
责任链模式 | 责任链模式是行为型设计模式之一。使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止 | 在Android源码中比较类似的实现就是对于触摸事件的分发处理。每当用户接触屏幕时,Android都会将对应的事件包装成一个事件对象从ViewTree的顶部至上而下地分发传递。ViewGroup事件投递的递归调用就类似于一条责任链,一旦寻找到责任者,那么将由责任者持有并消费掉该次事件,具体地体现在View的onTouchEvent方法中返回值的设置,如果onTouchEvent返回false,那么意味着当前View不会是该次事件的复制人,将不会对其持有;如果为true则相反,此时View会持有该事件并不再向外传递 | 责任链模式 |
装饰模式 | 装饰模式也称为包装模式,结构型设计模式之一,其使用一种对客户端透明的方式来动态地扩展对象的功能,同时它也是继承关系的一种替代方案之一。动态地给一个对象添加一些额外的职责 | Context类本身是一个纯abstract类,他有两个具体的实现子类:ContextImpl和ContextWrapper。其中ContextWrapper类,只是一个包装而已,ContextWrapper构造函数中必须包含一个真正的Context引用,同时ContextWrapper提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,调用ContextWrapper的方法都会被转向其所包含的真正的Context对象 | 装饰模式 |
组合模式 | 组合模式也称为部分整体模式,结构型设计模式之一,组合模式比较简单,它将一组相似的对象看作一个对象处理,并根据一个树状结构来组合对象,然后提供一个统一的方法去访问相应的对象 | Android源码中关于组合模式有一个非常经典的实现,我们几乎每天都会使用到,那就是View和ViewGroup的嵌套组合 | 组合模式 |
备忘录模式 | 备忘录模式是一种行为模式,该模式用于保存对象当前状态,并且在之后可以再次恢复到此状态。备忘录模式实现的方式需要保证被保存的对象状态不能被对象从外部访问,目的是为了保护好被保存的这些对象状态的完整性以及内部实现不向外暴露 | 在Android开发中,状态模式应用是Android中的状态保持,也就是里面的onSaveInstanceState和onRestoreInstanceState。当Activity不是正常方式退出,且Activity在随后的时间内被系统杀死之前会调用这两个方法让开发人员可以有机会存储Activity的相关信息,并且在下次放好Activity的时候恢复这些数据 | 备忘录模式 |
享元模式 | 享元模式是池技术的重要实现方式,使用共享对象可有效地支持大量的细粒度的对象 | 数据库链接池、长连接池 | 享元模式 |
迭代器模式 | 迭代器模式提供一种方法顺序访问一个容器对象的各个元素,而又不需要暴露该对象的内部表示 | JDK中各种集合的遍历 | 迭代器模式 |
其他设计模式
设计模式 | 速记 | 详解 |
---|---|---|
工厂模式 | 工厂模式是创建型设计模式之一。定义一个用户创建对象的接口,让子类决定实例化那个类。工厂模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以使用 | 工厂模式 |
抽象工厂模式 | 抽象工厂也是创建型设计模式之一。为创建一组相关或者是相互依赖的对象提供一个接口,而不需要指定他们的具体类。一个对象族有相同约束时可以使用抽象工厂模式 | 抽象工厂模式 |
代理模式 | 为其他对象提供一种代理以控制对这个对象的访问,有静态代码和动态代理 | 代理模式 |
原型模式 | 用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象 | 原型模式 |
策略模式 | 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们还可以互相替换。策略模式让算法独立于使用它的客户而独立变化 | 策略模式 |
访问者模式 | 封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作 | 访问者模式 |
状态模式 | 状态模式中的行为是由状态来决定的,不同的状态下又不同的行为。状态模式和策略模式的结构几乎完全一样,但他们的目的,本质却完全不一样。状态模式的行为是平行的、不可替换的,策略模式的行为是彼此独立、互相替换的 | 状态模式 |
解释器模式 | 解释器模式是一种用得比较少的行为模式,其提供了一种解释语言的语法或表达式的方式,该模式定义了一个表达式接口,通过该接口解释一个特定的上下文 | 解释器模式 |
桥梁模式 | 桥梁模式也称为桥接模式,是结构型设计模式之一。将抽象和实现解耦,使得两者可以独立地变化 | 桥梁模式 |
总结
装饰模式、代理模式、外观模式区别
代理模式注重的是隔离限制,关注于控制对对象的访问,让外部不能访问你实际的调用对象,比如权限控制。代理和真实对象之间的的关系通常在编译时就已经确定了。
装饰模式注重的是功能的拓展,关注于在一个对象上动态的添加方法,在同一个方法下实现更多的功能。装饰者能够在运行时递归地被构造。
适配器模式注重的是接口的兼容。
外观模式注重的是多个类的集成、统一适配。
推荐书籍
《设计模式之禅》
《Android源码设计模式解析与实战》