前言
设计模式是一套代码设计经验的总结,使用它是为了可重用代码,让代码更容易被他人理解,保证代码的可靠性、程序的重用性。一共有 23 种设计模式,本文主要记录下常见的几个。
单例设计模式
使用单例模式需要满足以下条件:
- 构造器私有化。
- 通过一个静态方法或者枚举返回单例类实例。
- 确保单例类对象有且只有一个,尤其在多线程情况下。
- 确保单例类对象在反序列化时不会重新构建对象(只在实现序列化接口时需要),当实现了序列化接口需要在 readResolve 方法内部返回那个静态变量,因为静态变量不会被序列化,所以直接返回相当于对象没变。
饿汉模式
加载类就进行初始化,可能会用不到导致资源浪费。代码如下:
class A { |
懒汉模式
每次调用 getInstance 都是同步的,效率低下,不推荐用。代码如下:
class B { |
双重校验锁
DCL 一般可以使用。代码如下:
class C { |
静态内部类
推荐使用。代码如下:
class D { |
枚举单例
推荐使用。代码如下:
enum E { |
简单工厂设计模式
对实现了同一接口的类进行实例的创建,拥有三种角色,一个工厂类,一个基类,若干个基类的派生类(或实现类) 。
使用场景是工厂类负责创建的对象比较少,用户只需要知道传给工厂类的参数就行了,而不需关系具体创建。
优点: 用户根据参数获得实例,避免了直接实例化类,降低了耦合性。
缺点: 简单工厂需要知道其所有要生成的类型,当类型过多是不适合使用,并且如果需求变动比如我要添加微博分享,那么就得修改工厂类,违背了开闭原则。
interface ShareChannel { |
工厂方法设计模式
拥有四种角色,抽象工厂类、具体工厂类、基类(或接口)、实现类,把简单工厂的实现修改一下。
比如微信分享 WXAPIFactory.createWXAPI()、钉钉分享 DDShareApiFactory.createDDShareApi() 都是使用了工厂方法设计模式。
对比简单工厂模式,当需求改变需要增加微博分享时我们只需要创建一个 WBShareChannel 就行了,没有违背开闭原则。
interface ShareChannel { |
建造者设计模式
- 创建一个复杂对象时使用,通过 Builder 类一步一步创建,对比构建器,其参数可选,并且可以无序。
- 比如 Android 中的 AlertDialog 其组成非常复杂,比如可以只设置 Title、Message,也可以只设置 Title,有很多种可选的设置。分享功能需要很多的参数,而很多参数都是可选的就可以使用建造者模式。
- 其主要的缺点就是产生了多余的类。
class ShareParams { |
代理设计模式
代理设计模式包括静态代理以及动态代理。
静态代理
- 在符合开闭原则的情况下对目标对象进行功能扩展,防止与真实对象接触降低耦合性。
- 接口一旦发生改变,代理类也得发生改变。
interface BuyTicket { |
动态代理
相对于静态代理其代理类不用在代码中写死,但是必须要有一个接口才能进行代理,因为 java 不支持多继承。
class DynamicProxy { |
装饰模式
- 与装饰模式不同的是,代理模式一般实际类对象都在代理类内部创建(对外隐藏),而装饰模式一般是外界传入的实际类对象。
- 优点 可以在不改变被装饰类的情况下加强类的功能,或者类似 Android 里面的 ContextWrap 类,使其子类 Activity、Service 不必包含复杂的实现逻辑(比如 startActivity )。
class ContextImpl implements Context { |
外观模式
- 一般在封装 SDK 或者 API 时用到,暴露给客户端外观类(该类把子系统的功能进行结合)供客户端使用。
- 优点:客户端无需关心具体的子系统的实现,直接与外观类进行交互,降低了耦合性,这样即使具体的子系统的内容发生改变,用户也不会感知到;同时由于客户端只能使用外观类的功能,而不能使用子系统的其他功能所以加强了安全性。
- 缺点:不符合开闭原则,如果业务发生变更那么可能要直接修改外观类。
- 假设用户有一台手机,手机有打电话系统和拍照系统,手机的外观类是 MobilePhone ,用户只能调用该类中的方法去执行操作,无法调用 CallManager.privateFunction 方法,因此加强了安全性。
class MobilePhone { |
享元设计模式
- 池技术的重要实现技术,可以减少重复对象的创建,降低程序内存的占用,提高性能,一般拥有着以下三个角色
- 抽象享元角色,同时定义出对象的外部状态和内部状态的接口。
- 具体享元角色,实例的抽象享元角色的业务。
- 享元工厂,负责管理对象池和创建享元对象(一般内部用 Map、SpareArray 实现),如下面示例中的 Ticket 抽象类就是抽象享元角色,内部的 from、to 属于内部状态,level 属于外部状态会随着外界状态变化而变化,TrainTicket 属于具体享元角色,TicketFactory 属于享元工厂。
- 其实就是缓存的一种,比如 Java 中的 String 一旦常量池有了就不会重新创建、Android 中的 Message 通过一个链表来缓存、Volley 里面的 ByteArrayPool、OkHttp 里面的连接池。
class FlyWeight { |
策略设计模式
- 针对同一类型的问题拥有多种解决方式,仅仅具体行为有差异,或者有多个派生自同一个类的实现类又需要通过 if-else、switch 判断使用哪个的。
- 优点, Context 类只依赖于抽象类 ISort,不依赖与具体实现,Context 只需要知道它操作的是一个 ISort 类就行了 ,遵守了开闭原则,当添加了新的策略时不需要修改 Context 类。
interface ISort { |
模板方法设计模式
主要有两种角色, 一个抽象父类,多个派生自该抽象父类的子类。
某个方法有些步骤是固定的,有些步骤是不固定的,就可以使用该模式,下面以 BaseActivity 为例。
优点 将不变的行为搬移到超类,去除了子类中的重复代码。
public abstract class BaseActivity extends AppCompatActivity { |
观察者设计模式
- 拥有4种角色,一个 Observable 接口或类、一个 Observer 接口、一个 Observable 的派生类、多个Observer 接口的实现类。
- 优点 观察者与被观察者是抽象耦合易于扩展。
- 缺点 由于通知观察者是顺序通知的,如果一个观察者做的事情比较多造成了卡顿,那么就会影响其他后面的观察者。
- 下面的代码微信公众号是被观察者,用户是观察者,其中 hfw、cbw 订阅了该微信公众号,而 ym 没有订阅,所以 ym 不会收到该微信公众号的推送(调用 update)。
class ObserverPattern { |