【设计模式】Android中的设计模式

来源:互联网 发布:windows 3.1 下载 编辑:程序博客网 时间:2024/06/02 06:11

写在前面

  • 1、本文所有源码在这里
  • 2、相关的模式定义和部分应用例子参考修改了《Android源码设计模式解析与实战》,建议直接阅读原书,在此致谢!
  • 3、每个模式大致由三部分组成:定义代码实例相关应用,中间会穿插说明和解释。
  • 4、为了便于理解,代码实例尽可能的使用用了简单例子,只适合初步理解每种模式,代码有些写得比较粗糙,请斟酌参考。
  • 5、源码分为23个文件夹,对应23种模式,在MainActivity中进行实际调用,结果输出在logcat,可用于参考。

概述

设计模式分为三大类:

1、创建型模式,5种:单例模式、建造者模式、原型模式、工厂方法模式、抽象工厂模式
2、结构型模式,7种:适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模式、享元模式
3、行为型模式,11种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式

接下来会依次对这23种设计模式作介绍。

1、单例模式

确保某一个类中只有一个实例,而且自行实例化并向整个系统提供这个实例。

避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个,单例有不少实现方式,下面给出一种。

1.1 简单示例

public class SingleTon {    private static volatile SingleTon sInstance;    private SingleTon() {    }    public static SingleTon getInstance() {        if (sInstance == null) {            synchronized (SingleTon.class) {                if (sInstance == null) {                    sInstance = new SingleTon();                }            }        }        return sInstance;    }}

1.2 注意点

  • 构造函数不对外开放,一般为private。
  • 通过static静态方法或者枚举类返回单例类对象
  • 确保多线程环境下有且只有一个对象,采用synchronized关键字执行同步。
  • 第二次非空判断确保此前没有其它线程进入synchronized创建新实例。比如一个线程进入同步块创建实例时,另一个线程到来进行了第一次非空判断后开始等待,之后第一个线程创建实例完离开,另一个线程进入同步块,所以此时需要进行第二次非空判断。
    • volatile关键字修饰instance,保证每次线程使用时,instance都是最新的。

1.3 应用
Android中通过Context获取的系统级别的服务,很多都是通过单例模式实现的。有些Application也会以单例模式实现,以及Activity管理类和图片加载工具等。
此外,单例对象经常使用static,如果持有Context,很容易引发内存泄漏。

2、建造者模式(Buider模式)

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

2.1 实例情景
假设有一个对象Student,它有很多的属性,像id,name,age等,同时它的属性有的可以为空有的必须设置(假设id为必须的),这时如果我们想要实例化一些Student时,就必须定义很多的构造函数,每个构造函数传入不同的参数,类似:

public Student(int id){        this.id = id;    }    public Student(int id, String name){        this.id = id;        this.name = name;    }

这样子如果属性很多时,构造函数会越来越多,很不优雅,这时可以采用Builder模式。

2.2 示例代码

public class Student {    private int id;    private String name;    private int age;    private String address;    private Student(Builder builder) {        this.id = builder.id;        this.name = builder.name;        this.age = builder.age;        this.address = builder.address;    }    public static class Builder {        private int id;        private String name;        private int age;        private String address;        // 假设id为必须        public Builder(int id) {            this.id = id;        }        public Builder setName(String name) {            this.name = name;            return this;        }        public Builder setAge(int age) {            this.age = age;            return this;        }        public Builder setAddress(String address) {            this.address = address;            return this;        }        public Student build() {            return new Student(this);        }    }    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public String getAddress() {        return address;    }    public void setAddress(String address) {        this.address = address;    }}
  • 1.构建一个Builder,定义和Student一样的属性变量。
  • 2.通过成员函数进行属性设置,每个函数返回this,也就是Builder对象
  • 3.提供一个build函数创建Student对象,传入Builder。
  • 4.在Student的构造函数中,利用传入的Builder对Student的属性成员赋值。
  • 5.调用
Student student = new Student.Builder(1)                .setName("小明")                .setAge(15)                .setAddress("China")                .build();

这样子整体代码可读性提高了很多,过程也清晰了

2.3 应用
在Android中,比较常用的AlertDialog就是采用了Builder模式。

     new AlertDialog.Builder(this)                .setMessage("对话框")                .setIcon(R.mipmap.ic_launcher)                .create()                .show();

此外,图片加载框架Android-Universal-Image-Loader中的DisplayImageOptions也是采用了建造者模式,具体实现可以参考源码。

3、原型模式

用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

简单来说就是拷贝一个对象,一般通过实现Cloneable接口,并重写clone方法

3.1 实例代码

ublic class Teacher implements Cloneable {    private int age;    private String name;    private List<String> studens = new ArrayList<>();    private List<String> classes = new ArrayList<>();    public Teacher(int age, String name, List<String> studens, List<String> classes){        this.age = age;        this.name = name;        this.studens = studens;        this.classes = classes;    }    @Override    public Object clone() {        try {            Teacher teacher = (Teacher) super.clone();            teacher.age = this.age;            teacher.name = this.name;            // 浅拷贝            teacher.studens = this.studens;            // 深拷贝            teacher.classes = new ArrayList<>(this.classes);            return teacher;        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return null;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public List<String> getStudens() {        return studens;    }    public void setStudens(List<String> studens) {        this.studens = studens;    }    public List<String> getClasses() {        return classes;    }    public void setClasses(List<String> classes) {        this.classes = classes;    }}

调用时

 List<String> students = new ArrayList<>();        students.add("小A");        students.add("小B");        List<String> classes = new ArrayList<>();        classes.add("Chinese");        classes.add("Math");        Teacher teacher = new Teacher(33, "王老师", students, classes);        Teacher cloneTeacher = (Teacher)teacher.clone();

原型模式相对比较简单,这里有一个注意点:浅拷贝和深拷贝,参考代码第20行。
浅拷贝并不是重新构造了一份,而只是拷贝了原对象的引用,Teacher有一个Student列表,在clone里进行了浅拷贝;在实际调用中,如果cloneTeacher对其student列表进行修改的话,原teacher的student列表也会相应的修动。
深拷贝则重新构造了一份对象,参考classes列表,当cloneTeacher的classes列表改变时,原teacher的classes列表不会改变,类似保护性拷贝。
一般原型模式应该尽量选择深拷贝,避免影响原始对象。

3.2 应用
Android的Intent和Bundle都实现了Cloneable接口,重写了clone方法,直接返回了一个新对象

public Object clone() {        return new Bundle(this);    }

4、工厂方法模式

定义一个用于创建对象的接口,让子类决定实例化哪个类。

工厂方法分为四个模块:抽象产品,抽象工厂,具体产品,具体工厂,先看下通用模式代码
4.1 代码示例

public abstract class Car {    public abstract void drive();}public class CarA extends Car{    @Override    public void drive() {        System.out.print("这是CarA");    }}public class CarB extends Car {    @Override    public void drive() {        System.out.print("这是CarB");    }}public abstract class CarFactory {    public abstract Car createCar();}public class CarFactoryA extends CarFactory {    @Override    public CarA createCar() {        return new CarA();    }}public class CarFactoryB extends CarFactory {    @Override    public CarB createCar() {        return new CarB();    }}

实际调用时

CarA carA = new CarFactoryA().createCar();CarB carB = new CarFactoryB().createCar();

代码相对比较简单,抽象类定义抽象方法,子类实现具体的业务逻辑,将实例化的过程交给具体工厂,实际调用不参与产品的实例化,比较适合复杂对象的实例化。
当产品很多时,会需要很多产品类和工厂类,所以可以采用传入类名反射获取的方法,产品类不需要改变,修改下抽象工厂和具体工厂

public abstract class CarFactory {    // 反射获取类    public abstract <T extends Car> T createCar(Class<T> clz);}public class CarFactoryA extends CarFactory {    // 反射获取类    @Override    public <T extends Car> T createCar(Class<T> clz) {        Car car = null;        try {            car = (Car) Class.forName(clz.getName()).newInstance();        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {            e.printStackTrace();        }        return (T) car;    }}

这样只需要一个抽象工厂和一个具体工厂就能实现多种产品的实例

CarA car = new CarFactoryA().createCar(CarA.class);

4.2 应用
android中,Context有一个抽象方法

public abstract String getSystemServiceName(Class<?> serviceClass);

Activity中具体实现了该方法,通过传入服务名,返回不同的manager

@Override    public Object getSystemService(@ServiceName @NonNull String name) {        if (getBaseContext() == null) {            throw new IllegalStateException(                    "System services not available to Activities before onCreate()");        }        if (WINDOW_SERVICE.equals(name)) {            return mWindowManager;        } else if (SEARCH_SERVICE.equals(name)) {            ensureSearchManager();            return mSearchManager;        }        return super.getSystemService(name);    }

5、抽象工厂模式

为创建一组相关或者是相互依赖的对象提供一个接口,而不需要指定它们的具体类。

5.1 代码示例

// 抽象产品Apublic abstract class AbstractProductA {    public abstract void method();}// 抽象产品Bpublic abstract class AbstractProductB {    public abstract void method();}// 具体产品A1public class ConcreteProductA1 extends AbstractProductA {    @Override    public void method() {        System.out.print("这是A1");    }}// 具体产品A2public class ConcreteProductA2 extends AbstractProductA {    @Override    public void method() {        System.out.print("这是A2");    }}// 具体产品B1public class ConcreteProductB1 extends AbstractProductB {    @Override    public void method() {        System.out.print("这是B1");    }}// 具体产品B2public class ConcreteProductB2 extends AbstractProductB {    @Override    public void method() {        System.out.print("这是B2");    }}// 抽象工厂public abstract class AbstractFactory {    public abstract AbstractProductA createProductA();    public abstract AbstractProductB createProductB();}// 具体工厂1public class ConcreteFactory1 extends AbstractFactory {    @Override    public AbstractProductA createProductA() {        return new ConcreteProductA1();    }    @Override    public AbstractProductB createProductB() {        return new ConcreteProductB1();    }}// 具体工厂2public class ConcreteFactory2 extends AbstractFactory {    @Override    public AbstractProductA createProductA() {        return new ConcreteProductA2();    }    @Override    public AbstractProductB createProductB() {        return new ConcreteProductB2();    }}

抽象工厂模式类更多,但结构相对比较清晰。抽象工厂比起工厂方法而言,着重于一组产品,同时产品间或多或少有一定的关联,但抽象工厂模式会导致类的剧增,扩展起来也比较麻烦,相对而言用得比较少。

5.2 应用
Adroid里抽象工厂模式暂时还没看到有什么实际应用,抽象工厂扩展比较难,所以一般比较少提供对外访问。

6、适配器模式

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

6.1 适用场景

  • 系统需要类的接口不符合系统的需要,即接口不兼容
  • 想建立一个可以复用的的类,使得该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作
  • 需要一个统一的输出接口,而输入的类型不可预知

适配器模式有两种:类适配器模式和对象适配器模式
假设有一台手机,需要5V的输入,但只有220V的电源,这时候需要一个适配器来进行转换,下面看下两种适配器模式的代码

6.2 类适配器模式

// 标准接口Targetpublic interface Volt5 {    int getVolt5();}// Adaptee,需要被转换的对象public class VoltResource220 {    public int getVolt220() {        return 220;    }}// 适配器Adapter,继承了Adaptee,又实现了标准接口public class VoltAdapter extends VoltResource220 implements Volt5{    // 在这个类里将220V转为5V    @Override    public int getVolt5() {        getVolt220();        return 5;    }}

实际调用时

VoltAdapter adapter = new VoltAdapter();adapter.getVolt5();

类适配器模式继承被适配类Adaptee来获取其方法或成员变量,同时实现标准接口来实现。

6.3 对象适配器模式

public class ObjectVoltAdapter implements Volt5 {    private VoltResource220 resource220;    public ObjectVoltAdapter(VoltResource220 resource) {        this.resource220 = resource;    }    @Override    public int getVolt5() {        return 5;    }}

实际调用时传入Adaptee

ObjectVoltAdapter adapter1 = new ObjectVoltAdapter(new VoltResource220());adapter1.getVolt5();

对象适配器模式不采用继承的方式关联Adaptee,而是采用代理的方式,将被适配对象传递到Adapter中,相对而言再加灵活。

6.4 应用
适配器模式在Android里应该相当广泛,最常见的是ListView,RecyclerView等的适配器。列表为什么需要适配器呢?因为列表样式有千千万万,但它只关心它的每一个子项item view,而不关心其具体实现,所以增加一个Adapter来实现每一个子项的变化,然后将Adapter设置给列表。

7、装饰者模式

动态地给一个对象添加一些额外的职责。

装饰者模式可以不改变原类的情况下,动态地添加职责,从而实现功能的动态拓展。
假设有一个boy,最开始只穿了pants,后来想穿上衣服了,比如T-shirt,比如戴上帽子,这时候用装饰者模式来实现如下。

7.1 代码示例

// 抽象人public abstract class Person {    protected String name = "";    // 自定义所需方法    public abstract void wear(String clothes);}// 具体实现boypublic class Boy extends Person {    public Boy() {        name = "boy";    }    @Override    public void wear(String clothes) {        System.out.println(name + "穿了" + clothes);    }}//hat装饰器,继承自Personpublic class HatDecorator extends Person {    Person person;    public HatDecorator(Person person) {        this.person = person;    }    @Override    public void wear(String clothes) {        person.wear(clothes);    }}// T-shirt装饰器public class TshirtDecorator extends Person {    Person person;    public TshirtDecorator(Person person) {        this.person = person;    }    @Override    public void wear(String clothes) {        person.wear(clothes);    }}

实际调用时

Person boy = new Boy();boy.wear("pants");HatDecorator hat = new HatDecorator(boy);TshirtDecorator tshirt = new TshirtDecorator(boy);hat.wear("Hat");tshirt.wear("T-shirt");

装饰器和被装饰者有相同的接口或相同的父类,同时保存了一个被装饰器的引用,可以在不改变原类层次 结构的基础上,实现功能的扩展。装饰者模式的拓展性和灵活性很高,通过不同的装饰类可以使原有对象具备各种各样的属性。

7.2 应用
Android中比较经典的是Context抽象类扩展出的ContextWrapper,从名字就可以看出来这是一个Wrapper。
Context是一个抽象类,它的具体实现是ContextImpl,ContextWrapper是Activity的父类,它同样是继承自Context,同时它有一个Context的引用,所以ContextWrapper就是一个装饰器。

public ContextWrapper(Context base) {        mBase = base;    }

8、代理模式

为其他对象提供一种代理以控制对这个对象的访问。

代理模式就是不直接访问某个对象,而是通过一个代理对象来间接访问,就像不去饭店吃饭,让同事帮带饭或者叫个外卖一样,同事或外卖平台成为了一个代理对象。
假如有一个HungryPerson,他很饿想订个餐,但又不想出门,于是让他的朋友Friend帮他带个饭。

8.1 代码示例

// 订餐流程public interface IOrderMeal {    void chooseFood();    void pay();}// 饥饿的人想订餐public class HungryPerson implements IOrderMeal {    @Override    public void chooseFood() {        System.out.println("订个鸡腿饭吧");    }    @Override    public void pay() {        System.out.println("付款");    }}// 朋友帮订public class Friend implements IOrderMeal {    IOrderMeal person;    public Friend(IOrderMeal person) {        this.person = person;    }    @Override    public void chooseFood() {        person.chooseFood();    }    @Override    public void pay() {        person.pay();    }}

实际调用

IOrderMeal orderMeal = new Friend(new HungryPerson());orderMeal.chooseFood();orderMeal.pay();

代理对象Friend持有一个被代理对象HungryPerson的引用,代理对象的方法本质上是调用被代理对象的方法,主要还是一种委托机制,相对比较容易理解。代理模式有比较高的扩展性,符合开闭原则,方便进行修改。

8.2 应用
Android中的ActivityManager的getRunningServices执行时采用代理执行。

    public List<RunningServiceInfo> getRunningServices(int maxNum)            throws SecurityException {        try {            return ActivityManagerNative.getDefault()                    .getServices(maxNum, 0);        } catch (RemoteException e) {            throw e.rethrowFromSystemServer();        }    }

在实际开发中,有时候使用一些开源库,比如图片加载工具之类的,一般都会进行封装,而不在代码中直接使用,对外提供自定义的方法。这样如果要更换库或者维护什么的,可以在封装类中进行,尽量减少对其它代码的影响,可以看作一种代理模式。

9、外观模式

要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。

外观模式简单说就是为一个复杂的子系统提供一个简单的接口,外部隐藏其内部的具体实现,隔离变化。
假设有一台电脑,包含CPU和显示器两个子模块,每个模块的启动都需要一些步骤。

9.1 代码示例

// CPU模块public class CPU {    public void start(){        System.out.println("CPU启动");        System.out.println("CPU初始化");        System.out.println("CPU运行");    }}// 显示屏模块public class Display {    public void start(){        System.out.println("显示器启动");        System.out.println("显示器初始化");        System.out.println("显示器运行");    }}// 电脑public class Computer {    CPU cpu = new CPU();    Display display = new Display();    // 开机    public void start() {        cpu.start();        display.start();    }}

实际调用

Computer computer = new Computer();computer.start();

这样子不需要在意其子系统具体的实现,对外暴露统一的方法,为用户提供了一个高层次接口方法,使系统更加易用,同时隐藏了内部实现,这样即使内部系统发生变化时,外部也不会感知到。

9.2 应用
Android里类似startActivity()、sendBroadcast()等就是用了外观模式,调用时不需要关心其内部具体实现,而直接使用其暴露的方法就能完成功能实现。

10、桥接模式

将抽象部分与实现部分分享,使它们都可以独立地进行变化。

桥接模式通过提供桥接结构,把抽像化和实现化解耦,当一个类存在多个维度的变化,且两个维度都可扩展时,可以考虑采用桥接模式。
假设现在有pants和dress,都属于衣服clothes,同时每种都有大小两个尺寸。
10.1 代码示例

// 尺寸接口public interface ISize {    String getSize();}// 大号尺寸public class BigSize implements ISize {    @Override    public String getSize() {        return "大号";    }}// 小号尺寸public class SmallSize implements ISize {    @Override    public String getSize() {        return "小号";    }}// 衣服抽象类public abstract class Clothes {    ISize size;    public Clothes(ISize size) {        this.size = size;    }    public String wear() {        return size.getSize() + getClothes();    }    protected abstract String getClothes();}// 短裤public class Pants extends Clothes {    public Pants(ISize size) {        super(size);    }    @Override    protected String getClothes() {        return "pants";    }}// 连衣裙public class Dress extends Clothes {    public Dress(ISize size) {        super(size);    }    @Override    protected String getClothes() {        return "dress";    }}

实际调用

 Clothes clothes = new Dress(new BigSize());String wearClothes = clothes.wear();clothes = new Pants(new SmallSize());wearClothes = clothes.wear();
  • 先定义一个ISize接口,即桥接口,尺寸实现该接口
  • 定义衣服抽象类,表示变化维度的另一方,内部持有ISize接口引用,各种衣服都继承该抽象类。

可以看出,不管Size或者Clothes增加或减少,相对对方而言都是独立的,两者的联系是Clothes持有Size的引用,充当两者的纽带。桥接模式有一定的方向性,桥接口的一方是被动的,由抽象方持有并调用。

10.2 应用
在View的视图层级中,Button,TextView等都属于View的子类,每个子类都有各自的属性和行为,这是View一个维度上的变化;另一个维度的变化是屏幕绘制,这一部分的功能实现由Canvas、DisplayList等负责,这可以看成是桥接模式的一种应用。

11、组合模式

将对象组成成树形结构,以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

直接看下文件系统,其包含两种,文件和文件夹。

// 抽象文件,根节点public abstract class Dir {    // 存储文件夹下所有元素    protected List<Dir> dirs = new ArrayList<>();    // 当前文件夹或文件名    private String name;    public Dir(String name){        this.name = name;    }    public abstract void addDir(Dir dir);    public String getName() {        return name;    }}// 文件public class File extends Dir {    public File(String name) {        super(name);    }    @Override    public void addDir(Dir dir) {        System.out.print("文件不支持添加");    }}// 文件夹public class Folder extends Dir {    public Folder(String name) {        super(name);    }    @Override    public void addDir(Dir dir) {        dirs.add(dir);    }}

实际调用

Dir myFolder = new Folder("图片");myFolder.addDir(new File("a.jpg"));myFolder.addDir(new File("b.jpg"));myFolder.addDir(new Folder("里层文件夹"));

组合模式重点在于树形结构,抽象根节点,实际调用时可以一致地使用组合结构或其中的单个对象,方便遍历,简化代码。对于树状结构或者二叉树等都可以采用组合模式。但是组合模式使设计更加抽象,元素来自相同的抽象层,必须在实现时进行类型检查。

11.2 应用
Android中View、ViewGroup的嵌套组合就是采用组合模式实现的。

12、享元模式

使用共享对象可有效地支持大量的细粒度的对象。

面向对象可以很好的解决一些灵活性和可扩展的问题,但很多情况下需要在系统中增加类和对象的个数。当对象数量过多时,会导致创建和回收的代价过高,造成性能下降。
享元模式适用于可能存在大量重复对象的场景,来缓存可共享的对象,避免创建过多的重复对象。

假设现在一个手机销售店,每个客人来了都会询问下手机的价格配置等,如果每次询问都创建一个新对象的话,当人数过多时会导致大量对象的产生,可以利用享元模式来避免这个问题。

12.1 示例代码

// 享元对象接口public interface IPhone {    void showInfo();}// 具体享元对象public class PhoneInfo implements IPhone {    String phone;    public PhoneInfo(String phone) {        this.phone = phone;    }    @Override    public void showInfo() {        System.out.print("这是" + phone + "的信息");    }}// 享元工厂public class PhoneFactory {    // map缓存对象    private static Map<String, PhoneInfo> phones = new HashMap<>();    public static PhoneInfo getPhoneInfo(String phone) {        String key = phone;        if (phones.containsKey(key)) {            return phones.get(key);        } else {            PhoneInfo phoneInfo = new PhoneInfo(phone);            phones.put(key, phoneInfo);            return phoneInfo;        }    }}

实际调用时

PhoneInfo phone1 = PhoneFactory.getPhoneInfo("小米");phone1.showInfo();PhoneInfo phone2 = PhoneFactory.getPhoneInfo("华为");phone2.showInfo();

创建的这两个对象会缓存到map中,下次再查询时,会从对象池中直接拿出来,不需要再创建对象。

享元模式一般分为三个部分:

  • Flyweight:享元对象抽象基类或接口
  • ConcreteFlyweight:具体的享元对象
  • FlyweightFactory:享元工厂,负责管理享元对象池和创建享元对象。

12.2 java的String
java中的String采用的就是享元模式,一个String被定义后就被缓存到了常量池中。
java里字符串比较有两种,equals和==。
==比较的是他们在内存中的存放地址,String是非基本数据类型的变量,也叫引用类型的变量,它存储的不是值本身,而是其关联的对象的内存的地址。

String a = "aaa";String b = "aaa";boolean result = (a == b);

这样子比较后,得到的result为true,说明a和b指向的是同一个对象内存,b使用了缓存池中的a对象。
equals继承自Object的方法,在Object中

public boolean equals(Object obj) {        return (this == obj);    }

这个方法比较的是内存地址,但在String中,这个方法被重写了,变成了根据字符值进行判断。

 public boolean equals(Object anObject) {        if (this == anObject) {            return true;        }        if (anObject instanceof String) {            String anotherString = (String) anObject;            int n = count;            if (n == anotherString.count) {                int i = 0;                while (n-- != 0) {                    if (charAt(i) != anotherString.charAt(i))                            return false;                    i++;                }                return true;            }        }        return false;    }

12.3 应用
Android 线程间通信时,经常会用到Message,可以通过new Message来创建一个新的Message,但不建议使用,最好使用Message.obtain来获取实例,因为在obtain里,Message对象被缓存了。

public static Message obtain() {        synchronized (sPoolSync) {            if (sPool != null) {                Message m = sPool;                sPool = m.next;                m.next = null;                m.flags = 0; // clear in-use flag                sPoolSize--;                return m;            }        }        return new Message();    }

这是官方文档的解释

While the constructor of Message is public, the best way to get one of these is to call Message.obtain() or one of the Handler.obtainMessage() methods, which will pull them from a pool of recycled objects.

13、策略模式

策略模式定义了一系列的算法,并将每个算法封装起来,而且使它们还可以相互替换。
策略模式让算法独立于使用它的客户而独立变化。

举个例子,我想去旅游了,去的方式可以选择飞机,船,火车等等,每种的花费都不一样,有些可能还需要换乘什么的。这时候可以写一个类用if-else或者switch-case,对每种方式进行计算。这样子确实可以实现功能,但维护成本变高,如果我要增加或者减少出行方式时,需要改变if-else,容易出错。
考虑使用策略模式。

13.1 代码示例

// 策略接口public interface ICalculate {    String name();    int cost();}// 具体方式1public class PlaneCalculate implements ICalculate {    @Override    public String name() {        return "plane";    }    @Override    public int cost() {        return 1000;    }}// 具体方式2public class ShipCalculate implements ICalculate {    @Override    public String name() {        return "plane";    }    @Override    public int cost() {        return 500;    }}// 策略操作类public class CalculateStrategy {    private ICalculate calculate;    public void setCalculate(ICalculate calculate) {        this.calculate = calculate;    }    public void cost() {        System.out.println(calculate.name() + "花费" + calculate.cost());    }}

实际调用时

CalculateStrategy strategy = new CalculateStrategy();strategy.setCalculate(new PlaneCalculate());strategy.cost();

当要添加出行方式时,只需要继续实现策略接口就行了,实际调用时,可以随便替换具体策略。

13.2 应用
Android动画效果中,有一个时间插值器(TimeInterpolator),它的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比。除了系统预置的几种插值器外,还可以自定义插值器,通过注入不同的插值器,可以实现不同的动态效果,这就是策略模式。

14、模板方法模式

定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

模板方法其实是封装一个固定流程,在抽象类中定义好,而子类可以有不同和算法实现。假设我想去商店买面包,这件事有三个步骤是必须的:拿钱,去商店,买面包。

14.1 代码示例

public abstract class AbstractBuyBread {    public final void buySomeBread() {        getMoney();        goShop();        buy();    }    protected void getMoney() {        System.out.println("找老爸要钱");    }    protected void goShop() {        System.out.println("去商店");    }    protected void buy() {        System.out.println("买面包");    }}// 具体实现public class BuyBread extends AbstractBuyBread {    @Override    protected void goShop() {        System.out.println("骑单车去商店");    }}

实际调用

BuyBread buyBread = new BuyBread();buyBread.buySomeBread();

在这里有一个抽象的买面包类,定义了三个步骤,同时定义了一个final方法,把三个步骤固定了。在具体子类中,可以重写三个步骤的方法,我可以换成自己出钱或者找老爸要钱等等,但没法修改整体流程,这就是模板方法模式。

14.2 应用
异步任务类AsyncTask可以实现异步操作,并提供接口反馈。创建AsyncTask后调用execute方法执行异步任务,execute是一个final方法,调用execute后会依次执行onPreExecute(),doInBackground ()等,而这些方法可以在实际使用时自定义业务逻辑。

15、观察者模式

定义对象间一种一对多的依关系,使得每当一个对象改变状态,则所有依赖于它的对象都地得到通知并被自动更新。

观察者模式基本用过Rx系列的都会知道一些,它是一个使用率很高的模式。事件触发场景,订阅发布系统,消息队列,事件总线等很多地方都会用来。
小明和小黄是漫画迷,他们用了一款漫画app,他们肯定不想一直不停刷新app看有没有更新,而是希望有更新时,app会提醒他们。Java里提供了Observer和Observable来实现观察者模式。

15.1 代码示例

// 被观察者public class ComicApp extends Observable {    public void hasNew(String comic) {        System.out.println(comic + "更新了");        setChanged();        notifyObservers(comic);    }}// 观察者public class People implements Observer {    String name;    public People(String name) {        this.name = name;    }    @Override    public void update(Observable o, Object arg) {        System.out.println(name + "收到更新!");    }}

实际调用

ComicApp comicApp = new ComicApp();People people1 = new People("小明");People people2 = new People("小黄");comicApp.addObserver(people1);comicApp.addObserver(people2);comicApp.hasNew("海贼王");
  • 被观察者继承了Observable,定义了一个方法,当调用这个方法时,会输入更新语句,同时调用setChanged()和notifyObservers()进行通知。
  • 观察者实现了Observer接口,当被观察者通知时,会调用观察者的update方法。
  • 实际使用时,需要给被观察者添加Observer才能将两者绑定到一起。

运行后会输入

海贼王更新了小明收到更新!小黄收到更新!

15.2 应用
开发中经常会用到RecyclerView,数据改变时,调用notifyDataSetChanged()进行刷新,这里就是用的观察者模式。

 public final void notifyDataSetChanged() {    mObservable.notifyChanged();}

16、迭代器模式

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

举个例子来说,教务主任想看下各班的学生成绩,让老师们各自统计出来给主任查看,A老师用了List,也许B老师用了数组,C老师又用了别了,这样主任就要针对不同的数据提供不同的遍历方法,所以采用迭代器模式来实现。

16.1 代码示例

// 迭代器接口public interface IScoreIterator {    boolean hasNext();    Object next();}// 具体实现迭代器public class ListIterator implements IScoreIterator {    private List<String> list;    private int position = 0;    public ListIterator(List<String> list) {        this.list = list;    }    @Override    public boolean hasNext() {        return !(position > list.size() - 1 || list.get(position) == null);    }    @Override    public Object next() {        String score = list.get(position);        position++;        return score;    }}// 集合接口public interface IScoreAggregate {    IScoreIterator getIterator();}// 集合实现public class ListAggregate implements IScoreAggregate {    private List<String> list = new ArrayList<>();    public ListAggregate() {        list.add("小明:100");        list.add("小白:80");        list.add("小黄:60");    }    @Override    public IScoreIterator getIterator() {        return new ListIterator(list);    }}

实际调用

ListAggregate list = new ListAggregate();IScoreIterator iterator = list.getIterator();while (iterator.hasNext()) {    System.out.println(iterator.next());}

大致分成四个模块

  • 迭代器接口:进行hasNext判断和返回下一个对象。
  • 具体迭代器:继承自迭代器接口,不管用的是哪种集合,对应地实现接口方法。
  • 集合接口:可以定义数据的添加、删除、清空等方法,同时返回一个迭代器。
  • 具体集合实现:继承集合接口,具体实现各种方法。
  • 实际调用时,不管集合用的是List或者是数组还是其它,都可以用hasNext判断,循环用next获取数据。

16.2 应用
Android里各种List,Map都包含有迭代器,此外,当使用SQLiteDatabase的query方法查询时,会返回一个Cursor游标对象,实质上就是一个迭代器。

17、责任链模式

使多个对象都有机会处理请求,从而避免请求的发送者和接受者直接的耦合关系,将这些对象连成一条链,并沿这条链传递该请求,直到有对象处理它为止。

这个比较好理解,多个对象都可以处理同一个请求,沿着链传递,直到有对象处理为止。
假设小明要去买一本书,由近到远有三个书店,小明依次到这三个书店买书。

17.1 代码示例

// 抽象书店public abstract class AbstractShop {    // 下一个节点    public AbstractShop nextShop;    public final void queryBook() {        query();        if (hasBook()) {            System.out.println("有书!");            buyBook();        } else if (nextShop != null) {            System.out.println("没有书!");            nextShop.queryBook();        }    }    public abstract void query();    public abstract boolean hasBook();    public abstract void buyBook();}// 具体书店public class ShopA extends AbstractShop {    @Override    public void query() {        System.out.println("Shop A开始查询");    }    @Override    public boolean hasBook() {        return false;    }    @Override    public void buyBook() {        System.out.println("在Shop A买到了书");    }}public class ShopB extends AbstractShop {    @Override    public void query() {        System.out.println("Shop B开始查询");    }    @Override    public boolean hasBook() {        return false;    }    @Override    public void buyBook() {        System.out.println("在Shop B买到了书");    }}public class ShopC extends AbstractShop {    @Override    public void query() {        System.out.println("Shop C开始查询");    }    @Override    public boolean hasBook() {        return true;    }    @Override    public void buyBook() {        System.out.println("在Shop C买到了书");    }}

实际调用

ShopA shopA = new ShopA();ShopB shopB = new ShopB();ShopC shopC = new ShopC();shopA.nextShop = shopB;shopB.nextShop = shopC;shopA.queryBook();

运行结果

Shop A开始查询没有书!Shop B开始查询没有书!Shop C开始查询有书!Shop C买到了书

抽象类持有下一个节点的引用,同时声明了处理请求的方法,当能够处理请求时,则处理;无法处理时,调用下一个节点的处理方法。

17.2 应用
Android里点击事件的分发处理就用到了责任链模式,接收到点击事件时,父View先进行处理,如果不处理再传递给子View。

18、命令模式

将一个请求封装成一个对象,从而让用户使用不同的请求把客户端参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。

面向对象时,我们一般会给对象定义参数方法等,请求时具体调用对应方法。如果整个请求过程比较复杂,或者需要额外处理如日志等,可以采用命令模式。
举个简单例子,手机的开关机,用命令模式的话可以这么写。

18.1 代码示例

// 具体对象手机public class CellPhone {    public void start() {        System.out.println("手机开机");    }    public void off() {        System.out.println("手机关机");    }}// 命令接口public interface ICommand {    // 执行命令    void execute();}// 开机命令public class StartCommand implements ICommand {    private CellPhone phone;    public StartCommand(CellPhone phone) {        this.phone = phone;    }    @Override    public void execute() {        phone.start();    }}// 关机命令public class OffCommand implements ICommand {    private CellPhone phone;    public OffCommand(CellPhone phone) {        this.phone = phone;    }    @Override    public void execute() {        phone.off();    }}// 命令控制类public class ControlButton {    private ICommand startCommand;    private ICommand offCommand;    public ControlButton(ICommand start, ICommand off) {        this.startCommand = start;        this.offCommand = off;    }    public void start() {        startCommand.execute();    }    public void off() {        offCommand.execute();    }}

实际调用

CellPhone cellPhone = new CellPhone();StartCommand startCommand = new StartCommand(cellPhone);OffCommand offCommand = new OffCommand(cellPhone);ControlButton control = new ControlButton(startCommand, offCommand);control.start();control.off();

每个命令持有操作对象的引用,在命令里对对象进行操作,封装一个控制类对各个命令进行控制。命令模式其实就是对对命令的封装,将命令的发出者和命令的执行者分离。

18.2 应用
Android的事件机制中底层逻辑对事件的转发处理采用的是命令模式,每一种事件在屏幕上产生后都会经由底层逻辑将其转换为一个NotifyArgs对象。

19、备忘录模式

在不破坏封闭的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态,这样,以后就可将对象恢复到原先保存的状态中。

备忘录模式其实就是增加一个备份对象,可以时不时进行备份和恢复。
比如玩游戏,玩到一半存个档,需要的时候恢复下存档。

19.1 代码示例

// 备忘录对象public class Memento {    public int playerLife;    public int playerLevel;}// 原对象public class Player {    private int level;    private int life;    public Player() {        level = 0;        life = 100;        System.out.println("初始人物,等级:" + level + ",生命:" + life);    }    public void play() {        level++;        life -= 10;        System.out.println("玩了一下,等级:" + level + ",生命:" + life);    }    // 保存备忘录    public Memento savaMemento() {        Memento memento = new Memento();        memento.playerLevel = level;        memento.playerLife = life;        System.out.println("存档");        return memento;    }    // 恢复    public void restore(Memento memento) {        level = memento.playerLevel;        life = memento.playerLife;        System.out.println("恢复存档,等级:" + level + ",生命:" + life);    }}// 备忘录管理类public class MementoManager {    private Memento memento;    public Memento getMemento() {        return memento;    }    public void setMemento(Memento memento) {        this.memento = memento;    }}

实际调用

MementoManager mementoManager = new MementoManager();Player player = new Player();player.play();mementoManager.setMemento(player.savaMemento());player.play();player.play();player.restore(mementoManager.getMemento());

输出结果

初始人物,等级:0,生命:100玩了一下,等级:1,生命:90存档玩了一下,等级:2,生命:80玩了一下,等级:3,生命:70恢复存档,等级:1,生命:90

可以看出备忘录模式大致可以分成三个模块:原有对象,备忘录对象和备忘录管理类,代码相对比较简单,不多说了。
备忘录模式可以把原有对象屏蔽起来,保持封装的隐私性,同时便于恢复。但是如果状态数据过多时,存储到备忘录上的成本会比较高。

19.2 应用
Android里备忘录模式应用很明显,Activity的状态保存和恢复,也就是onSaveInstanceState和onRestoreInstanceState。当Activity非正常退出,且被杀死前会调用这两个方法存储Activity的相关信息,并在下次返回Activity时恢复数据。

20、状态模式

当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。

状态模式经常会用到,也比较简单好理解,只是写法各种各样。最简单的不经封装的就是用if-else判断,每个行为都进行状态的判断,根据状态执行操作,但这样代码太难看了,维护起来也麻烦,下面进行简单的封装示例。
假如小红喜欢买买买吃吃吃玩玩玩,但只有发工资有钱后才能去浪,月底花光了就去不了了。

20.1 代码示例

// 状态接口,定义了一些行为public interface IState {    void buy();    void eat();    void play();}// 有钱状态,实现了状态接口public class MoneyState implements IState {    @Override    public void buy() {        System.out.println("买买买");    }    @Override    public void eat() {        System.out.println("吃吃吃");    }    @Override    public void play() {        System.out.println("玩玩玩");    }}// 没钱状态public class PoorState implements IState {    @Override    public void buy() {        System.out.println("没钱,不买");    }    @Override    public void eat() {        System.out.println("没钱,不吃");    }    @Override    public void play() {        System.out.println("没钱,不玩");    }}// 状态控制器public class MoneyControl {    IState state;    public void getMoney() {        state = new MoneyState();        System.out.println("拿到钱了");    }    public void clearMoney() {        state = new PoorState();        System.out.println("钱花光了");    }    public void buy(){        state.buy();    }    public void eat(){        state.eat();    }    public void play(){        state.play();    }}

实际调用

MoneyControl moneyControl = new MoneyControl();moneyControl.getMoney();moneyControl.buy();moneyControl.clearMoney();moneyControl.buy();moneyControl.eat();

运行输出

拿到钱了买买买钱花光了没钱,不买没钱,不吃

20.2 应用
Android里的WIFI管理就是采用了状态模式,打开和关闭两种状态,对具体的处理请求是不一样的。

21、访问者模式

封装一些作用于某种数据结构中各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。

最开始一听到这个名字我以为是类似电脑或者Chrome的访客模式,了解后发现这个模式写起来相对复杂了不少,一个简单的小例子也要在不同类里来来回回。
简单来说,就是有很多被访问者,它们都有一个accept方法来接受访问者的访问,访问者可以有多种多样,他们有一个visit方法,来访问被访问者们,但是访问者们可能关注的是不同的数据,所以需要区别对待。
比如,有一家书店,里面有各种各样的书,提供了一个书单给顾客客遍历查看,但不同的顾客关注的地方不同,假设小明关心的是价格,小红关心的是内容,看下代码。

21.1 代码示例

// 抽象被访问者,书籍public abstract class Book {    public String name;    public String value;    public int price;    public Book(String name, String value, int price) {        this.name = name;        this.value = value;        this.price = price;    }    public abstract void accept(IVisitor visitor);}// 具体书Apublic class BookA extends Book {    public BookA(String name, String value, int price) {        super(name, value, price);    }    @Override    public void accept(IVisitor visitor) {        visitor.visit(this);    }}// 具体书Bpublic class BookB extends Book {    public BookB(String name, String value, int price) {        super(name, value, price);    }    @Override    public void accept(IVisitor visitor) {        visitor.visit(this);    }}// 访问者接口public interface IVisitor {    void visit(BookA bookA);    void visit(BookB bookB);}// 具体访问者小明public class XiaoMin implements IVisitor {    @Override    public void visit(BookA bookA) {        System.out.println("小明看了下" + bookA.name + "的价格:" + bookA.price);    }    @Override    public void visit(BookB bookB) {        System.out.println("小明看了下" + bookB.name + "的价格:" + bookB.price);    }}// 具体访问者小红public class XiaoHong implements IVisitor {    @Override    public void visit(BookA bookA) {        System.out.println("小红看了下" + bookA.name + "的内容:" + bookA.value);    }    @Override    public void visit(BookB bookB) {        System.out.println("小红看了下" + bookB.name + "的内容:" + bookB.value);    }}// 书单,用于遍历public class BookList {    List<Book> list = new ArrayList<>();    public BookList() {        list.add(new BookA("A1", "很好看", 100));        list.add(new BookA("A2", "不好看", 90));        list.add(new BookB("B1", "还行", 100));        list.add(new BookB("B2", "一般般", 80));    }    // 展示给顾客    public void showBook(IVisitor visitor) {        for (Book book : list) {            book.accept(visitor);        }    }}

实际调用

BookList bookList = new BookList();IVisitor xiaomin = new XiaoMin();IVisitor xiaohong = new XiaoHong();bookList.showBook(xiaomin);bookList.showBook(xiaohong);

输出

小明看了下A1的价格:100小明看了下A2的价格:90小明看了下B1的价格:100小明看了下B2的价格:80小红看了下A1的内容:很好看小红看了下A2的内容:不好看小红看了下B1的内容:还行小红看了下B2的内容:一般般

大致上结构就是这样,分成Vistor,ConcreteVisitor,Element,ConcreteElement,ObjectStructure,一个个来看:

  • Visitor:例子中的IVistor,访客接口或者抽象类,定义了对Element的访问方法visit,它的方法理论上和元素个数是一样的,例子中有A,B两种元素,所以定义两个访问方法。
  • ConcreteVistor:具体访问者,也就是小明和小红,对不同的元素访问作出具体的行为,小明关心的是价格,小红关心的是内容。
  • Element:元素接口或抽象类,也就是例子的Book,定义了一个accept方法,每个元素都接受访问。
  • ConcreteElement:具体元素,也就是BookA和BookB,提供具体实现,通常使用访问者提供访问该元素类的方法。
  • ObjectStructure:对象结构,也就是书单,对元素集合进行管理,并可遍历访问。

21.2 应用
Android里应用的话就是编译期注解了。
注解可以分成两种,运行时注解和编译期注解。运行时注解存在一些性能问题,编译期注解比较多见,核心原理依赖APT实现,类似 ButterKnife、Dagger等。

22、中介者模式

中介者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显调用,从而使他们可以轻松耦合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用保证这些作用可以彼此独立的变化,中介者模式将多对多的相互作用转为一对多的相互作用。

中介者模式又称调解者模式,当对象之间的交互很多而且各种操作依赖彼此,为了防止一个对象的修改涉及到其它对象的行为时,可以采用中介者模式,降低耦合。本质上来说,就是把多对多的对象关系,转变成一对多关系。
假设小王想去租房,找到中介了,通过中介和房东联系。

22.1 代码示例

// 抽象人物,持有中介者Mediator的引用public abstract class APerson {    protected Mediator mediator;    public APerson(Mediator mediator) {        this.mediator = mediator;    }}// 抽象中介者,定义了操作方法public abstract class Mediator {    public abstract void operate(APerson person);}// 小王public class XiaoWang extends APerson {    public XiaoWang(Mediator mediator) {        super(mediator);    }    public void query() {        System.out.println("小王向中介询问房子");        mediator.operate(this);    }    public void getPrice() {        System.out.println("中介告知小王价格");    }}// 房东public class Landlord extends APerson {    public Landlord(Mediator mediator) {        super(mediator);    }    public void tell() {        System.out.println("房东告诉中介价格");        mediator.operate(this);    }    public void getInfo() {        System.out.println("中介告诉房东有人来问房了");    }}// 具体中介public class HouseMediator extends Mediator {    private XiaoWang xiaoWang;    private Landlord landlord;    public void setXiaoWang(XiaoWang person) {        xiaoWang = person;    }    public void setLandlord(Landlord person) {        landlord = person;    }    @Override    public void operate(APerson person) {        if (person == xiaoWang) {            landlord.getInfo();            landlord.tell();        } else {            xiaoWang.getPrice();        }    }}

实际调用

HouseMediator mediator = new HouseMediator();XiaoWang xiaowang = new XiaoWang(mediator);Landlord landload = new Landlord(mediator);mediator.setLandlord(landload);mediator.setXiaoWang(xiaowang);xiaowang.query();

输出

小王向中介询问房子中介告诉房东有人来问房了房东告诉中介价格中介告知小王价格

思路上其实比较简单,由原来的小王和房东的相互调用,变成了通过中介进行调用。代码写得比较粗糙,总体思路知道就可以了。

22.2 应用
Android里有一个KeyguardViewMediator,从名字就可以看出这个类扮演了中介者的角色。这个中介有很多管理类的成员变量,同时定义了管理器的交互方法。

23、解释器模式

给定一个语言,定义它的语法,并定义一个解释器,这个解释器用于解析语言。

小时候数学偶尔会出现一种题目,比如说

5&2=122&3=90&9=?

然后会让问?的数字是什么。其实就就是解释器模式,给了&一个新语法,定义了它的计算规则,用于实际计算。
举个简单的例子,定义一个“*”,它的作用是使它之间的数字+1。

23.1 代码示例

// 抽象解释器public abstract class AbstractExpression {    public abstract void interpret(String value);}// 具体解释器public class StarExpression extends AbstractExpression {    @Override    public void interpret(String value) {        if ("*".equals(value.substring(value.length() - 1))) {            String num = value.substring(0, value.length() - 1);            int result = Integer.parseInt(num) + 1;            System.out.println(value + "的计算结果是:" + result);        }    }}

如果输入21*,就会输出22。
如果有多个Expression,还需要定义一个计算类,来处理计算逻辑,这里就不写出来了。

23.2 应用
解释器模式其实实际用得很少,或者说我们很少会自己去构造一个类似的语法。Android里的配置文件AndroidManifest就是解释器模式的应用,里面有各种各样的声明定义,Android内部根据自定的语法去读取。

写在最后

这篇算是最近学习设计模式的一次总结,对设计模式还处于学习阶段,有些地方的理解可能不到位,写得不好,望指出。
最后还是源码。