六大原则
- 单一职责
- 开闭原则
- 里氏替换原则(只要父类能出现的地方子类就能出现,而且替换为子类不会出现错误或异常。使用者不知道用的是父类还是子类,总结两个字:抽象)
- 依赖倒置原则
模块之间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,依赖是通过接口或者抽象类发生的,总结:面向接口(抽象)编程。(依赖接口不要依赖具体实现) - 接口隔离原则
让客户端依赖的接口尽可能小,类间依赖关系建立在最小接口上。例如:关闭FileOutputStream,只需要知道对象可关闭即可,其他的一律不关心。
以上为SOLID(面向对象编程的五个基本原则)
- 迪米特原则
关键字:解耦。一个类应该对自己需要耦合或者调用的类知道的越少越好,无需知道被调用者如何实现的,只需知道如何调用即可。
一、单例模式
- 使用场景
- 创建一个对象消耗过多的资源(访问IO和数据库等)
- 某种类型的对象只应该有且只有一个
- 实现方式
- 懒汉式(需要的时候再加载)
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
//优点:使用时加载,节省资源。
//缺点:每次都同步,造成不必要的同步开销
2.Double Check Lock(DCL)
public class Singleton {
private Singleton(){}
private static volatile Singleton instance = null;
public static Singleton getInstance(){
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
//两次null判断的意义:
//第一层为了避免不必要的同步
//第二层是为了在null的情况下创建实例
// new Singleton()后执行的三个步骤:
// 1.给Singleton()分配内存
// 2.调用Singleton()的构造函数
// 3.将instance对象指向分配的内存空间(此时instance已经不是空了)
// DCL失效问题
// 由于:java编译器允许处理器重排序
// 所以:如果执行顺序是132,13执行以后,另一个线程获取到instance不是空,使用时会出错(由于未执行2)
// volatile意义:
// 1.每次都从主内存中读取数据.
// 2.插入特定的内存屏障指令,通过内存屏障来禁止特定类型的处理器指令重排序
//
// 所以:volatile修饰的变量阻止指令重排序,其他线程读取到非空的时候,此时instance已经初始化完成。
- 静态内部类
public class Singleton {
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
//优点:延迟加载:只有调用getInstance时才会new Singleton
//线程安全,单例唯一
- 源码实现
- Context.getSystemService(String name)——— WindowManagerService ActivityManagerService
- LayoutInflator.from(Context context) —— LayoutInflator服务获取
- 总结:
- 优点:
- 一个实例,避免对象的创建销毁,减小内存开支
- 减小性能开销
- 避免对资源的多重占用
- 缺点
- 没接口,扩展困难。
- 持有Context容易发生内存泄露,所以传给ApplicationContext而不是Activity。
- 优点:
二、策略模式
- 使用场景
- 实现一个功能可以有多种算法或者策略,我们根据实际情况选择不同的算法或者策略来完成该功能。(如果所有算法都写到一个类中,增加新的算法违背了单一职责和开闭原则)
- 出现同一抽象类有多个子类,而又需要使用if-else或switch选择具体子类。
- 源码实现
-
时间插值器(线性 加速 减速)
-
时间插值器(线性 加速 减速)
- 优缺点:
- 耦合度较低,扩展方便
- 封装彻底
- 缺点:子类繁多
三、代理模式
- 场景
- 当无法或不想直接或访问某个对象存在困难,可以通过一个代理对象来间接访问。(代理上网<无法>、同事帮忙买饭<不想>、请律师打官司<困难>)
- 三个角色:客户端,代理方(vpn、同事、律师),真实方(我)。代理方和真实方要实现相同的接口(上网,买饭,打官司)。整体逻辑:我委托vpn上网,我委托同事买饭,我委托律师打官司。
- 定义:为其他对象提供一种代理,以控制对这个对象的访问。
-
UML
- 分类
- 静态代理
- 动态代理
- 区分
- Code层面:动态代理—代理类和被代理类(真实方)解耦,通过java—InvovcationHandler,不必实现相同接口;静态代理—只能给指定接口下的实现类代理。
- 代理对象的确认:静态代理-编译期,动态代理-运行期。
- 使用范围:(两者均适用)
- 远程代理:为某个对象在不同的内存地址空间提供局部代理,使系统可以将server部分的实现隐藏,以便client可以不必考虑Server的存在。(跨进程)
- 虚拟代理:使用一个代理对象表示一个十分耗资源的对象并在真正需要时才创建。
- 保护代理:原始对象有不同的访问权限,使用代理控制对原始对象的访问。
- 智能引用:对指向原始对象的引用计数,在访问原始对象的时候执行一些自己的附加操作。
-
源码实现
- ActivitivityManagerProxy为代理对象,ActivityManagerService为真实对象,两者处于不同的进程,所以该代理模式的实质为远程代理。
- 优点:
- 符合单一职责,调用者无需关心具体内部实现细节。
- 符合开闭原则,增加代理对象不用修改,只需拓展。
- 缺点:类增加
四、装饰者模式
- 定义
动态扩展对象的功能,继承关系的一种替代方案,但更灵活。 - 场景
需要动态扩展类的功能。(人穿衣服,男孩穿牛仔裤,女孩穿裙子) -
UML
-
源码实现
- ContextWrapper是装饰者,ContextImpl是被装饰者,ContextWrapper扩展Context功能。
- 优点:
- 组合好于继承关系。装饰者和被装饰者可以动态的组合。
- 与代理区别:
- 装饰者模式—为所装饰的对象增加功能,继承的替代方案。
- 代理模式—对代理的对象施加控制,不对本身功能进行增强。
- 代理是给一个对象提供一个代理对象,并由代理对象控制对原对象的访问。
- 装饰是给一个对象提供一个装饰对象,并由装饰对象增强原对象的功能。
五、适配器模式
- 定义
两个接口完全不兼容,通过适配器,让两个类可以一起工作。 - 场景
电源适配器电压转化 220v-5v -
UML
- 优点:复用和扩展。
- 缺点:系统可能会凌乱。
六、工厂方法
- 场景
- 复杂对象适合用工厂,用new就可以完成的不需要工厂
- 一系列产品有一个共同的父类,比如Q3、Q7都是奥迪,生产Q3还是Q7由子类决定,适合使用工厂。
-
UML
- 角色:抽象工厂,具体工厂,抽象产品,具体产品
- 源码
- Activity_onCreate_setContentView, view是产品,activity是工厂,通过不同的activity去设置不同的contentView返回给frameWork处理
- 实战
- 抽象产品(读写工具IOHandler)、具体产品(文件读写FileHandler,数据库读写SqlHander,xml读写XmlHandler)、具体工厂(文件读写厂IOFactory)
七、抽象工厂
- 场景
- windows 、Android 、iOS 下的短信和拨号软件,都不一样。一个对象族有相同的约束时,可以使用抽象工厂。短信和拨号都是软件,但从属于不同的系统。轮胎和发动机都属于汽车配件,但从属于不同的汽车(Q3Q7)。
-
UML
- 角色:抽象产品A、具体产品A1、具体产品A2、、抽象产品B、具体产品B1、具体产品B2、抽象工厂、具体工厂1、具体工厂2。
- 例子
- 工厂方法:
- 抽象工厂(汽车工厂),具体工厂(奥迪工厂)、抽象产品(奥迪汽车)、具体产品1(奥迪q3)、具体产品2(奥迪q7)
- 抽象工厂:
- 抽象工厂(汽车工厂),具体工厂1(奥迪q3工厂),具体工厂2(奥迪q7工厂),抽象产品A(轮胎),具体产品A1(普通轮胎),具体产品A2(越野轮胎),抽象产品B(发动机),具体产品B1(国产发动机),具体产品B2(进口发动机)。
- Q3工厂——Q3汽车——普通轮胎、国产发动机。
- Q7工厂——Q7汽车——越野轮胎、进口发动机。
- 工厂方法:
- 总结:
- 与工厂方法区别:
- 抽象工厂:多个工厂(对应的多个产品)、一个工厂生产多件产品。
- 工厂方法:一个工厂(对应的一个产品)、一个工厂生产一件产品。
- 优点:面向接口编程,不需要知道他如何实现的。
- 缺点:文件增多,不太容易扩展新的产品类。
- 与工厂方法区别:
- 源码
- 从framework的角度来说,具体工厂1(activity),具体工厂2(service),抽象产品A(View),抽象产品B(Binder service-onbind-binder)。
八、责任链
- 场景
- 多个对象处理同一个请求,具体哪个执行,运行时动态决定。
- 请求处理者不明确的情况下向多个对象中的一个提交一个请求。
- 例子
- xiaoming向领导报销个饭费,领导们各有一个报销额度,小于报销额度可以审批,否则向上级汇报审批。组长-100,总监-1000,VP-10000,CEO-100000。
-
UML
- 源码
- viewGroup中的事件分发机制,dispatchTouchEvent
- 优点
- 请求者和处理者关系解耦
- 缺点
- 遍历过多,影响性能。
九、观察者模式
- 定义
- 对象间一对多的关系,当一个对象状态改变,所有依赖于它的对象都会得到通知并被自动更新。
-
UML
- 源码
- BaseAdapter是一个观察者模式,ListView设置adapter会构建一个adapterDateSetObserver,这是一个观察者,当调用adapter.notifyDataSetChange()方法时,会遍历所有观察者的onChanged函数,然后调用ListView的requestLayout()方法重新进行布局,更新界面。
- 优点:
- 观察者与被观察者完全解耦。
- 灵活性和可扩展性
- 缺点
- 一个被观察者多个观察者,同步顺序执行,如果一个观察者卡顿,影响效率,这种情况,建议异步。
十、Builder模式
- 场景
- 初始化一个对象特别复杂,参数多,可以采用此模式。
-
UML
- 源码:
- AlertDialog.Builder,使用该builder构建复杂的AlertDialog对象。
- 好处:
- 复杂对象的构建与表示分离,同样的构建过程可以创建不同的表示,易扩展。
- 良好的封装性,创建者不必知道构造的细节。
- 坏处:
- 产生多于的builder,消耗内存
十一、原型模式
- 定义
- 用原型指定创建对象的种类,通过克隆这些原型创建新的对象。
- 一个对象需要提供给其他对象访问,而且各个调用者可能都要修改其值,可以考虑使用原型拷贝多个对象供调用者使用,即保护性拷贝。(例如:某个对象对外是只读的,为了防止对这个对象进行修改,通常可以返回一个对象拷贝的形式实现只读的限制)
- 使用
- 实现cloneable接口,重写clone方法。
- 源码
- Intent重写了clone方法,没有调用super.clone(),而是new clone()。
- 深拷贝&浅拷贝
- 深拷贝是在拷贝对象时,对于引用型的字段也要采用拷贝。
- 优点:
- 内存二进制流的拷贝,要比new性能好很多。
- 缺点:
- 直接在内存中拷贝,不执行构造函数