设计模式汇总及其分类
定义:设计模式(Design pattern):是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
优点:使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
分类:设计模式依据其目的可分为创建型、结构型、行为型三种。
- 创建型模式与对象的创建有关
- 结构型模式处理类或对象的组合
- 行为型模式对类或对象怎样交互和怎样分配职责进行描述
单例模式
1. 定义
确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
2. 使用场景
确保某个类只有一个实例对象,避免产生多个对象消耗过多的资源;或者逻辑上某个类的对象应该只有一个,出现多个则出现一些错误。例如:
- 对IO、数据库、网络、图片、SharePreference等的访问
- 需要定义大量的静态常量和静态方法,例如Utils类
- 唯一序列号生成的场合
- 需要一个共享访问点或者共享数据的场合,例如全局的计数器
- Android中的SystemService就是通过单例的方式注册到系统当中
3. 实现
3.1 懒汉式
/**
* 懒汉式
* 特点:Lazy初始化;线程安全,但是由于每次需要同步性能较低,不建议使用
*/
public class Singleton {
private static Singleton sInstance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (sInstance == null) {
sInstance = new Singleton();
}
return sInstance;
}
}
3.2 双检锁/双重校验锁(DCL,即 double-checked locking)
/**
* 双检锁/双重校验锁(DCL,即 double-checked locking)
* 特点:懒汉式的改进版,Lazy初始化;线程安全,且在多线程情况下能保持高性能
*/
public class Singleton {
private static Singleton sInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (sInstance == null) {
synchronized (Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
}
3.3 饿汉式
/**
* 饿汉式
* 特点:非Lazy初始化,浪费内存;线程安全,基于ClassLoader机制避免了多线程的同步问题
*/
public class Singleton {
//方式一:类装载的时候初始化
private static Singleton sInstance = new Singleton();
//方式二:类初始化的时候才去初始化
static {
sInstance = new Singleton();
}
private Singleton() {
}
public static synchronized Singleton getInstance() {
return sInstance;
}
}
3.4 静态内部类
/**
* 静态内部类
* 特点:饿汉式只要类装载或者类初始化的时候单例初始化,但是静态内部类的方式确保调用getInstance才Lazy初始化;线程安全;推荐使用
*/
public class Singleton {
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.sInstance;
}
}
3.5 枚举
/**
* 枚举
* 特点:Lazy初始化;线程安全;这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
*/
public enum Singleton {
INSTANCE
}
3.6 通过容器来实现
/**
* 通过容器来实现
* 特点:通过特定时机(例如程序初始化)将单例注入到容器当中,使用的时候通过key来获取;降低了耦合度,提高易用性
*/
public class Singleton {
public static class SingletonManager {
private SingletonManager() {
}
private static Map sSingletonMap = new HashMap<>();
public static void register(String key, Singleton value) {
if (!sSingletonMap.containsKey(key)) {
sSingletonMap.put(key, value);
}
}
public static void unregister(String key) {
if (sSingletonMap.containsKey(key)) {
sSingletonMap.remove(key);
}
}
public static Singleton getSingleton(String key) {
return sSingletonMap.get(key);
}
}
}
3.7 Kotlin中通过object关键字实现
/**
* Kotlin中通过object关键字可以实现最简单的单例,相当于饿汉式
* 特点:这种单例只有一个实现的对象;不能自定义构造方法;可以实现接口、继续父类
*/
object Singleton {
public fun test() {
println("")
}
}
//调用方式
fun main(args: Array) {
Singleton.test();
}
4. 优点
- 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁的被创建、销毁,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决(在Java EE中采用单例模式时需要注意JVM垃圾回收机制);
- 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
- 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
5. 缺点
- 单例模式没有接口,扩展很困难,若要扩展,除了修改代码没有第二种途径可以实现。单例模式为什么不能增加接口呢?因为接口对单例模式是没有任何的意义,它要求“自行实例化”,并且提供单一实例、接口或抽象类是不可能被实例化的。
- 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
- 单例模式与单一职责原则有冲突。一个类应该只实现一个的逻辑,而不关心它是否是单例的,决定它是不是要单例是环境决定的,单例模式把“要单例”和业务逻辑融合也在一个类中。
- 通常来说,单例对象如果持有Context,很容易引发内存泄漏。此时需要注意传递给单例对象的Context是ApplicationContext。
工厂方法模式
1. 定义
工厂方法模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
2. 使用场景
- 复杂对象的生成,代替new的方式来屏蔽产品类的创建过程:需要一种产品,而不想知道也不需要知道工厂是如何生产出来的,只需要知道具体对应的工厂就行。
- 产品的替换需要灵活、可扩展性高的场合。
- 测试驱动开发的框架中,往往通过工厂方法把对象虚拟出来(用得比较少)。
- Android中的Bitmap就是通过工厂方法来创建的。
3. 实现
抽象产品类:
public abstract class Product {
}
具体产品类:
public class ConcreteProduct1 extends Product {
}
public class ConcreteProduct2 extends Product {
}
抽象工厂类:
public abstract class Factory {
public abstract T createProduct(Class clz);
}
具体工厂类:
public class ConcreteFactory extends Factory {
@Override
public T createProduct(Class clz) {
Product product = null;
try {
product = (Product) Class.forName(clz.getName()).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return (T) product;
}
}
场景类:
public class Client {
public static void main(String[] args) {
ConcreteFactory factory = new ConcreteFactory();
factory.createProduct(ConcreteProduct1.class);
}
}
4. 优点
- 良好的封装性与解偶。利用工厂的工厂方法类去创建具体的产品对象,隐藏了具体产品对象的创建细节,只需要关心具体产品对应的具体工厂。高层模块只需要关心抽象产品类。
- 遵守开闭原则,扩展性好。加入新的产品类时,只需要同时加入工厂类就可以实现扩展,无需修改原来的代码。
5. 缺点
- 随着产品种类的数量的增长,工厂类也会随之增加,将不利于系统的维护,增加系统编译和运行的开销。
抽象工厂模式
1. 定义
为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。
抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时(产品族——两个互相影响的产品线),通过抽象工厂模式产生需要的对象是一种非常好的解决方式。
2. 使用场景
一个对象族(或是一组没有任何关系的对象)都有相同的约束,例如生成不同操作系统的程序,则可以使用抽象工厂模式。(相对来说用得比较少)
3. 实现
两个产品线(1、2和A、B)产品的抽象类及其实现:
public abstract class ProductA {}
public class ConcreteProductA1 extends ProductA {}
public class ConcreteProductA2 extends ProductA {}
public abstract class ProductB {}
public class ConcreteProductB1 extends ProductB {}
public class ConcreteProductB2 extends ProductB {}
抽象工厂类:
public abstract class Factory {
public abstract ProductA createProductA();
public abstract ProductB createProductB();
}
具体的工厂实现类:
public class ConcreteFactory1 extends Factory {
@Override
public ProductA createProductA() {
return new ConcreteProductA1();
}
@Override
public ProductB createProductB() {
return new ConcreteProductB1();
}
}
public class ConcreteFactory2 extends Factory {
@Override
public ProductA createProductA() {
return new ConcreteProductA2() {
};
}
@Override
public ProductB createProductB() {
return new ConcreteProductB2();
}
}
4. 优点
- 封装性:与工厂方法模式一样,高层模块无需关心产品的具体实现,只需要关心接口。
- 产品族内的约束为非公开状态,例如可以增加控制产品族之间的比例的功能。
5. 缺点
- 扩展性差:产品族难扩展,产品等级易扩展。
构建者模式
1. 定义
建造者模式也叫做生成器模式。将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
2. 使用场景
- 相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式。
- 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可以使用该模式。
- 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适。
- 在对象创建过程中会使用到系统中的一些其他对象,这些对象在产品对象的创建过程中不易得到时,也可以采用建造者模式封装该对象的创建过程。该种场景只能是一个补偿方法,因为一个对象不容易获得,而在设计阶段竟然没有发觉,而要通过创建者模式柔化创建过程,本身已经违反设计的最初目标。
- Android当中的Dialog.Builder、一些常见的框架的初始化等等,都是采用构建者模式
3. 实现
设计模式只是纯理论,具体实现有多种变化。下面以最常见的连点调用为例子:
public class Product {
private String mPartA;
private String mPartB;
private String mPartC;
public static class Builder{
Product mProduct;
public Builder() {
this.mProduct = new Product();
}
public Builder buildPartA(String partA) {
this.mProduct.mPartA = partA;
return this;
}
public Builder buildPartB(String partB) {
this.mProduct.mPartB = partB;
return this;
}
public Builder buildPartC(String partC) {
this.mProduct.mPartC = partC;
return this;
}
public Product build() {
return mProduct;
}
}
public static void main(String[] args) {
Product product = new Product.Builder()
.buildPartA("a")
.buildPartB("b")
.buildPartC("c")
.build();
}
}
4. 优点
封装性:使用建造者模式可以使客户端不必知道产品内部组成的细节。
建造者相互独立,容易扩展
便于控制细节风险:由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。
5. 缺点
产生多余的Builder对象,浪费内存
原型模式
1. 定义
原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
2. 使用场景
- 资源优化场景:类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
- 性能和安全要求的场景:通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
- 一个对象多个修改者的场景:一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
3. 实现
基类需要实现Cloneable接口:
public abstract class Prototype implements Cloneable {
}
子类的代码如下,主要实现了clone方法:
public class ConcretePrototype {
@Override
protected Object clone() {
ConcretePrototype cp = null;
try {
cp = (ConcretePrototype) super.clone();
} catch (Exception e) {
e.printStackTrace();
}
return cp;
}
}
4. 优点
- 性能优良:原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
5. 缺点
- 逃避构造函数的约束(clone的时候,构造函数不会执行):这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的,需要在实际应用时考虑。
- 需要注意类中可变引用成员浅拷贝带来的问题,可以考虑使用深拷贝(对成员也进行一次clone,并且赋值给新对象的成员)
- 类成员中有final类型的成员,这个类不能进行clone。(此时应该去掉final关键字)
代理模式
1. 定义
代理模式:为其他对象提供一种代理以控制对这个对象的访问。
代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式(如状态模式、策略模式、访问者模式)本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制。
静态代理通用UML:
动态代理通用UML:
2. 使用场景
- 当无法或者不想直接访问某个对象或者访问某个对象存在困难的时候都可以通过代理来访问。
- Android中的IPC机制就使用到了代理模式。
- 面向切面编程(AOP)中,核心就是动态代理。
3. 实现
3.1 普通的静态代理
抽象主题类:
public interface Subject {
void request();
}
具体主题类,也是被代理对象:
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("业务逻辑");
}
}
主题类的代理类:
public class SubjectProxy implements Subject {
private Subject mSubject;
public SubjectProxy(Subject subject) {
mSubject = subject;
}
@Override
public void request() {
//代理中可以增加一些自定义的逻辑,这是面向切面编程的雏形
before();
mSubject.request();
after();
}
public void before() {
System.out.println("before");
}
public void after() {
System.out.println("after");
}
}
场景类:
public class Client {
public static void main(String[] args) {
Subject subject = new SubjectProxy(new RealSubject());
subject.request();
}
}
3.2 动态代理
动态代理的InvocationHandler实现类:
public class SubjectIH implements InvocationHandler {
private Object mTarget;
public SubjectIH(Object target) {
mTarget = target;
}
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
before();
Object res = method.invoke(this.mTarget, args);
after();
if ("request".equals(method.getName())) {
System.out.println("request!!");
}
return res;
}
public void before() {
System.out.println("before");
}
public void after() {
System.out.println("after");
}
}
场景类:
public class Client {
public static void main(String[] args) {
RealSubject subject = new RealSubject();
//运行的时候动态产生一个代理对象,在实现阶段不用关心代理谁
Subject proxy = (Subject) Proxy.newProxyInstance(
subject.getClass().getClassLoader(),
new Class[]{Subject.class},
new SubjectIH(subject));
proxy.request();
}
}
4. 优点
- 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
- 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了保护目标对象的作用。
5. 缺点
- 由于在客户端和真实对象之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢;
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
适配器模式
1. 定义
适配器模式:把一个类的接口变换为客户端所期待的另一种接口,从而解决接口不兼容问题。
适配器模式分为:类适配器模式、对象适配器模式,UML如下所示:
2. 使用场景
- 系统需要使用现有的类,而此类的接口不符合系统的需要,即接口不兼容。
- 想要建立一个可以重复使用的类,用于一些彼此之间没有太大关联的一些类,包括一些将来可能会被引进的类一起工作。
- 需要一个统一的输出接口,而输入端的类型不可预知。
- Android中ListView、RecycleView等控件的设计就采用了适配器模式。
3. 实现
3.1 类适配器
原角色:
public class Adaptee {
//原有的业务逻辑
public void volt220() {
System.out.println("220V电压");
}
}
目标角色:
public interface Target {
void volt5();
}
public class ConcreteTarget implements Target {
@Override
public void volt5() {
System.out.println("5V电压");
}
}
适配器角色:
public class Adapter extends Adaptee implements Target {
@Override
public void volt5() {
System.out.println("5V电压");
}
public void volt220() {
super.volt220();
}
}
场景类:
public class Client {
public static void main(String[] args) {
Target t1 = new ConcreteTarget();
t1.volt5();
Target t2 = new Adapter();
t2.volt5();
}
}
3.2 对象适配器(用得比较多)
适配器角色(核心不同时:适配器角色持有了原角色):
public class Adapter implements Target {
private Adaptee mAdaptee;
public Adapter(Adaptee adaptee) {
mAdaptee = adaptee;
}
@Override
public void volt5() {
System.out.println("5V电压");
}
public void vol220() {
mAdaptee.volt220();
}
}
场景类:
public class Client {
public static void main(String[] args) {
Target t1 = new ConcreteTarget();
t1.volt5();
Target t2 = new Adapter(new Adaptee());
t2.volt5();
}
}
4. 优点
- 提高代码复用性:更好地复用现有的类,而并不需要大范围地重构代码。
- 更好的扩展性。
5. 缺点
- 适配器模式过多地使用,系统会比较凌乱,不易把握。
- 基于以上的观点,在重构能够解决的前提下,尽量不要使用适配器模式。
享元模式
1. 定义
享元模式:使用共享对象可以有效地支持大量的细粒度的对象。
共享技术:防止对象分配过多而造成系统的性能问题,甚至OOM。
-
细粒度:区分一个对象的内部状态和外部状态,通过外部状态唯一标识一个对象,防止重复对象更多。
- 内部状态:可以共享的信息,存储在对象内部并且不会随环境改变。
- 外部状态:不可以共享的信息,随着环境的改变而改变,是对象的索引值。
2. 使用场景
- 系统中存在着大量相似的对象。
- 细粒度的对象都具备外部状态和内部状态,外部状态不随环境而变化。
- 需要缓冲池的场合。
3. 实现
抽象与具体的享元角色如下:
public abstract class Flyweight {
//内部状态,可以共享的信息,存储在对象内部并且不会随环境改变
private String mIntrinsic;
//外部状态,不可以共享的信息,随着环境的改变而改变,是对象的索引值
protected final String mExtrinsic;
public Flyweight(String extrinsic) {
mExtrinsic = extrinsic;
}
}
public class ConcreteFlyweight extends Flyweight {
public ConcreteFlyweight(String extrinsic) {
super(extrinsic);
}
}
享元工厂如下:
public class FlyweightFactory {
//池容器
private static Map sPool = new HashMap<>();
/**
* 通过外部状态结合池来管理对象
* @param extrinsic
* @return
*/
public static Flyweight getFlyweight(String extrinsic) {
Flyweight result = null;
if (sPool.containsKey(extrinsic)) {
result = sPool.get(extrinsic);
} else {
result = new ConcreteFlyweight(extrinsic);
sPool.put(extrinsic, result);
}
return result;
}
}
4. 优点
- 减少对象的创建,降低应用程序的内存占用,增强程序的性能,防止OOM。
5. 缺点
- 为了实现共享,需要剥离对象的外部、内部状态,使得系统更加复杂。
- 外部状态具有固化性,不随着环境、内部状态的变化而变化,否则共享池就会出现混乱。
装饰模式
1. 定义
装饰模式:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。装饰模式中,通过装饰者给被装饰者扩展了功能。
2. 使用场景
- 需要扩展一个类的功能,或给一个类增加附加功能。
- 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
- 需要为一批的兄弟类进行改装或加装功能,当然是首选装饰模式。
- Java中的IO流的架构。
- Android中的Context家族。
3. 实现
被装饰者的抽象及其实现:
public abstract class Component {
public abstract void operate();
}
public class ConcreteComponent extends Component {
@Override
public void operate() {
System.out.println("operate");
}
}
装饰者的抽象:
public abstract class Decorator extends Component {
private Component mComponent;
public Decorator(Component component) {
mComponent = component;
}
@Override
public void operate() {
//委托给被修饰者去执行对应的方法
this.mComponent.operate();
}
}
装饰者的具体实现:
public class ConcreteDecorator1 extends Decorator{
public ConcreteDecorator1(Component component) {
super(component);
}
@Override
public void operate() {
decorate1();
super.operate();
}
public void decorate1() {
System.out.println("修饰方法1");
}
}
public class ConcreteDecorator2 extends Decorator {
public ConcreteDecorator2(Component component) {
super(component);
}
@Override
public void operate() {
decorate2();
super.operate();
}
public void decorate2() {
System.out.println("修饰方法2");
}
}
场景类:
public class Client {
public static void main(String[] args) {
Component component = new ConcreteDecorator1(new ConcreteDecorator2(new ConcreteComponent()));
component.operate();
}
}
运行结果:
修饰方法1
修饰方法2
operate
4. 优点
- 装饰模式可以动态地扩展一个实现类的功能。
- 装饰模式是继承关系的一个替代方案。不管装饰多少层,最终返回的对象还是抽象类。
- 装饰类和被装饰类可以独立发展,而不会相互耦合。
5. 缺点
- 多层的装饰是比较复杂的。
- 应该尽量减少装饰类的数量,以便降低系统的复杂度。
外观模式
1. 定义
门面模式,也叫外观模式,是一种比较常用的封装模式:要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。
2. 使用场景
- 为一个复杂的模块或子系统提供一个供外界访问的接口。
- 子系统相对独立。外界对子系统的访问只要黑箱操作即可。
- 预防低水平人员带来的风险扩散。降低个人代码质量对整体项目的影响风险,规定低水平人员只能在指定的子系统中开发,然后再提供门面接口进行访问操作。
- Android中第三方SDK的设计,比如AndFix、Tinker、Gilde等就采用了外观模式。
3. 实现
子系统可以是一个或者很多个类,下面以三个类作为子系统的一个例子(三个类属于近邻,处理相关的业务,应该被认为是一个子系统的不同逻辑处理模块。并且子系统不知道门面角色的存在):
public class ClassA {
public void doSomethingA() {
System.out.println("doSomethingA");
}
}
public class ClassB {
public void doSomethingB() {
System.out.println("doSomethingB");
}
}
public class ClassC {
public void doSomethingC() {
System.out.println("doSomethingC");
}
}
门面角色,客户端可以调用这个角色的方法。此角色知晓子系统的所有功能和责任。一般情况下,本角色会将所有从客户端发来的请求委派到相应的子系统去,也就说该角色没有实际的业务逻辑,只是一个委托类。
public class Facade {
ClassA mA = new ClassA();
ClassB mB = new ClassB();
ClassC mC = new ClassC();
public void methodA() {
mA.doSomethingA();
}
public void methodB() {
mB.doSomethingB();
}
public void methodC() {
mC.doSomethingC();
}
}
4. 优点
- 减少系统的相互依赖。如果不使用门面模式,外界访问直接深入到子系统内部,相互之间是一种强耦合关系。门面模式解决了该问题,所有的依赖都是对门面对象的依赖,与子系统无关。
- 提高了灵活性。依赖减少了,灵活性自然提高了。不管子系统内部如何变化,只要不影响到门面对象就行。
- 提高安全性。门面角色可以对子系统的访问进行控制。
5. 缺点
- 门面模式最大的缺点就是不符合开闭原则。出现问题的时候唯一能做的一件事就是修改门面角色的代码,而不是通过继承等手段进行扩展,这个风险相当大。
桥接模式
1. 定义
桥接模式也叫桥梁模式,通过抽象角色引用实现角色,将抽象和实现解耦,使得两者可以独立地变化。其中抽象和实现可以理解为两个不同的维度。
2. 使用场景
- 一个类存在两个独立变化的维度,并且两个维度都需要扩展。
- 不希望或不适用使用继承的场景。
- 接口或抽象类不稳定的场景,明知接口不稳定还想通过实现或继承来实现业务需求,那是得不偿失的。
- 重用性要求较高的场景,设计的颗粒度越细,则被重用的可能性就越大,而采用继承则受父类的限制,不可能出现太细的颗粒度。
3. 实现
这里以冲咖啡为例子进行代码实现,实现大杯小杯、加糖不加糖两个维度的搭配。
抽象化角色——咖啡:
/**
* Abstraction是抽象化角色,保持实现化角色的引用
* 这里的Abstraction代表咖啡
*/
public abstract class Abstraction {
//抽象化角色,保持实现化角色的引用,是桥接模式的核心
//这里的实现化角色代表咖啡的调料
private Implementor mImplementor;
public Abstraction(Implementor implementor) {
mImplementor = implementor;
}
public Implementor getImplementor() {
return mImplementor;
}
/**
* 冲咖啡,具体实现由子类实现
*/
public abstract void operator();
}
修正抽象化角色——大杯、小杯的咖啡:
/**
* RefineAbstraction是修正抽象化角色,通过引用实现化角色Implementor对抽象化角色Abstraction进行修正
* RefineAbstraction1是咖啡的实现类,代表大杯的咖啡
*/
public class RefineAbstraction1 extends Abstraction {
public RefineAbstraction1(Implementor implementor) {
super(implementor);
}
@Override
public void operator() {
System.out.println("大杯的" + this.getImplementor().implement() + "咖啡");
}
}
public class RefineAbstraction2 extends Abstraction {
public RefineAbstraction2(Implementor implementor) {
super(implementor);
}
@Override
public void operator() {
System.out.println("小杯的" + this.getImplementor().implement() + "咖啡");
}
}
实现化角色——调料:
/**
* Implementor是实现化角色
* 这里代表咖啡的调料,具体调料由实现类实现
*/
public interface Implementor {
String implement();
}
具体实现化角色——加糖、不加糖:
/**
* ConcreteImplementor具体的实现化角色
* ConcreteImplementor咖啡的调料的实现类,代表加糖
*
*/
public class ConcreteImplementor1 implements Implementor {
@Override
public String implement() {
return "加糖";
}
}
public class ConcreteImplementor2 implements Implementor {
@Override
public String implement() {
return "不加糖";
}
}
场景类:
public class Client {
public static void main(String[] args) {
Implementor i1 = new ConcreteImplementor1();//加糖
Implementor i2 = new ConcreteImplementor1();//不加糖
Abstraction a1 = new RefineAbstraction1(i1);//大杯、加糖
Abstraction a2 = new RefineAbstraction1(i2);//大杯、不加糖
Abstraction a3 = new RefineAbstraction2(i1);//小杯、加糖
Abstraction a4 = new RefineAbstraction2(i2);//小杯、不加糖
a1.operator();
a2.operator();
a3.operator();
a4.operator();
}
}
运行结果:
大杯的加糖咖啡
大杯的加糖咖啡
小杯的加糖咖啡
小杯的加糖咖啡
4. 优点
- 抽象和实现分离。解决了继承的缺点,桥接模式实现了不受抽象的约束,不用再绑定在一个固定的抽象层次上。
- 优秀的扩充能力。抽象和实现都可以独立扩展。
- 实现细节对客户透明。客户不用关心细节的实现,它已经由抽象层通过聚合关系完成了封装。
5. 缺点
- 桥接模式的引入会增加系统的理解与设计难度。由于聚合关联关系建立在抽象层,要求开发者针对抽象设计和编程。
- 实际中不容易发现可以使用桥接模式的地方。桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
组合模式
1. 定义
组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
2. 使用场景
- 维护和展示部分-整体关系的场景,如树形菜单、关系型数据库、文件和文件夹管理。
- 从一个整体中能够独立出部分模块或功能的场景。
- Android中View与ViewGroup的设计就采用了组合模式。
3. 实现
这里以文件夹、文件为例子来实现(透明的)组合模式。
抽象构件角色:
/**
* 抽象构件角色:定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性
* 例子:文件夹、文件的抽象
*/
public abstract class Component {
/**
* 结点名。例子:文件夹/文件的名字
*/
protected String mName;
public Component(String name) {
mName = name;
}
/**
* 业务逻辑
*/
public abstract void doSomething();
/**
* 添加结点。例子:添加文件夹/文件
* @param component
*/
public abstract void addChildren(Component component);
/**
* 删除结点。例子:删除文件夹/文件
* @param component
*/
public abstract void removeChildren(Component component);
/**
* 获取所有子结点。例子:获取所有子文件夹/文件
* @return
*/
public abstract List getChildrens();
}
树枝构件:
/**
* 树枝构件:树枝对象,它的作用是组合树枝节点和叶子节点形成一个树形结构。
* 例子:文件夹
*/
public class Composite extends Component {
private List mChildrens = new ArrayList<>();
public Composite(String name) {
super(name);
}
@Override
public void doSomething() {
System.out.println("文件夹:" + this.mName);
}
@Override
public void addChildren(Component component) {
this.mChildrens.add(component);
}
@Override
public void removeChildren(Component component) {
this.mChildrens.remove(component);
}
@Override
public List getChildrens() {
return this.mChildrens;
}
}
叶子构件:
/**
* 叶子构件:叶子对象,其下再也没有其他的分支,也就是遍历的最小单位。
* 例子:文件
*/
public class Leaf extends Component {
public Leaf(String name) {
super(name);
}
@Override
public void doSomething() {
System.out.println("文件:" + this.mName);
}
@Override
public void addChildren(Component component) {
throw new UnsupportedOperationException("叶子结点不支持该操作");
}
@Override
public void removeChildren(Component component) {
throw new UnsupportedOperationException("叶子结点不支持该操作");
}
@Override
public List getChildrens() {
throw new UnsupportedOperationException("叶子结点不支持该操作");
}
}
场景类:
public class Client {
public static void main(String[] args) {
Component c1 = new Composite("C盘");
Component c2 = new Composite("文件夹A");
Component c3 = new Leaf("文件A");
Component c4 = new Leaf("文件B");
c1.addChildren(c2);
c1.addChildren(c3);
c2.addChildren(c4);
}
}
4. 优点
- 高层模块调用简单。高层模块不必关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。
- 节点自由增加、删除。非常容易扩展,符合开闭原则,对以后的维护非常有利。
5. 缺点
- 普通的组合模式不符合依赖倒置原则,而透明的组合模式是符合的。
- 因为构件的抽象都是一样的,所以透明的组合模式在新增构件的时候不好对构件类型进行限制,容易产生运行时的异常。
观察者模式
1. 定义
观察者模式也叫发布订阅模式/模型,在项目中常用:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
2. 使用场景
- 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。
- 事件多级触发场景。
- 跨系统的消息交换场景,如消息队列的处理机制。
- Android中的列表控件、消息处理机制、广播、RxJava(扩展的观察者模式)等等
3. 实现(实际开发中一般是使用java.util包下面的Observer、Observable)
被观察者:
public abstract class Subject {
private Vector mObservers = new Vector<>();
public void addObserver(Observer o) {
this.mObservers.add(o);
}
public void removeObserver(Observer o) {
this.mObservers.remove(o);
}
public void notifyObservers() {
for (Observer o : this.mObservers) {
o.update();
}
}
}
public class ConcreteSubject extends Subject {
public void doSomething() {
System.out.println("doSomething");
this.notifyObservers();
}
}
观察者:
public interface Observer {
void update();
}
public class ConcreteObserver implements Observer {
@Override
public void update() {
System.out.println("收到消息");
}
}
场景类:
public class Client {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
Observer observer1 = new ConcreteObserver();
Observer observer2 = new ConcreteObserver();
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.doSomething();
}
}
运行结果:
doSomething
收到消息
收到消息
4. 优点
- 观察者和被观察者之间是抽象耦合。观察者和被观察者的扩展比较方便。
- 建立一套触发机制。例如建立一条触发链。
5. 缺点
- 开发效率问题。一个被观察者,多个观察者,开发和调试就会比较复杂。
- 运行效率问题。多个观察者、多级触发等情况下,因为在Java默认是顺序执行,所以一个观察者卡壳,会影响整体的执行效率,这时候推荐考虑采用异步的方式。
中介者模式
1. 定义
中介者模式:用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
2. 使用场景
- 当对象之间的交互很多而且每个对象的行为操作都依赖彼此时,为了防止在修改一个对象的行为同时涉及修改很多其他对象的行为。
- 出现了网状结构导致出现紧密耦合的时候,中介者模式可以将多对多变成一对多的关系,实现星形结构,降低系统复杂性,提高系统扩展性。
- Android中Activity就充当了中介者的角色。
- MVC、MVP等框架。
3. 实现
下面以一个完整的买房子的过程为例子,介绍中介者模式的简单实现。
中介者及其实现:
/**
* 中介者:协调各个同事角色,实现协作
*/
public abstract class Mediator {
protected ConcreteColleague1 mColleague1;
protected ConcreteColleague2 mColleague2;
public abstract void doSomething1();
public abstract void doSomething2();
public ConcreteColleague1 getColleague1() {
return mColleague1;
}
public void setColleague1(ConcreteColleague1 colleague1) {
mColleague1 = colleague1;
}
public ConcreteColleague2 getColleague2() {
return mColleague2;
}
public void setColleague2(ConcreteColleague2 colleague2) {
mColleague2 = colleague2;
}
}
/**
* 例子:房产中介
*/
public class ConcreteMediator extends Mediator {
@Override
public void doSomething1() {
System.out.println("房产中介:帮你找到卖家");
//通知卖家整理好房子
this.mColleague2.selfMethod2();
this.mColleague2.depMethod2();
}
@Override
public void doSomething2() {
System.out.println("房产中介:帮你找到买家");
this.mColleague1.selfMethod1();
}
}
同事类及其实现:
/**
* 同事类
*/
public abstract class Colleague {
protected Mediator mMediator;
public Colleague(Mediator mediator) {
mMediator = mediator;
}
}
/**
* 买家
*/
public class ConcreteColleague1 extends Colleague {
public ConcreteColleague1(Mediator mediator) {
super(mediator);
}
/**
* 自发行为:同事本身的行为,例如改变对象自身的状态,处理自己的行为
*/
public void selfMethod1() {
System.out.println("买家:付款,购买房子");
}
/**
* 依赖行为:自己不能完成的业务,必须依赖中介者才能完成的行为
*/
public void depMethod1() {
System.out.println("买家:嘿,房产中介,我要买房");
//通知中介者,我要买房
this.mMediator.doSomething1();
}
}
/**
* 卖家
*/
public class ConcreteColleague2 extends Colleague {
public ConcreteColleague2(Mediator mediator) {
super(mediator);
}
public void selfMethod2() {
System.out.println("卖家:嘿,房子整理中……房子整理好了");
}
public void depMethod2() {
System.out.println("卖家:嘿,房产中介,帮我告诉买家,房子整理好了,房子可以出售");
//通知中介者,房子可以出售
this.mMediator.doSomething2();
}
}
场景类:
public class Client {
public static void main(String[] args) {
Mediator mediator = new ConcreteMediator();
ConcreteColleague1 colleague1 = new ConcreteColleague1(mediator);
ConcreteColleague2 colleague2 = new ConcreteColleague2(mediator);
mediator.setColleague1(colleague1);
mediator.setColleague2(colleague2);
//买房子
colleague1.depMethod1();
}
}
运行结果:
买家:嘿,房产中介,我要买房
房产中介:帮你找到卖家
卖家:嘿,房子整理中……房子整理好了
卖家:嘿,房产中介,帮我告诉买家,房子整理好了,房子可以出售
房产中介:帮你找到买家
买家:付款,购买房子
4. 优点
- 中介者模式的优点就是减少类间的依赖,把原有的一对多的依赖变成了一对一的依赖。同事类只依赖中介者,减少了依赖,当然同时也降低了类间的耦合。
- 降低系统复杂度,提高系统的可扩展性。
5. 缺点
- 中介者会膨胀得很大,而且逻辑复杂,原本N个对象直接的相互依赖关系转换为中介者和同事类的依赖关系,同事类越多,中介者的逻辑就越复杂。
- 中介者模式容易被误用。如果几个类之间的依赖关系并不复杂,使用中介者模式反而会使得逻辑结构变复杂。
模板方法模式
1. 定义
模板方法模式:定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
2. 使用场景
- 多个子类有公有的方法,并且逻辑基本相同时。
- 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
- 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数(Hook Method)约束其行为。
- Android中,抽取BaseActivity就使用到了模板方法模式。
3. 实现
抽象模板:
/**
* 抽象模板
*/
public abstract class AbstractClass {
/**
* 基本方法:基本方法也叫做基本操作,是由子类实现的方法,并且在模板方法被调用。
*/
protected abstract void method1();
protected abstract void method2();
/**
* 钩子函数:子类可以通过钩子函数(Hook Method)约束父类的行为。
*
* @return
*/
protected abstract boolean hookMethod();
/**
* 模板方法:可以有一个或几个,一般是一个具体方法,也就是一个框架,实现对基本方法的调度,完成固定的逻辑。
* 为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。
*/
public final void templateMethod() {
method1();
if (hookMethod()) {
method2();
}
}
}
具体模板:
/**
* 具体模板:实现父类所定义的一个或多个抽象方法,也就是父类定义的基本方法在子类中得以实现。
*/
public class ConcreteClass1 extends AbstractClass {
@Override
protected void method1() {
System.out.println("ConcreteClass1 : method1");
}
@Override
protected void method2() {
System.out.println("ConcreteClass1 : method2");
}
@Override
protected boolean hookMethod() {
return true;
}
}
public class ConcreteClass2 extends AbstractClass {
@Override
protected void method1() {
System.out.println("ConcreteClass2 : method1");
}
@Override
protected void method2() {
System.out.println("ConcreteClass2 : method2");
}
@Override
protected boolean hookMethod() {
return false;
}
}
场景类:
public class Client {
public static void main(String[] args) {
AbstractClass c1 = new ConcreteClass1();
AbstractClass c2 = new ConcreteClass2();
c1.templateMethod();
c2.templateMethod();
}
}
运行结果(注意钩子函数对父类行为的约束):
ConcreteClass1 : method1
ConcreteClass1 : method2
ConcreteClass2 : method1
4. 优点
- 封装不变部分,扩展可变部分。把认为是不变部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展。
- 行为由父类控制,子类实现。基本方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开闭原则。
- 提取公共部分代码,便于维护。
5. 缺点
- 子类对父类产生了影响:模板方法模式中抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响了父类的结果。
- 增加阅读难度:在复杂的项目中,会带来代码阅读的难度,而且也会让新手产生不适感。
策略模式
1. 定义
策略模式:定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。
2. 使用场景
- 多个类只有在算法或行为上稍有不同的场景。
- 算法需要自由切换的场景。
- 需要屏蔽算法规则的场景。
- 同一抽象有多个子类,而且需要使用if-else、switch-case来选择具体的子类的时候。
- Android中,设计图片加载框架的时候,可以使用策略模式来选择加载策略。
3. 实现
策略的抽象及其实现:
/**
* 抽象策略角色
* 通常为接口,定义每个策略或算法必须具有的方法和属性。
*/
public interface Strategy {
void doSomething();
}
/**
* 具体策略角色
* 实现抽象策略中的操作,该类含有具体的算法。
*/
public class ConcreteStrategy1 implements Strategy {
@Override
public void doSomething() {
System.out.println("执行策略1");
}
}
public class ConcreteStrategy2 implements Strategy {
@Override
public void doSomething() {
System.out.println("执行策略2");
}
}
上下文角色:
/**
* Context封装角色:它也叫做上下文角色
* 起承上启下封装作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。
*/
public class Context {
private Strategy mStrategy;
public Context(Strategy strategy) {
this.mStrategy = strategy;
}
public void setStrategy(Strategy strategy) {
this.mStrategy = strategy;
}
public void doSomething() {
this.mStrategy.doSomething();
}
}
场景类:
public class Client {
public static void main(String[] args) {
Strategy s1 = new ConcreteStrategy1();
Strategy s2 = new ConcreteStrategy2();
Context context = new Context(s1);
//执行策略1
context.doSomething();
//替换策略,然后执行策略2
context.setStrategy(s2);
context.doSomething();
}
}
运行结果:
执行策略1
执行策略2
4. 优点
- 封装性好,使用简单。
- 算法可以自由切换。
- 避免使用多重条件判断。
- 扩展性良好。扩展只需要增加策略的具体实现类,替换策略。符合开闭原则。
5. 缺点
- 随着策略的增加,策略类数量增多。
- 所有的策略类都需要对外暴露,与迪米特原则相违背。
命令模式
1. 定义
命令模式是一个高内聚的模式,其定义为:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
2. 使用场景
- 需要抽象出待执行的动作,然后以参数的形式提供出来。替代过程设计中的回调机制。
- 在不同的时刻指定、排列和执行请求。
- 需要支持事务操作、取消功能、日志功能。
- Android中的事件传递机制就使用到了命令模式。
3. 实现
以项目经理分别下发写代码、设计UI两条命令给程序员、视觉设计师为例子来实现命令模式。
接收者角色及其实现:
/**
* 接收者角色:该角色就是干活的角色,命令传递到这里是应该被执行的。
* 例子:程序员、视觉设计师的基类
*/
public abstract class Receiver {
public abstract void doSomething();
}
/**
* 例子:程序员
*/
public class ConcreteReceiver1 extends Receiver{
@Override
public void doSomething() {
System.out.println("写代码");
}
}
/**
* 例子:视觉设计师
*/
public class ConcreteReceiver2 extends Receiver{
@Override
public void doSomething() {
System.out.println("设计UI");
}
}
命令角色及其实现:
/**
* 命令角色:需要执行的所有命令都在这里声明。
*/
public abstract class Command {
protected Receiver mReceiver;
public Command(Receiver receiver) {
this.mReceiver = receiver;
}
public abstract void execute();
}
/**
* 例子:写代码的命令,下发给程序员
*/
public class ConcreteCommand1 extends Command {
public ConcreteCommand1() {
super(new ConcreteReceiver1());
}
public ConcreteCommand1(Receiver receiver) {
super(receiver);
}
@Override
public void execute() {
this.mReceiver.doSomething();
}
}
/**
* 例子:设计UI的命令,下发给视觉设计师
*/
public class ConcreteCommand2 extends Command {
public ConcreteCommand2() {
super(new ConcreteReceiver2());
}
public ConcreteCommand2(Receiver receiver) {
super(receiver);
}
@Override
public void execute() {
this.mReceiver.doSomething();
}
}
调用者角色:
/**
* 调用者角色:接收客户端的命令,并执行命令。
* 例子:项目经理
*/
public class Invoker {
private Command mCommand;
public void setCommand(Command command) {
this.mCommand = command;
}
public void action() {
this.mCommand.execute();
}
}
场景类:
public class Client {
public static void main(String[] args) {
Invoker invoker = new Invoker();
invoker.setCommand(new ConcreteCommand1());
invoker.action();
invoker.setCommand(new ConcreteCommand2());
invoker.action();
}
}
运行结果:
写代码
设计UI
4. 优点
- 类间解耦。调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需调用Command抽象类的execute方法就可以,不需要了解到底是哪个接收者执行。
- 可扩展性。Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合。
- 命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式,则可以减少Command子类的膨胀问题。
5. 缺点
- 命令模式如果有N个命令Command的子类就有N个,类数量膨胀得非常大。
备忘录模式
1. 定义
备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。备忘录模式就是一个对象的备份模式,提供了一种程序数据的备份方法。
2. 使用场景
- 需要保存和恢复数据的相关状态场景。例如游戏开发中的存档功能。
- 需要提供一个可回滚(rollback)的操作。比如Word中的CTRL+Z组合键,IE浏览器中的后退按钮,文件管理器上的backspace键等。
- 需要监控的副本场景中。例如要监控一个对象的属性,但是监控又不应该作为系统的主业务来调用,它只是边缘应用,即使出现监控不准、错误报警也影响不大,因此一般的做法是备份一个主线程中的对象,然后由分析程序来分析。
- 数据库连接的事务管理就是用的备忘录模式,例如JDBC驱动中事务的实现是用备忘录模式。
- Android中,Activity状态的保存就使用了备忘录模式。
3. 实现
发起人角色:
/**
* 发起人角色:记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。
*/
public class Originator {
private String mState = "";
public String getState() {
return this.mState;
}
public void setState(String state) {
this.mState = state;
}
/**
* 创建一个备忘录
* @return
*/
public Memento createMemento() {
return new Memento(this.mState);
}
/**
* 恢复一个备忘录
* @param memento
*/
public void restoreMemento(Memento memento) {
this.setState(memento.getState());
}
}
备忘录角色:
/**
* 备忘录角色:负责存储发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。
*/
public class Memento {
private String mState;
public Memento(String state) {
this.mState = state;
}
public String getState() {
return mState;
}
}
备忘录管理员角色:
/**
* 备忘录管理员角色:对备忘录进行管理、保存和提供备忘录。实际开发中这个管理员角色一般会比较复杂。
*/
public class Caretaker {
private Memento mMemento;
public void setMemento(Memento memento) {
this.mMemento = memento;
}
public Memento getMemento() {
return this.mMemento;
}
}
场景类:
public class Client {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.setState("状态1");
printState(originator);
System.out.println("创建一个备忘录");
caretaker.setMemento(originator.createMemento());
System.out.println("修改状态");
originator.setState("状态2");
printState(originator);
System.out.println("恢复一个备忘录");
originator.restoreMemento(caretaker.getMemento());
printState(originator);
}
public static void printState(Originator originator) {
System.out.println("当前的状态:" + originator.getState());
}
}
运行结果:
当前的状态:状态1
创建一个备忘录
修改状态
当前的状态:状态2
恢复一个备忘录
当前的状态:状态1
4. 优点
- 为用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
- 实现了信息的封装,使得用户不需要关心状态的保存细节。
5. 缺点
- 资源消耗。如果发起者角色类成员较多,每一次创建备忘录都会增加内存消耗。
状态模式
1. 定义
当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。状态模式的核心是封装,状态的变更引起了行为的变更,从外部看起来就好像这个对象对应的类发生了改变一样。
2. 使用场景
- 行为随状态改变而改变的场景。这也是状态模式的根本出发点,例如权限设计,人员的状态不同即使执行相同的行为结果也会不同,在这种情况下需要考虑使用状态模式。
- 条件、分支判断语句的替代者。在程序中大量使用switch语句或者if判断语句会导致程序结构不清晰,逻辑混乱,使用状态模式可以很好地避免这一问题,它通过扩展子类实现了条件的判断处理。
- Android中的硬件操作,例如WIFI、蓝牙等大多都使用了状态模式。
- Android中的权限机制、登录功能设计等都使用了状态模式。
3. 实现
抽象状态角色:
/**
* 抽象状态角色:接口或抽象类,负责对象状态定义,并且封装环境角色以实现状态切换。
*/
public abstract class State {
//抽象环境中声明一个环境角色,提供各个状态类自行访问。
protected Context mContext;
public void setContext(Context context) {
this.mContext = context;
}
//提供所有状态的抽象行为,由各个实现类实现。
public abstract void handle1();
public abstract void handle2();
@Override
public String toString() {
return this.getClass().getSimpleName();
}
}
具体状态角色:
/**
* 具体状态角色:每一个具体状态必须完成两个职责:
* 1. 本状态的行为管理以及趋向状态处理,通俗地说,就是本状态下要做的事情。
* 2. 以及本状态如何过渡到其他状态。
*/
public class ConcreteState1 extends State{
@Override
public void handle1() {
System.out.println("ConcreteState1下必须处理的逻辑");
}
@Override
public void handle2() {
//本状态不能处理,需要切换状态,并且委托给下一个状态去处理
System.out.println("当前状态" + this + "不能处理,切换到"+Context.STATE2);
this.mContext.setCurrentState(Context.STATE2);
this.mContext.handle2();
}
}
public class ConcreteState2 extends State{
@Override
public void handle1() {
System.out.println("当前状态" + this + "不能处理,切换到"+Context.STATE1);
this.mContext.setCurrentState(Context.STATE1);
this.mContext.handle1();
}
@Override
public void handle2() {
System.out.println("ConcreteState2下必须处理的逻辑");
}
}
环境角色:
/**
* 环境角色:定义客户端需要的接口,并且负责具体状态的切换。
*/
public class Context {
//定义所有状态
public static final State STATE1 = new ConcreteState1();
public static final State STATE2 = new ConcreteState2();
//当前状态
private State mCurrentState;
public Context() {
//初始化
STATE1.setContext(this);
STATE2.setContext(this);
this.mCurrentState = STATE1;
}
public State getCurrentState() {
return this.mCurrentState;
}
public void setCurrentState(State state) {
this.mCurrentState = state;
}
//行为委托给State去处理
public void handle1() {
this.mCurrentState.handle1();
}
public void handle2() {
this.mCurrentState.handle2();
}
}
场景类:
public class Client {
public static void main(String[] args) {
Context context = new Context();
System.out.println("当前状态:" + context.getCurrentState());
context.handle1();
System.out.println("当前状态:" + context.getCurrentState());
context.handle2();
System.out.println("当前状态:" + context.getCurrentState());
}
}
运行结果:
当前状态:ConcreteState1
ConcreteState1下必须处理的逻辑
当前状态:ConcreteState1
当前状态ConcreteState1不能处理,切换到ConcreteState2
ConcreteState2下必须处理的逻辑
当前状态:ConcreteState2
4. 优点
- 结构清晰。避免了过多的switch...case或者if...else语句的使用,避免了程序的复杂性,提高系统的可维护性。
- 遵循设计原则。很好地体现了开闭原则和单一职责原则,每个状态都是一个子类,需要要增加状态就要增加子类,你要修改状态,你只修改一个子类就可以了。
- 封装性非常好。这也是状态模式的基本要求,状态变换放置到类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变换。
5. 缺点
- 状态模式的缺点是状态过多的时候,状态子类会太多,也就是类膨胀。
责任链模式
1. 定义
使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
责任链模式的重点是在“链”上,由一条链去处理相似的请求在链中决定谁来处理这个请求,并返回相应的结果。
2. 使用场景
- 多个对象可以处理同一个请求,但是具体由哪个对象处理需要在运行时动态决定。
- 在处理对象不明确的前提下,向多个对象提交一个请求。
- 需要动态指定一组对象来处理请求。
- OkHttp的核心-拦截器就是使用了责任链模式
3. 实现
抽象处理者:
/**
* 抽象处理者
* 抽象的处理者实现三个职责:
* 1. 定义一个对外开放的请求的处理方法handleMessage模板方法(final)
* 2. 定义一个链的编排方法setNext,设置下一个处理者
* 3. 定义了具体的请求者必须实现的两个方法:定义自己能够处理的级别getHandlerLevel和具体的处理任务doSomething方法
*/
public abstract class Handler {
private Handler mNextHandler;
public final Response handleRequest(Request request) {
Response response;
//判断是否是自己的处理级别
if (request.getRequestLevel() == this.getHandlerLevel()) {
response = this.doSomething(request);
} else if (this.mNextHandler != null) {
//不属于自己的处理级别,交给下一个处理
response = this.mNextHandler.handleRequest(request);
} else {
//没有适当的处理者,业务自行处理
response = new Response("没有适当的处理者");
}
return response;
}
public void setNextHandler(Handler nextHandler) {
this.mNextHandler = nextHandler;
}
protected abstract int getHandlerLevel();
protected abstract Response doSomething(Request request);
}
具体处理者:
public class ConcreteHandler1 extends Handler {
@Override
protected int getHandlerLevel() {
return 1;
}
@Override
protected Response doSomething(Request request) {
return new Response(this.getClass().getSimpleName() + "处理了" + request.getRequest());
}
}
public class ConcreteHandler2 extends Handler {
@Override
protected int getHandlerLevel() {
return 2;
}
@Override
protected Response doSomething(Request request) {
return new Response(this.getClass().getSimpleName() + "处理了" + request.getRequest());
}
}
public class ConcreteHandler3 extends Handler {
@Override
protected int getHandlerLevel() {
return 3;
}
@Override
protected Response doSomething(Request request) {
return new Response(this.getClass().getSimpleName() + "处理了" + request.getRequest());
}
}
相关框架代码:
/**
* 请求
*/
public class Request {
//请求等级
private int mRequestLevel;
//请求参数
private String mRequest;
public Request(int requestLevel, String request) {
this.mRequestLevel = requestLevel;
this.mRequest = request;
}
public int getRequestLevel() {
return this.mRequestLevel;
}
public String getRequest() {
return this.mRequest;
}
}
/**
* 相应
*/
public class Response {
//相应内容
private String mResult;
public Response(String result) {
this.mResult = result;
}
public String getReslust() {
return this.mResult;
}
}
场景类:
public class Client {
public static void main(String[] args) {
Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();
Handler handler3 = new ConcreteHandler3();
//动态生成处理者的责任链
handler1.setNextHandler(handler2);
handler2.setNextHandler(handler3);
//发送请求A
Response response1 = handler1.handleRequest(new Request(1, "请求A"));
System.out.println(response1.getReslust());
//发送请求B
Response response2 = handler1.handleRequest(new Request(2, "请求B"));
System.out.println(response2.getReslust());
//发送请求C
Response response3 = handler1.handleRequest(new Request(3, "请求C"));
System.out.println(response3.getReslust());
}
}
运行结果:
ConcreteHandler1处理了请求A
ConcreteHandler2处理了请求B
ConcreteHandler3处理了请求C
4. 优点
- 责任链模式非常显著的优点是将请求和处理分开。请求者可以不用知道是谁处理的,处理者可以不用知道请求的全貌。
- 请求和处理解耦,提高了系统的灵活性。
5. 缺点
- 性能问题。每个请求都是从链头遍历到链尾,特别是在链比较长的时候,性能是一个非常大的问题。(观察者模式也有类似问题)
- 调试不很方便。特别是链条比较长,环节比较多的时候,由于采用了类似递归的方式,调试的时候逻辑可能比较复杂。
迭代器模式
1. 定义
迭代器模式:提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。
迭代器是为容器服务的。迭代器模式提供了遍历容器的方便性,容器只要管理增减元素就可以了,需要遍历时交由迭代器进行。
2. 使用场景
- 需要遍历一个容器对象的时候。
- 由于Java API已经提供了java.util.Iterator接口,并且基本的容器类已经实现了这个接口。换句话说,Java已经把迭代器模式融入到基本的API中,因此开发者不需要手写迭代器模式。
3. 实现
迭代器:
/**
* 抽象迭代器:抽象迭代器负责定义访问和遍历元素的接口
*/
public interface Iterator {
//遍历到下一个元素
Object next();
//是否已经遍历到尾部
boolean hasNext();
//删除当前指向的元素
boolean remove();
}
/**
* 具体迭代器
*/
public class ConcreteIterator implements Iterator {
private Vector mVector;
//定义当前游标
private int mCursor = 0;
public ConcreteIterator(Vector vector) {
this.mVector = vector;
}
@Override
public Object next() {
if (this.hasNext()) {
return this.mVector.get(this.mCursor++);
} else {
return null;
}
}
@Override
public boolean hasNext() {
if (this.mCursor == this.mVector.size()) {
return false;
} else {
return true;
}
}
@Override
public boolean remove() {
this.mVector.remove(this.mCursor);
return true;
}
}
容器:
/**
* 抽象容器:容器角色负责提供容器的基本操作、创建具体迭代器角色的接口,在Java中一般是iterator()方法。
*/
public interface Aggregate {
void add(Object o);
void remove(Object o);
Iterator iterator();
}
/**
* 具体容器
*/
public class ConcreteAggregate implements Aggregate {
private Vector mVector = new Vector();
@Override
public void add(Object o) {
this.mVector.add(o);
}
@Override
public void remove(Object o) {
this.mVector.remove(o);
}
@Override
public Iterator iterator() {
return new ConcreteIterator(this.mVector);
}
}
场景类:
public class Client {
public static void main(String[] args) {
Aggregate agg = new ConcreteAggregate();
agg.add("a");
agg.add("b");
agg.add("c");
Iterator iterator = agg.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
4. 优点
- 支持以不同的方式去遍历一个容器对象,也可以有多个遍历。
- 弱化了容器类与遍历算法之间的关系。
5. 缺点
- 类文件增加。
访问者模式
1. 定义
访问者模式:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
2. 使用场景
- 作为迭代器模式的扩充,业务规则要求遍历多个不同的对象,然后执行不同的操作,也就是针对访问的对象不同,执行不同的操作。这本身也是访问者模式出发点,迭代器模式只能访问同类或同接口的数据(避免使用instanceof)。
- 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作,也就说是用迭代器模式已经不能胜任的情景。
- 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。
- 访问者模式可以充当拦截器(Interceptor)角色。
3. 实现
抽象元素以及具体元素:
/**
* 抽象元素
* 接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的
*/
public abstract class Element {
public String mCommonProperty;
public Element(String commonProperty) {
mCommonProperty = commonProperty;
}
public abstract void accept(Visitor visitor);
}
/**
* 具体元素
* 实现accept方法,通常是visitor.visit(this),基本上都形成了一种模式了
*/
public class ConcreteElement1 extends Element {
public String mProperty1;
public ConcreteElement1(String commonProperty, String property) {
super(commonProperty);
this.mProperty1 = property;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class ConcreteElement2 extends Element {
public String mProperty2;
public ConcreteElement2(String commonProperty, String property) {
super(commonProperty);
this.mProperty2 = property;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
抽象访问者以及具体访问者:
/**
* 抽象访问者
* 抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法的参数定义哪些对象是可以被访问的
*/
public interface Visitor {
void visit(ConcreteElement1 element);
void visit(ConcreteElement2 element);
}
/**
* 具体访问者
* 影响访问者访问到一个类后该怎么干,要做什么事情。
*/
public class ConcreteVisitor1 implements Visitor {
@Override
public void visit(ConcreteElement1 element) {
//注:这里的访问逻辑需要根据具体业务来定
//一般来说,不同访问者对同一种元素类型有不同的访问方式
System.out.println("ConcreteVisitor1:" + element.mCommonProperty);
}
@Override
public void visit(ConcreteElement2 element) {
System.out.println("ConcreteVisitor1:" + element.mCommonProperty);
}
}
public class ConcreteVisitor2 implements Visitor {
@Override
public void visit(ConcreteElement1 element) {
System.out.println("ConcreteVisitor2:" + element.mProperty1);
}
@Override
public void visit(ConcreteElement2 element) {
System.out.println("ConcreteVisitor2:" + element.mProperty2);
}
}
场景类:
public class Client {
public static void main(String[] args) {
//这里的List充当了UML中的ObjectStruture——结构对象,又叫元素产生者,一般容纳在多个不同类、不同接口的容器,如List、Set、Map等。
//在项目中,一般很少抽象出这个角色。
List elements = new ArrayList<>();
elements.add(new ConcreteElement1("A", "1"));
elements.add(new ConcreteElement1("B", "2"));
elements.add(new ConcreteElement2("C", "3"));
elements.add(new ConcreteElement2("D", "4"));
Visitor visitor1 = new ConcreteVisitor1();
Visitor visitor2 = new ConcreteVisitor2();
for (Element e : elements) {
e.accept(visitor1);
}
System.out.println("-----------");
for (Element e : elements) {
e.accept(visitor2);
}
}
}
运行结果:
ConcreteVisitor1:A
ConcreteVisitor1:B
ConcreteVisitor1:C
ConcreteVisitor1:D
-----------
ConcreteVisitor2:1
ConcreteVisitor2:2
ConcreteVisitor2:3
ConcreteVisitor2:4
4. 优点
- 符合单一职责原则。具体元素角色负责数据的加载,而Visitor类则负责报表的展现,两个不同的职责非常明确地分离开来,各自演绎变化。
- 优秀的扩展性。由于职责分开,直接在Visitor中增加一个方法可以扩展对数据的操作。
- 灵活性非常高。例如访问不同的元素类型,使用不同的系数、参数等。
5. 缺点
- 违反迪米特原则。具体元素对访问者公布细节。
- 具体元素变更比较困难。具体元素的变更可能涉及到访问者的变更。
- 违背了依赖倒置转原则。访问者依赖的是具体元素,而不是抽象元素。直接依赖实现类使得扩展比较难。
解释器模式
1. 定义
定义:给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
解释器模式是一种按照规定语法进行解析的方案,在现在项目中使用较少。
2. 使用场景
- 在项目中可以使用Shell、JRuby、Groovy等脚本语言代替解释器模式,弥补Java编译型语言的不足。一定要使用解释器的时候,可以使用Expression4J、MESP、Jep等开源分析工具包代替自己实现。
- 重复发生的问题可以使用解释器模式。例如对日志文件进行分析处理,由于各个服务器的日志格式不同,但是数据要素是相同的,按照解释器的说法就是终结符表达式都是相同的,但是非终结符表达式就需要制定。
- 一个简单语法需要解释的场景。解释器模式一般用来解析比较标准的字符集,例如SQL语法分析,但是该部分逐渐被专用工具所取代。
- 复杂语法的解释不推荐使用解释器模式,会带来类膨胀、调试困难、运行效率低等问题。
- 在某些特用的商业环境下也会采用解释器模式。例如金融模型、实验数据的计算等。
- Android中,对清单文件的解释就使用了解释器模式。
3. 实现
由于解释器模式在实际项目中使用使用得比较少,而且又不太好理解,所以通过具体的一个加减法计算器例子来实现一次。
抽象解释器(AbstractExpression):
/**
* 抽象表达式(解释器)(AbstractExpression)
* 具体的解释任务由各个实现类完成,具体的解释器分别由TerminalExpression和NonterminalExpression完成
*/
public abstract class Expression {
//每个表达式必须有一个解析任务
public abstract int interpreter(HashMap var);
}
终结符表达式(TerminalExpression):
/**
* 终结符表达式(TerminalExpression)
* 终结符表达式又叫做运算元素、运算变量,也叫做终结符号,运算元素就是指a、b、c等符号,需要具体赋值的对象
* 终结符:这些元素除了需要赋值外,不需要做任何处理。所有运算元素都对应一个具体的业务参数,这是语法中最小的单元逻辑,不可再拆分
* 通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。
*/
public class VarExpression extends Expression {
private String mKey;
public VarExpression(String key) {
this.mKey = key;
}
@Override
public int interpreter(HashMap var) {
return var.get(this.mKey);
}
}
非终结符表达式(NonterminalExpression):
/**
* 非终结符表达式(NonterminalExpression)
* 非终结符表达式又叫运算符号、做非终结符号
* 运算符号就是加减符号,需要我们编写算法进行处理,每个运算符号都要对应处理单元,否则公式无法运行
* 非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式
*/
public abstract class SymbolExpression extends Expression {
//每个非终结符表达式都会对其他表达式产生依赖
protected Expression mLeft;
protected Expression mRight;
public SymbolExpression(Expression left, Expression right) {
this.mLeft = left;
this.mRight = right;
}
}
/**
* 加法
*/
public class AddExpression extends SymbolExpression{
public AddExpression(Expression left, Expression right) {
super(left, right);
}
@Override
public int interpreter(HashMap var) {
return this.mLeft.interpreter(var) + this.mRight.interpreter(var);
}
}
/**
* 减法
*/
public class SubExpression extends SymbolExpression {
public SubExpression(Expression left, Expression right) {
super(left, right);
}
@Override
public int interpreter(HashMap var) {
return this.mLeft.interpreter(var) - this.mRight.interpreter(var);
}
}
封装类:
/**
* 封装类
* 将需要的操作封装到一个类中,符合单一职责原则
*/
public class Calculator {
private Expression mExpression;
//通过递归的方式来解释表达式
public Calculator(String expStr) {
Stack stack = new Stack<>();
char[] charArray = expStr.toCharArray();
Expression left;
Expression right;
for (int i = 0; i < charArray.length; i++) {
switch (charArray[i]) {
case '+':
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new AddExpression(left, right));
break;
case '-':
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new SubExpression(left, right));
break;
default:
stack.push(new VarExpression(String.valueOf(charArray[i])));
break;
}
}
//解析结果赋值给成员变量,通过calculate方法进行运算、得到计算结果
this.mExpression = stack.pop();
}
//计算结果
public int calculate(HashMap var) {
return this.mExpression.interpreter(var);
}
//获取表达式
public static String getExpStr() throws Exception {
System.out.println("请输入表达式:");
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
return reader.readLine();
}
//为表达式中的运算符号赋值
public static HashMap getValues(String expStr) throws Exception {
HashMap values = new HashMap<>();
for (char ch : expStr.toCharArray()) {
if (ch != '+' && ch != '-') {
String key = String.valueOf(ch);
//这里需要防止同样的元素被重复赋值导致值被覆盖
if (!values.containsKey(key)) {
System.out.println("请输入" + key + "的值:");
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
Integer value = Integer.valueOf(reader.readLine());
values.put(key, value);
}
}
}
return values;
}
}
场景类:
public class Client {
public static void main(String[] args) throws Exception {
//获取表达式
String expStr = Calculator.getExpStr();
//为表达式中的元素赋值,这里的HashMap充当了Context上下文角色
HashMap values = Calculator.getValues(expStr);
//构造Calculator,解释表达式
Calculator calculator = new Calculator(expStr);
//运算
int result = calculator.calculate(values);
System.out.println("计算结果为:" + result);
}
}
运行结果:
请输入表达式:
a+b-c
请输入a的值:
1
请输入b的值:
2
请输入c的值:
3
计算结果为:0
4. 优点
- 公式可以运行时编辑。
- 高扩展性。修改语法规则只要修改相应的非终结符表达式就可以了;若扩展语法,则只要增加非终结符类就可以了。
5. 缺点
- 语法规则比较复杂时,解释器模式会引起类膨胀,维护困难。
- 解释器模式采用了循环、递归调用等方法,会带来效率、调试困难等问题。