Android经典的设计模式

【单例模式】【Build模式】【原型模式】【工厂模式】【策略模式】【状态模式】【责任链模式】【解释器模式】【命令模式】【观察者模式】【备忘录模式】【迭代器模式】

【模板方法模式】【访问者模式】【中介者模式】【代理模式】【组合模式】【装饰模式】【享元模式】【外观模式】【桥接模式】

【单例模式】

当第一次加载Singleton类时不会初始化sInstance,只有在第一次调用Singleton的getInstance方法时才会导致sInstance被初始化。因此第一次调用getInstance方法会导致

虚拟机加载SingletonHolder类,这种方法不仅能够确保线程安全,也能够保证单例对象的唯一性,同时也延迟的单例的实例化,所以这是推荐使用的单例模式方式

public class Singleton {
private Singleton(){};
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
/**
* 静态内部类
*/
private static class SingletonHolder{
private static final Singleton sInstance = new Singleton();
} }

这个方法虽然好像也很不错,但是好像会出现什么双重检查锁定(DCL)失效。

单例如果持有Context,那么很容易引发内存泄漏,此时需要注意传递给单例对象的Context最好是Application Context。

public class MyImageLoader extends ImageLoader {
private static MyImageLoader instance; public static MyImageLoader getInstance() {
if (instance == null) {
synchronized (MyImageLoader.class) {
if (instance == null) {
instance = new MyImageLoader();
}
}
}
return instance;
} protected MyImageLoader() { }
}

源码模式中的LayoutInflater

【Build模式】

优点:拥有良好的封装性,使用建造者模式可以使客户端不必知道产品内部组成的细节。

   将构建复杂对象的过程和它的部件解耦,使得构建过程和部件的表示隔离开来。 

缺点:会产生多余的Builder对象以及Director对象,消耗内存。

AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle(topTitle)
.setView(settingView)
.setNegativeButton(R.string.ok, new DialogInterface.OnClickListener() { @Override
public void onClick(DialogInterface dialog, int which) {
String servAddr = mServAddrEt.getText().toString().trim();
String roomNo = mRoomNoEt.getText().toString().trim();
String hid = mHidEt.getText().toString().trim();
}
}).setPositiveButton(R.string.cancel, null).create();
dialog.show();

源码模式中dialog

【原型模式】

可以通过克隆来实现原型模式;

只有当通过new构造对象比较耗时或者说成本较高的时候,通过clone方法才能获得效率上的提升;

通过clone拷贝对象时并不会执行构造方法;

//浅拷贝,特点是只复制了一份引用,改变一个对象的集合个数,另一个的集合个数也会改变
public class comA implements Serializable, Cloneable {
... .... public Object clone() {
comA o = null;
try {
o = (comA ) super.clone();
       o.text=this.text;
       o.image=this.image;//一个集合
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
//深拷贝 对于引用型的字段也采用拷贝的形式,而不是单纯的引用,这样改变一个对象的集合的个数,另一个对象的集合的个数不会改变
//整型并不是一个对象,它是值类型,而不是引用类型,因此不需要克隆
public class comA implements Serializable, Cloneable {
... .... public Object clone() {
comA o = null;
try {
o = (comA ) super.clone();
       o.text=this.text;//int 或String的话这一步可以省略
       o.image=(ArrayList<String>)this.image.clone();//一个集合 或对象 这一步省略之后,就是浅引用了
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
 

源码模式中的克隆Clone()

【工厂模式】  (简单工厂模式,工厂方法模式,抽象工厂模式)

一:简单工厂模式:
public static Operation createOperate(string operate)
{
Operation oper = null;
switch (operate)
{
case "+":
{
oper = new OperationAdd();
break;
}
case "-":
{
oper = new OperationSub();
break;
}
case "*":
{
oper = new OperationMul();
break;
}
case "/":
{
oper = new OperationDiv();
break;
}
}
return oper;
}
}
调用工厂,需要createOperator("/"),就能返回除法运算符。
  优点:客户端不需要修改代码。
  缺点: 当需要增加新的运算类的时候,不仅需新加运算类,还要修改工厂类,违反了开闭原则。 前文提到的简单工厂模式跟工厂方法模式极为相似,区别是:简单工厂只有三个要素,他没有工厂接口,并且得到产品的方法一般是静态的。
因为没有工厂接口,所以在工厂实现的扩展性方面稍弱,可以算所工厂方法模式的简化版。

二:工厂方法模式:   解耦最好

//这种方法没有下面那个反射实现工厂的方便
interface Product { //也可以通过abstract来完成 //只有一个产品接口
public void productMethod();
} class ProductA implements Product { //产品需要多个
public void productMethod() {
System.out.println("产品");
}
} interface Factory {       //只有一个工厂借口
public IProduct createProduct();
} class FactoryA implements Factory { //工厂也需要多个
public IProduct createProduct() {
return new ProductA();
}
} public class Client {
public static void main(String[] args) {
Factory factory = new FactoryA();
Product prodect = factory.createProduct();
prodect.productMethod();
}
}

这个和简单工厂有区别,简单工厂模式只有一个工厂,工厂方法模式对每一个产品都有相应的工厂

  好处:增加一个运算类(例如N次方类),只需要增加运算类和相对应的工厂,两个类,不需要修改工厂类。

  缺点:增加运算类,会修改客户端代码,工厂方法只是把简单工厂的内部逻辑判断移到了客户端进行。

//我比较喜欢用这种,扩展方便
public abstract class AudiFactory{
public abstract <T extends AudiCar>T createAudiCar(Class<T> clz);
} public class AudiCarFactory extends AudiFactory{ //工厂类用反射的方式来实现,只需要一个就可以满足多个产品的实现 @Override
public <T extends AudiCar> T createAudiCar(Class<T> clz) {
AudiCar car=null;
try {
car=(AudiCar)Class.forName(clz.getName()).newInstance();
} catch (Exception e) {
// TODO: handle exception
}
return (T)car;
}
}
public abstract class AudiCar{
public abstract void drive();
}
public class AudiQ3 extends AudiCar{ @Override
public void drive() {
System.out.print("Q3 启动了");
}
}
public class AudiQ5 extends AudiCar{ @Override
public void drive() {
System.out.print("Q5 启动了");
}
}
public class AudiQ10 extends AudiCar{ @Override
public void drive() {
System.out.print("Q10 启动了");
}
}

public static void main(String[] args) {
  AudiFactory factory =new AudiCarFactory();
  AudiQ3 audiQ3=factory.createAudiCar(AudiQ3.class);
  audiQ3.drive();
  AudiQ5 audiQ5=factory.createAudiCar(AudiQ5.class);
  audiQ5.drive();
  AudiQ10 audiQ10=factory.createAudiCar(AudiQ10.class);
  audiQ10.drive();
}

 

工厂方法模式有四个要素:

  • 工厂接口。工厂接口是工厂方法模式的核心,与调用者直接交互用来提*品。在实际编程中,有时候也会使用一个抽象类来作为与调用者交互的接口,其本质上是一样的。
  • 工厂实现。在编程中,工厂实现决定如何实例化产品,是实现扩展的途径,需要有多少种产品,就需要有多少个具体的工厂实现。
  • 产品接口。产品接口的主要目的是定义产品的规范,所有的产品实现都必须遵循产品接口定义的规范。产品接口是调用者最为关心的,产品接口定义的优劣直接决定了调用者代码的稳定性。同样,产品接口也可以用抽象类来代替,但要注意最好不要违反里氏替换原则。
  • 产品实现。实现产品接口的具体类,决定了产品在客户端中的具体行为。

三:抽象工厂模式:  用的比较少,对复杂的情况感觉很有用

 

interface ITire {    //轮胎  比较特殊,有多个产品接口
public void show();
}
class NormalTire implements ITire {
public void show() {
System.out.println("普通轮胎");
}
}
class SUVTire implements ITire {
public void show() {
System.out.println("越野轮胎");
}
}
interface IEngine { //发动机 比较特殊,有多个产品接口
public void show();
} class DomesticEngine implements IEngine {
public void show() {
System.out.println("国产发动机");
}
}
class ImportEngine implements IEngine {
public void show() {
System.out.println("进口发动机");
}
} public abstract class CarFactory {
public abstract ITire createTire(); //生产轮胎
public abstract IEngine createEngine(); //生产发动机
}
class Q3Factory implements CarFactory{
public ITire createTire() {
return new NormalTire();
}
public IEngine createEngine() {
return new DomesticEngine();
}
}
class Q7Factory implements CarFactory{
public ITire createTire() {
return new SUVTire();
}
public IEngine createEngine() {
return new ImportEngine();
}
} public class Client {
public static void main(String[] args){
CarFactory factoryQ3 = new Q3Factory();
factoryQ3.createTire().show();
factoryQ3.createEngine().show(); CarFactory factoryQ7 = new Q7Factory();
factoryQ7.createTire().show();
factoryQ7.createEngine().show();
}
} 普通轮胎
国产发动机 越野轮胎
进口发动机

抽象工厂模式的优点

抽象工厂模式除了具有工厂方法模式的优点外,最主要的优点就是可以在类的内部对产品族进行约束。所谓的产品族,一般或多或少的都存在一定的关联,抽象工厂模式就可以在类内部对产品族的关联关系进行定义和描述,而不必专门引入一个新的类来进行管理。

抽象工厂模式的缺点

产品族的扩展将是一件十分费力的事情,假如产品族中需要增加一个新的产品,则几乎所有的工厂类都需要进行修改。所以使用抽象工厂模式时,对产品等级结构的划分是非常重要的。

适用场景

当需要创建的对象是一系列相互关联或相互依赖的产品族时,便可以使用抽象工厂模式。说的更明白一点,就是一个继承体系中,如果存在着多个等级结构(即存在着多个抽象类),并且分属各个等级结构中的实现类之间存在着一定的关联或者约束,就可以使用抽象工厂模式。假如各个等级结构中的实现类之间不存在关联或约束,则使用多个独立的工厂来对产品进行创建,则更合适一点。

总结

无论是简单工厂模式,工厂方法模式,还是抽象工厂模式,他们都属于工厂模式,在形式和特点上也是极为相似的,他们的最终目的都是为了解耦。在使用时,我们不必去在意这个模式到底工厂方法模式还是抽象工厂模式,因为他们之间的演变常常是令人琢磨不透的。经常你会发现,明明使用的工厂方法模式,当新需求来临,稍加修改,加入了一个新方法后,由于类中的产品构成了不同等级结构中的产品族,它就变成抽象工厂模式了;而对于抽象工厂模式,当减少一个方法使的提供的产品不再构成产品族之后,它就演变成了工厂方法模式。

所以,在使用工厂模式时,只需要关心降低耦合度的目的是否达到了。

源码模式中onCreat()方法也是工厂模式

【策略模式】

  抽象折扣类
public interface MemberStrategy {
/**
* 计算图书的价格
* @param booksPrice 图书的原价
* @return 计算出打折后的价格
*/
public double calcPrice(double booksPrice);
}   初级会员折扣类
public class PrimaryMemberStrategy implements MemberStrategy { @Override
public double calcPrice(double booksPrice) { System.out.println("对于初级会员的没有折扣");
return booksPrice;
} }   中级会员折扣类
public class IntermediateMemberStrategy implements MemberStrategy { @Override
public double calcPrice(double booksPrice) { System.out.println("对于中级会员的折扣为10%");
return booksPrice * 0.9;
} }   高级会员折扣类
public class AdvancedMemberStrategy implements MemberStrategy { @Override
public double calcPrice(double booksPrice) { System.out.println("对于高级会员的折扣为20%");
return booksPrice * 0.8;
}
}
  价格类
public class Price {
//持有一个具体的策略对象
private MemberStrategy strategy;
/**
* 构造函数,传入一个具体的策略对象
* @param strategy 具体的策略对象
*/
public Price(MemberStrategy strategy){
this.strategy = strategy;
} /**
* 计算图书的价格
* @param booksPrice 图书的原价
* @return 计算出打折后的价格
*/
public double quote(double booksPrice){
return this.strategy.calcPrice(booksPrice);
}
}  客户端
public class Client { public static void main(String[] args) {
//选择并创建需要使用的策略对象
MemberStrategy strategy = new AdvancedMemberStrategy();
//创建环境
Price price = new Price(strategy);
//计算价格
double quote = price.quote();
System.out.println("图书的最终价格为:" + quote);
} }

策略模式的优点:
  (1)策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。  

  (2)使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,

    统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。

策略模式的缺点:
  (1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。
  (2)由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。

源码模式中动画中的时间插值器TimeInterpolator

【状态模式】

房间有三个状态:空闲、已预订、已入住,

源码实现中对wifi状态的管理

【责任链模式】

使用场景

  来考虑这样一个功能:申请聚餐费用的管理。

  很多公司都是这样的福利,就是项目组或者是部门可以向公司申请一些聚餐费用,用于组织项目组成员或者是部门成员进行聚餐活动。

  申请聚餐费用的大致流程一般是:由申请人先填写申请单,然后交给领导审批,如果申请批准下来,领导会通知申请人审批通过,然后申请人去财务领取费用,如果没有批准下来,领导会通知申请人审批未通过,此事也就此作罢。

  不同级别的领导,对于审批的额度是不一样的,比如,项目经理只能审批500元以内的申请;部门经理能审批1000元以内的申请;而总经理可以审核任意额度的申请。

  也就是说,当某人提出聚餐费用申请的请求后,该请求会经由项目经理、部门经理、总经理之中的某一位领导来进行相应的处理,但是提出申请的人并不知道最终会由谁来处理他的请求,一般申请人是把自己的申请提交给项目经理,或许最后是由总经理来处理他的请求。

源码实现有dispatchTouchEvent等对事件的传递

【解释器模式】

可用来做加减乘除

可利用场景比较少

源码实现有PMS对AndroidManifest.xml的解析

【命令模式】

例子,是模拟对电视机的操作有开机、关机、换台命令

源码实现有Android事件在底层逻辑转换成的NotifyArgs

【观察者模式】

实例描述:客户支付了订单款项,这时财务需要开具发票,出纳需要记账,配送员需要配货。

EventBus使用进阶比较好一点的Activity+Fragment及它们之间的数据交换

源码实现有listview的adapter也是属于观察者模式

【备忘录模式】

备忘录模式所涉及的角色有三个:备忘录(Memento)角色、发起人(Originator)角色、负责人(Caretaker)角色

源码实现有activity中的onSaveInstanceState()方法

【迭代器模式】

提供一种方法访问一个容器对象中各个元素,而又不暴露该对象的内部细节。

源码实现有数据库查询使用的Cursor

【模板方法模式】

其特点是封装流程

源码模式中AsyncTask和Activity的生命周期使用的都是模板方法模式

【访问者模式】

假如一个对象中存在着一些与本对象不相干(或者关系较弱)的操作,为了避免这些操作污染这个对象,则可以使用访问者模式来把这些操作封装到访问者中去。

可以用来做报表,比如CEO和CTO检查工程师和产品经理的年终报表,看的是不同的方面。

可以用来在方法或一些View上添加注解@*******

【中介者模式】

适当地使用中介者模式可以避免同事类之间的过度耦合,使得各同事类之间可以相对独立地使用。

源码实现比较好的例子是Keyguard锁屏功能的实现

【代理模式】

代理模式与动态代理模式详解

代理就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用

源码模式中的ActivityManagerProxy代理类,代理的是ActivityManagerNative的子类ActivityManagerService

【组合模式】

母公司,母公司的各部门,子公司,子公司的各部门的组合例子

操作系统的文件系统其实就是一种典型的组合模式

ViewGroup和view也是嵌套组合

【装饰模式】

例子:比如一个男孩的服装,可以对他进行便宜的衣服的装饰或贵的衣服的装饰

装饰模式应该为所装饰的对象增强功能;代理模式对代理的对象施加控制,但不对对象本身进行增强。

在源码中的表现是ContextWrapper是装饰者,ContextImpl则是具体实现类;其实我们平时在Activity的onCreate的一些视图、数据的处理也是类似于装饰模式

装饰模式避免了类爆炸,一层一层地嵌套,

【享元模式】

享元模式是一种对象池的实现,其应用场景有如果有几十万人一起去抢火车票,如果不用享元模式,则要新建几十万个对象,其结果就是导致频繁GC,导致系统卡顿。

一个例子

在源码中的实现,比较接近的是Handler消息机制。

【外观模式】

外观模式就是统一的接口封装,将子系统的逻辑,交互隐藏起来,为用户提供一个高层次的接口。这样即使子系统发生了变化,用户也不会感知,因为用户使用的是高层次的接口

一个简单的小例子 SDK很大概率会使用外观模式。

在源码中Context对开发者来说就是一个高层次的接口

【桥接模式】

比如喝咖啡,有大杯加糖,大杯不加糖,小杯加糖,小杯不加糖,实际上可以分为两种变化,一是大杯小杯,二是加糖不加糖。

一个小例子

源码中的Window与WindowManager属于桥接

Window为抽象接口,PhoneWindow为抽象部分的具体实现,WindowManager则是实现部分的基类,WindowManagerImpl为实现部分的具体逻辑实现,其使用的WindowManagerGlobal通过IWindowManager接口与WindowManagerService进行交互,并有WMS完成具体的窗口管理工作

上一篇:蓝桥杯—ALGO-12 幂方分解(递归递推)


下一篇:我们为什么以及是如何从 Angular.js 迁移到 Vue.js?