设计模式

212 阅读14分钟

模式是在某种情境下,针对某问题的某种解决方案

设计模式的精髓在于封装变化,我们总是花更多的时间在软件维护和更新上面,所以应该致力于提高软件的可维护性和可扩展性

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

1、单例模式

  • WindowManagerGlobal:内部维护着mViews、mRoots、mParams列表

image.png

2、原型模式

  • java.lang.Object.clone()

当创建给定类的实例很昂贵或很复杂时,就可以使用原型模式,原型模式允许你通过复制现有实例来创建新的实例(在Java中,通常意味着使用clone或者反序列化)

image.png

3、建造者模式

Kotlin具名参数、Setter方法都无法解决参数依赖 & 参数校验的能力,而且如果对象是不可变的,那还无法调用Setter方法

image.png

AlertDialog.Builer builder = new AlertDialog.Builder(context);
builder.setIcon(R.drawable.icon)
        .setTitle("title")
        .setMessage("message")
        .setPositiveButton("Button1",
                new DialogInterface.OnclickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        setTitle("click");
                    }
                })
        .create()
        .show();

4、工厂方法模式

// List和Set继承自Collection接口,Collection接口继承于Iterable接口,
// 所以List和Set接口也需要继承并实现Iterable中的iterator()方法
// 实现类ArrayList和HashSet中的iterator方法就给我们具体构造并返回了一个迭代器对象

public Iterator<E> iterator() {
    return new Itr();
}

工厂模式可以有效避免初始化造成的耦合问题

image.png

简单工厂并不是一个设计模式,而是一种编程习惯

image.png

image.png

工厂方法用来处理对象的创建,并将这样的行为封装在子类中。这样,客户程序中关于超类的代码就和子类对象创建代码解耦了

image.png

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类

image.png

在应用工厂方法后,高层组件(PizzaStore)和底层组件(具体的Pizza)都依赖了Pizza抽象。想要遵循**依赖倒置原则**,工厂方法不是并非是唯一的技巧,但却是最有威力的技巧之一

image.png

5、抽象工厂模式

public static LayoutInflater from(Context context) {
    LayoutInflater LayoutInflater =
            (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}

上面代码中,Context使用了抽象工厂模式

抽象工厂模式通过组合的方式提供抽象接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道实际产出的产品是什么。这样一来,客户就从具体的产品中被解耦

image.png

6、装饰者模式

Java IO
ContextWrapper

开放-封闭原则:观察者模式、装饰者模式

image.png

从类图来看,装饰者模式也用到了继承,但关键点在于,我们利用继承达到“类型匹配”,而不是利用继承获得“行为”。新行为是通过组合对象得来的

采用装饰者模式在实例化组件时,不只需要实例化组件,还要把组件包装进装饰者中,天晓得有多少个,这时我们可以考虑 工厂模式生成器模式

7、代理模式

  • Binder:Interface、Proxy、Stub

远程代理、虚拟代理、动态代理 代理模式为另一个对象提供替身或占位符以控制对这个对象的访问

image.png

8、适配器模式

// 创建字节流
FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt");
// 创建字符流适配器,将字节流转换为字符流
InputStreamReader isr = new InputStreamReader(fis);
OutputStreamWriter osw = new OutputStreamWriter(fos);

比较典型的有ListView和RecyclerView。为什么ListView需要使用适配器呢?
ListView用于显示列表数据,但列表数据形式多种多样,为了处理和显示不同的数据,我们需要对应的适配器作为桥梁
这样ListView就可以只关心它的每个ItemView,而不用关心这个ItemView具体显示的是什么
ListView和数据源之间没有任何关系,这时候需要通过适配器提供getView方法给ListView使用,
每次ListView只需提供位置信息给getView函数,然后getView函数根据位置信息向数据源获取对应的数据,根据数据返回不同的View

适配器将一个类的接口转换成客户期望的另一个接口。适配器可以让原本接口不兼容的类可以合作无间

image.png

9、组合模式

组合模式允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理单个对象及对象组合

Android中View的结构是树形结构,每个ViewGroup包含一系列的View,而ViewGroup本身又是View。
这是Android中非常典型的组合模式

image.png

组合模式以“单一职责”换取“透明性”,也就是说,一个节点是叶节点还是组合,对客户是透明的

10、外观模式

  • Context(四大组件启动)

外观模式提供了统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用(最少知识原则)

image.png

11、享元(蝇量)模式

如果让某个类的一个实例能用来提供许多“虚拟实例”,就使用蝇量模式

Java中的常量池,线程池等。主要是为了重用对象。
Android线程通信中每次我们调用Message.obtain()从消息池中取出可重复使用的消息,避免产生大量的Message对象

image.png

12、桥接模式

拆分或重组一个具有多重功能的庞杂类:(形状、颜色)

ActivityManagerService -> (ActivityStack -> ActivityRecord)

image.png

13、策略模式

// JDK使用场景
public interface Comparator<T> {
    int compare(T o1, T o2);
}
// Android在属性动画中使用时间插值器的时候就用到了策略模式
// 在使用动画时,你可以选择线性插值器LinearInterpolator、加速减速插值器以及自定义的插值器

接口提供能力,如果能力有多种实现方式,应该考虑使用策略模式来抽离变化(多用组合,少用继承

interface FlyBehaviour {
    void fly();
}

interface Flyable {
    void setFlyBehaviour(FlyBehaviour flyBehaviour);
    FlyBehaviour getFlyBehaviour();
    default void fly() {
        getFlyBehaviour().fly();
    }
}

class Duck { }

class WildDuck extends Duck implements Flyable {
    /* 可以使用建造者模式来保证FlyBehaviour一定会被设置 */
    FlyBehaviour mFlyBehaviour;
    
    @Override
    public void setFlyBehaviour(FlyBehaviour flyBehaviour) {
        mFlyBehaviour = flyBehaviour;
    }
    @Override
    public FlyBehaviour getFlyBehaviour() {
        return mFlyBehaviour;
    }
}

可以通过 建造者模式 来保证FlyBehaviour一定会被设置

image.png

**反例**:针对计费算法,一种方法可以将多种算法写在一个类中,比如将多种计费算法写在一个类中,**一个方法对应一个具体的计费算法**;也可以将这些算法封装在一个统一的方法中,**通过if…else等条件判断语句来选择具体的算法**。这两种实现方法我们都可以称为硬编码

14、状态模式

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类

Android源码中很多地方都有用到状态模式,举一个例子,就是Android的WIFI管理模块
当WIFI开启时,自动扫描周围的接入点,然后以列表的形式展示;当wifi关闭时则清空
这里wifi管理模块就是根据不同的状态执行不同的行为。

image.png

一般来说,我们把策略模式想成是除继承之外的一种弹性替代方案,有了策略模式,你可以通过组合不同的对象来改变行为。而我们把状态模式想成是不用在Context中放置许多判断条件的替代方案,通过将行为包装进状态对象中,你可以通过在Context内简单地改变状态对象来改变Context的行为

15、迭代器模式

  • 集合类

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示

image.png

16、观察者模式

  • LifecycleObserver

观察者模式是JDK使用最多的设计模式,它实现了一对多以及松耦合关系,符合好莱坞原则、符合开闭原则

image.png

17、责任链模式

  • 事件分发:在 Android Framework 中的 View 和 ViewGroup 中,事件分发机制就是一个典型的责任链模式的应用。当一个触摸事件发生时,它首先被传递给顶层的 View(通常是 Activity),然后依次传递给 View 层级中的每一个 View,直到找到合适的 View 来处理这个事件。如果某个 View 没有处理这个事件,它会将事件传递给它的父容器,直到事件被处理或者传递到了根布局

  • 异常处理:在 JDK 中,异常处理机制也是一个常见的责任链模式的应用。当一个方法抛出异常时,异常会被传递给调用栈中的上一级方法,如果上一级方法能够处理这个异常,它就会被捕获并处理掉,否则异常会继续向上级方法传递,直到被捕获或者到达顶层方法

  • 拦截器:在 Android Framework 中的 Interceptor 或者 OkHttp 中的 Interceptor 也是责任链模式的应用。拦截器链中的每个拦截器都有机会处理请求或者响应,如果某个拦截器处理了请求或者响应,它就可以决定是否继续传递给下一个拦截器

image.png

18、模版方法模式

  • View.onMeasure、onLayout、onDraw
  • Activity.onSaveInstanceState(Bundle)、onRestoreInstanceState(Bundle)、onCreate

直到目前,我们的议题都在围绕封装转:我们已经封装了对象创建、方法调用、复杂接口。接下来我们要深入封装算法块,好让子类在任何时候都可以将自己挂接进运算里 模版方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模版算法使得子类在不改变算法结构的情况下,重新定义算法中的某些步骤

image.png

**好莱坞原则**(高层组件对待底层组件的方式是“别调用我们,我们会调用你”),符合好莱坞原则的模式:工厂方法、观察者、模版方法 & PS:策略模式通过组合类来实现整个算法,模版方法模式通过继承来实现算法的某一步

19、命令模式

  • java.lang.Runnable

通过封装方法调用,我们可以把运算块包装成形。所以调用此运算的对象不需要关心事情是如何进行的,只要知道如何使用包装成形的方法来完成它就可以。通过封装方法调用,也可以做一些聪明的事情,例如日志请求(数据库事务),或者重复使用这些封装来实现撤销

image.png

20、备忘录模式

  • Activity-onSavedInstanceState
  • Activity-onRetainNonConfigurationInstance

当你需要让对象返回之前的状态时,就使用备忘录模式

image.png

21、解释器模式

使用解释器模式为语言创建解释器

这个用到的地方也不少,其一就是Android的四大组件需要在AndroidManifest.xml中定义,
其实AndroidManifest.xml就定义了,等标签(语句)的属性以及其子标签,规定了具体的使用(语法),
通过PackageManagerService(解释器)进行解析

22、中介者模式

java.util.concurrent.Executor(将任务和执行节藕)

Binder机制也用到了中介者模式。我们知道系统启动时,各种系统服务会向ServiceManager提交注册,
当我们需要获取系统的Service时,比如ActivityManager、WindowManager等,
首先是向ServiceManager查询指定标示符对应的Binder,再由ServiceManager返回Binder的引用。
并且客户端和服务端之间的通信是通过Binder驱动来实现,这里的ServiceManager和Binder驱动就是中介者

使用中介者来集中相关对象之间复杂的沟通和控制方式

image.png

image.png

23、访问者模式

Android中运用访问者模式,主要是在编译期注解中,编译期注解核心原理依赖APT(Annotation Processing Tools),
著名的开源库比如ButterKnife、Dagger、Retrofit都是基于APT

当你想要为一个对象的组合增加新的能力,且封装并不重要时,考虑使用访问者模式

截屏2024-01-31 12.14.33.png

MVC

image.png

杂记

代理模式是一种结构型设计模式,它允许在不改变原始对象的基础上为其添加新功能。代理通常与被代理对象实现相同的接口,并在需要的时候将方法调用传递给被代理对象。代理的主要优点是可以在不改变原始对象的情况下添加新功能,以及实现懒加载、安全控制等特性

委托模式是一种结构型设计模式,它通过将对象的部分行为或属性委托给另一个对象来实现。与代理模式不同,委托模式关注的是对象的行为委托,而不是代理实现新功能。在Kotlin中,可以使用by关键字轻松实现委托模式

策略模式是一种行为型设计模式,它定义了一系列算法,将每个算法封装到具有公共接口的独立类中,使得它们可以相互替换。策略模式可以在运行时根据需要选择不同的算法,而不需要修改原始代码

UML类图

截屏2024-01-31 12.14.33.png

  • 车的类图结构为<>,表示车是一个抽象类
  • 小汽车和自行车 与 车是继承关系;更具体为实现关系,使用带空心箭头的虚线表示
  • 小汽车与 SUV 之间也是继承关系,更具体为泛化关系,使用带空心箭头的实线表示
  • 小汽车与发动机之间是组合关系,使用带实心箭头的实线表示
  • 学生与班级之间是聚合关系,使用带空心箭头的实线表示
  • 学生与身份证之间为双向关联关系,使用一根实线表示
  • 学生上学需要用到自行车,与自行车是一种依赖关系,使用带箭头的虚线表示

截屏2024-01-31 12.14.33.png

UML 类表示法

具体类在类图中用矩形框表示,矩形框分为三层:第一层是类名字;第二层是类的成员变量;第三层是类的方法

成员变量以及方法前的访问修饰符用符号来表示:

  • “+”表示 public
  • “-”表示 private
  • “#”表示 protected
  • 不带符号表示 default

其中,属性和方法返回值的数据类型非必需,静态方法在方法名前加下划线用来区和实例方法的不同

抽象类在UML类图中同样用矩形框表示,但是抽象类的类名以及抽象方法的名字都用 斜体字 表示

接口在类图中也是用矩形框表示,但与类的不同的是,接口在类图中的第一层用 <<interface>> 表示

设计原则

我们在设计类的时候,首先要考虑的是单一职责,对于一个类应该只有一个引起变化的原因。比如我们设计一个汽车,设计一个类不能既管理引擎又管理刹车

扩展功能时我们要考虑的是开闭原则,对扩展开发,对修改关闭

在设计接口时,我们要考虑的是接口隔离里氏替换

在实现具体功能时,我们要考虑的是依赖反转,抽象不能依赖具体,具体应该依赖抽象

依赖倒置原则 - 实践

  • 工厂方法
  • 控制反转:DI(依赖注入)、DL(依赖查找)

思考题

如何在System.out.println调用时自动添加附加信息?

public class TimestampedPrintStream extends PrintStream {
    private PrintStream original;

    public TimestampedPrintStream(PrintStream original) {
        super(original);
        this.original = original;
    }

    @Override
    public void println(String x) {
        String timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date());
        original.println(timestamp + " " + x);
    }

    public static void main(String[] args) {
        // 创建一个代理对象来替换 System.out
        System.setOut(new TimestampedPrintStream(System.out));

        // 现在你可以直接调用 System.out.println,而时间戳信息会被自动添加
        System.out.println("This is a message with timestamp");
    }
}

参考文献

blog.csdn.net/liyf2/artic…

juejin.cn/post/721177…

www.runoob.com/w3cnote/del…

blog.csdn.net/wangzihan91…

www.baeldung.com/solid-princ…

juejin.cn/post/684490…

lingmoumou.github.io/p/2020/02/0…

juejin.cn/post/684490…