设计模式
目录
定义
某类特定问题的代码设计解决方案,实际上是一套针对某类问题的代码设计经验总结。
常用符号
- Java类之间的关系主要有以下几种:
- 继承关系(Inheritance):在Java中,一个类可以继承另一个类的属性和方法。被继承的类称为父类或超类,继承的类称为子类或派生类。
- 接口关系(Interface):Java中的接口是一种特殊的抽象类,它可以定义一组方法的声明,但不能实现这些方法。接口可以被类实现,从而强制实现类具有接口中定义的方法。
- 实现关系(Implementation):实现是指一个类实现一个接口中定义的所有方法,从而具有接口的功能。一个类可以实现多个接口。
- 关联关系(Association):关联是指一个类对象和另一个类对象之间的关系,通常用于描述一对多的关系。例如,一个人可以拥有多部手机,那么人和手机之间就存在关联关系。
- 聚合关系(Aggregation):聚合是指一种弱的关联关系,表示一个对象可以包含多个其他对象,称为整体对象和部分对象。整体对象可以存在,而部分对象也可以独立存在。例如,一台电脑可以由多个硬件组件组成,但是硬件组件可以独立于电脑而存在。
- 组合关系(Composition):组合是指一种强的关联关系,表示一个对象包含另一个对象,称为整体对象和部分对象。整体对象不存在时,部分对象也不存在。例如,一个汽车由发动机、车身和轮子等组成,只有汽车存在时,发动机、车身和轮子才存在。
作用
1.提高代码复用率,降低开发成本和周期。
2.提高代码可维护性、可拓展性。
3.使代码更加优雅、更容易被他人理解。
设计原则
在设计模式进行设计时需要遵循以下七个原则:
类型
设计模式的类型总共分为:3大类、23种具体设计模式,具体如下:
常用设计模式
创建型
单例模式
定义
Ensure a class has only one instance, and provide a global point of access to it.
动态确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
模式说明
实现一个类只有一个实例化对象,同时需要对外提供一个全局访问点。
如系统管理器,如果可以创建多个,其中一个关闭任务,其他管理器不知道,将发生错误。
作用
保证一个类只有一个实例对象,降低对象之间的耦合度。
实现原理
在Java中,我们通过使用对象(类实例化后)来操作这些类,类实例化是通过它的构造方法进行的,要是想实现一个类只有一个实例化对象,就要对类的构造方法下功夫:
单例模式的一般实现:
public class Singleton {
//1. 创建私有变量 ourInstance(用以记录 Singleton 的唯一实例)
//2. 内部进行实例化
private static Singleton ourInstance = new Singleton();
//3. 把类的构造方法私有化,不让外部调用构造方法实例化
private Singleton() {
}
//4. 定义公有方法提供该类的全局唯一访问点
//5. 外部通过调用getInstance()方法来返回唯一的实例
public static Singleton newInstance() {
return ourInstance;
}
}
优缺点
优点
- 提供了对唯一实例的受控访问;
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能;
- 可以根据实际情况需要,在单例模式的基础上扩展做出双例模式,多例模式;
- 单例模式可以避免对资源的多重占用,例如一个写文件时,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
缺点
- 单例类的职责过重,里面的代码可能会过于复杂,在一定程度上违背了“单一职责原则”。
- 如果实例化的对象长时间不被利用,会被系统认为是垃圾而被回收,这将导致对象状态的丢失。
实现方式
单例模式的实现方式有多种,根据需求场景,可分为2大类、6种实现方式。
初始化单例类时就创建单例
一、饿汉式
这是最简单的单例实现方式。
原理
依赖 JVM类加载机制,保证单例只会被创建1次,即线程安全。
1.JVM在类的初始化阶段(即 在Class被加载后、被线程使用前),会执行类的初始化。
2.在执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。
实现方式
class Singleton {
// 1. 加载该类时,单例就会自动被创建
private static Singleton ourInstance = new Singleton();
// 2. 构造函数 设置为 私有权限
// 原因:禁止他人创建实例
private Singleton() {
}
// 3. 通过调用静态方法获得创建的单例
public static Singleton newInstance() {
return ourInstance;
}
}
应用场景
除了初始化单例类时 即 创建单例外,继续延伸出来的是:单例对象要求初始化速度快 & 占用内存小。
二、枚举类型
原理
根据枚举类型的下述特点,满足单例模式所需的 创建单例、线程安全、实现简洁的需求。
实现方式
public enum Singleton{
//定义1个枚举的元素,即为单例类的1个实例
INSTANCE;
// 隐藏了1个空的、私有的 构造方法
// private Singleton () {}
}
// 获取单例的方式:
Singleton singleton = Singleton.INSTANCE;
注:这是 最简洁、易用 的单例实现方式,借用《Effective Java》的话:
单元素的枚举类型已经成为实现 Singleton的最佳方法
按需、延迟创建单例
一、懒汉式(基础实现)
原理
与 饿汉式 最大的区别是:单例创建的时机
饿汉式:单例创建时机不可控,即类加载时 自动创建 单例
懒汉式:单例创建时机可控,即有需要时,才 手动创建 单例
具体实现
class Singleton {
// 1. 类加载时,先不自动创建单例
// 即,将单例的引用先赋值为 Null
private static Singleton ourInstance = null;
// 2. 构造函数 设置为 私有权限
// 原因:禁止他人创建实例
private Singleton() {
}
// 3. 需要时才手动调用 newInstance() 创建 单例
public static Singleton newInstance() {
// 先判断单例是否为空,以避免重复创建
if( ourInstance == null){
ourInstance = new Singleton();
}
return ourInstance;
}
}
缺点
基础实现的懒汉式是线程不安全的,具体原因如下:
二、同步锁(懒汉式的改进)
原理
使用同步锁 synchronized锁住 创建单例的方法 ,防止多个线程同时调用,从而避免造成单例被多次创建
1.即,getInstance()方法块只能运行在1个线程中。
2.若该段代码已在1个线程中运行,另外1个线程试图运行该块代码,则 会被阻塞而一直等待。
3.而在这个线程安全的方法里我们实现了单例的创建,保证了多线程模式下 单例对象的唯一性。
代码实现
// 写法1
class Singleton {
// 1. 类加载时,先不自动创建单例
// 即,将单例的引用先赋值为 Null
private static Singleton ourInstance = null;
// 2. 构造函数 设置为 私有权限
// 原因:禁止他人创建实例
private Singleton() {}
// 3. 加入同步锁
public static synchronized Singleton getInstance(){
// 先判断单例是否为空,以避免重复创建
if ( ourInstance == null ) {
ourInstance = new Singleton();
}
return ourInstance;
}
}
// 写法2
// 该写法的作用与上述写法作用相同,只是写法有所区别
class Singleton{
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
// 加入同步锁
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
}
缺点
每次访问都要进行线程同步(即 调用synchronized锁),造成过多的同步开销(加锁 = 耗时、耗能)。
实际上只需在第1次调用该方法时才需要同步,一旦单例创建成功后,就没必要进行同步。
三、双重校验锁(懒汉式的改进)
原理
在同步锁的基础上,添加1层 if判断:若单例已创建,则不需再执行加锁操作就可获取实例,从而提高性能。
代码实现
class Singleton {
//使用volatile 表明每次都会去内存中读取最新的值
private volatitle static Singleton ourInstance = null;
private Singleton() {}
public static Singleton newInstance() {
// 加入双重校验锁
// 校验锁1:第1个if
// 作用:若单例已创建,则直接返回已创建的单例,无需再执行加锁操作
if ( ourInstance == null){ // ①
synchronized (Singleton.class){ // ②
// 校验锁2:第2个 if
// 作用:防止多次创建单例问题
// 原理
// 1. 线程A调用newInstance(),当运行到②位置时,此时线程B也调用了newInstance()
// 2. 因线程A并没有执行instance = new Singleton();,此时instance仍为空,因此线程B能突破第1层 if 判断,运行到①位置等待synchronized中的A线程执行完毕
// 3. 当线程A释放同步锁时,单例已创建,即instance已非空
// 4. 此时线程B 从①开始执行到位置②。此时第2层 if 判断 = 为空(单例已创建),因此也不会创建多余的实例
if( ourInstance == null){
ourInstance = new Singleton();
}
}
}
return ourInstance;
}
}
volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从内存中读取。
(首先我们要先意识到有这样的现象,编译器为了加快程序运行的速度,对一些变量的写操作会先在寄存器或者是CPU缓存上进行,最后才写入内存。而在这个过程,变量的新值对其他线程是不可见的。而volatile的作用就是使它修饰的变量的读写操作都必须在内存中进行!)
用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。其实volatile只能保证变量的可见性,不能保证原子性。
synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
同步块大家都比较熟悉,通过 synchronized关键字来实现,所有加上synchronized和 块语句,在多线程访问的时候,同一时刻只能有一个线程能够用synchronized修饰的方法 或者 代码块。不但保证了可见性还保证了原子性。
再就是这个双重判断null :
这是因为如果线程A进入了该代码,线程B 在等待,这是A线程创建完一个实例出来后,线程B 获得锁进入同步代码,实例已经存在,木有必要再创建一个,所以双重判断有必要。
注意:volatile对singleton的创建过程的重要性:
禁止指令重排序(有序性)
实例化一个对象其实可以分为三个步骤:
(1)分配内存空间。
(2)初始化对象。
(3)将内存空间的地址赋值给对应的引用。
但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:
(1)分配内存空间。
(2)将内存空间的地址赋值给对应的引用。
(3)初始化对象
如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果(如题目的描述,这里就是因为 instance = new Singleton(); 不是原子操作,编译器存在指令重排,从而存在线程1 创建实例后(初始化未完成),线程2 判断对象不为空后对其操作,但实际对象仍为空,造成错误)。因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量,volatile的禁止重排序保证了操作的有序性。
Singleton对象的内存可见性
这里由于synchronized锁的是Singleton.class对象,而不是Singleton对象,所以synchronized只能保证Singleton.class对象的内存可见性,但并不能保证Singleton对象的内存可见性;这里用volatile声明Singleton,可以保证Singleton对象的内存可见性。这一点作用也是非常重要的(如题目的描述,避免因为线程1 创建实例后还只存在自己线程的工作内存,未更新到主存。线程 2 判断对象为空,创建实例,从而存在多实例错误)。
缺点
实现复杂:多种判断,易出错。
四、静态内部类
原理
根据 静态内部类 的特性,同时解决了按需加载、线程安全的问题,同时实现简洁。
1.在静态内部类里创建单例,在装载该内部类时才会去创建单例。
2.线程安全:类是由 JVM加载,而JVM只会加载1遍,保证只有1个单例。
代码实现
class Singleton {
// 1. 创建静态内部类
private static class Singleton2 {
// 在静态内部类里创建单例
private static Singleton ourInstance = new Singleton();
}
// 私有构造函数
private Singleton() {}
// 延迟加载、按需创建
public static Singleton newInstance() {
return Singleton2.ourInstance;
}
}
// 调用过程说明:
// 1. 外部调用类的newInstance()
// 2. 自动调用Singleton2.ourInstance
// 2.1 此时单例类Singleton2得到初始化
// 2.2 而该类在装载 & 被初始化时,会初始化它的静态域,从而创建单例;
// 2.3 由于是静态域,因此只会JVM只会加载1遍,Java虚拟机保证了线程安全性
// 3. 最终只创建1个单例
总结
简单工厂模式
含义
简单工厂模式又叫静态方法模式(因为工厂类定义了一个静态方法)。
现实生活中,工厂是负责生产产品的;同样在设计模式中,简单工厂模式我们可以理解为负责生产对象的一个类,称为“工厂类”。
解决的问题
将“类实例化的操作”与“使用对象的操作”分开,让使用者不用知道具体参数就可以实例化出所需要的“产品”类,从而避免了在客户端代码中显式指定,实现了解耦。
即使用者可直接消费产品而不需要知道其生产的细节。
原理
模式组成
组成(角色) | 关系 | 作用 |
---|---|---|
抽象产品(Product) | 具体产品的父类 | 描述产品的公共接口 |
具体产品(Concrete Product) | 抽象产品的子类;工厂类创建的目标类 | 描述生产的具体产品 |
工厂(Creator) | 被外界调用 | 根据传入不同参数从而创建不同具体产品类的实例 |
UML类图
使用步骤
- 创建抽象产品类 & 定义具体产品的公共接口;
- 创建具体产品类(继承抽象产品类) & 定义生产的具体产品;
- 创建工厂类,通过创建静态方法根据传入不同参数从而创建不同具体产品类的实例;
- 外界通过调用工厂类的静态方法,传入不同参数从而创建不同具体产品类的实例
实例
背景
公司需要生产两种产品A和B
使用步骤
- 创建抽象产品类,定义具体产品的公共接口
abstract class Product{
public abstract void write();
}
- 创建具体产品类(继承抽象产品类),定义生产的具体产品
//具体产品类A
class ProductA extends Product{
@Override
public void write() {
System.out.println("产品A");
}
}
//具体产品类B
class ProductB extends Product{
@Override
public void write() {
System.out.println("产品B");
}
}
- 创建工厂类,通过创建静态方法从而根据传入不同参数创建不同具体产品类的实例
class Factory {
public static Product getProduct(String productName){
//工厂类里用switch语句控制生产哪种商品;
//使用者只需要调用工厂类的静态方法就可以实现产品类的实例化。
switch (productName){
case "A":
return new ProductA();
case "B":
return new ProductB();
default:
return null;
}
}
}
- 外界通过调用工厂类的静态方法,传入不同参数从而创建不同具体产品类的实例
//工厂产品生产流程
public class SimpleFactoryPattern {
public static void main(String[] args){
Factory mFactory = new Factory();
//客户要产品A
try {
//调用工厂类的静态方法 & 传入不同参数从而创建产品实例
mFactory.getProduct("A").write();
}catch (NullPointerException e){
System.out.println("没有这一类产品");
}
//客户要产品B
try {
mFactory.getProduct("B").write();
}catch (NullPointerException e){
System.out.println("没有这一类产品");
}
//客户要产品C
try {
mFactory.getProduct("C").write();
}catch (NullPointerException e){
System.out.println("没有这一类产品");
}
}
}
结果输出:
产品A
产品B
没有这一类产品
优缺点
优点
- 将创建实例的工作与使用实例的工作分开,使用者不必关心类对象如何创建,实现了解耦;
- 把初始化实例时的工作放到工厂里进行,使代码更容易维护。
更符合面向对象的原则 & 面向接口编程,而不是面向实现编程。
缺点
- 工厂类集中了所有实例(产品)的创建逻辑,一旦这个工厂不能正常工作,整个系统都会受到影响。
- 违背“开放 - 关闭原则”,一旦添加新产品就不得不修改工厂类的逻辑,这样就会造成工厂逻辑过于复杂。
- 简单工厂模式由于使用了静态工厂方法,静态方法不能被继承和重写,会造成工厂角色无法形成基于继承的等级结构。
应用场景
- 客户如果只知道传入工厂类的参数,对于如何创建对象的逻辑不关心时;
- 当工厂类负责创建的对象(具体产品)比较少时。
工厂方法模式
定义
工厂方法模式,又称工厂模式、多态工厂模式和虚拟构造器模式,通过定义工厂父类负责定义创建对象的公共接口,而子类则负责生成具体的对象。
作用
将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定应该实例化(创建)哪一个类。
解决的问题
工厂一旦需要生产新产品就需要修改工厂类的方法逻辑,违背了“开放 - 关闭原则。
1.即简单工厂模式的缺点
2.之所以可以解决简单工厂的问题,是因为工厂方法模式把具体产品的创建推迟到工厂类的子类(具体工厂)中,此时工厂类不再负责所有产品的创建,而只是给出具体工厂必须实现的接口,这样工厂方法模式在添加新产品的时候就不修改工厂类逻辑而是添加新的工厂子类,符合开放封闭原则,克服了简单工厂模式中缺点。
原理
模式组成
组成(角色) | 关系 | 作用 |
---|---|---|
抽象产品(Product) | 具体产品的父类 | 描述具体产品的公共接口 |
具体产品(Concrete Product) | 抽象产品的子类;工厂类创建的目标类 | 描述生产的具体产品 |
抽象工厂(Creator) | 具体工厂的父类 | 描述具体工厂的公共接口 |
具体工厂(Concrete Creator) | 抽象工厂的子类;被外界调用 | 描述具体工厂;实现FactoryMethod工厂方法创建产品的实例 |
UML类图
使用步骤
步骤1: 创建抽象工厂类,定义具体工厂的公共接口;
步骤2: 创建抽象产品类 ,定义具体产品的公共接口;
步骤3: 创建具体产品类(继承抽象产品类) & 定义生产的具体产品;
步骤4:创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;
步骤5:外界通过调用具体工厂类的方法,从而创建不同具体产品类的实例
实例
背景
公司目前只有一个工厂且该工厂只能生产产品A,客户提出需求需要产品B;
此时改变原来的工厂配置增加产品B的生产很困难,如果下一次客户需要产品C,又得改变原工厂配置,这种改变很耗费成本;
因此公司可以置办一个新的工厂用于生产新产品B。
即工厂方法模式。
使用步骤
- 创建抽象工厂类,定义具体工厂的公共接口
abstract class Factory{
public abstract Product getProduct();
}
- 创建抽象产品类 ,定义具体产品的公共接口;
abstract class Product{
public abstract void write();
}
- 创建具体产品类(继承抽象产品类), 定义生产的具体产品;
//具体产品A类
class ProductA extends Product{
@Override
public void write() {
System.out.println("产品A");
}
}
//具体产品B类
class ProductB extends Product{
@Override
public void write() {
System.out.println("产品B");
}
}
- 创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;
//工厂A类 - 生产A类产品
class FactoryA extends Factory{
@Override
public Product getProduct() {
return new ProductA();
}
}
//工厂B类 - 生产B类产品
class FactoryB extends Factory{
@Override
public Product getProduct() {
return new ProductB();
}
}
- 外界通过调用具体工厂类的方法,从而创建不同具体产品类的实例。
//生产工作流程
public class FactoryPattern {
public static void main(String[] args){
//客户要产品A
FactoryA mFactoryA = new FactoryA();
mFactoryA.getProduct().write();
//客户要产品B
FactoryB mFactoryB = new FactoryB();
mFactoryB.getProduct().write();
}
}
结果输出:
产品A
产品B
优缺点
优点
- 更符合开-闭原则
新增一种产品时,只需要增加相应的具体产品类和相应的工厂子类即可。
简单工厂模式需要修改工厂类的判断逻辑
- 符合单一职责原则
每个具体工厂类只负责创建对应的产品。
简单工厂中的工厂类存在复杂的switch逻辑判断
- 不使用静态工厂方法,可以形成基于继承的等级结构。
简单工厂模式的工厂类使用静态工厂方法
总结:工厂模式可以说是简单工厂模式的进一步抽象和拓展,在保留了简单工厂的封装优点的同时,让扩展变得简单,让继承变得可行,增加了多态性的体现。
缺点
- 添加新产品时,除了增加新产品类外,还要提供与之对应的具体工厂类,系统类的个数将成对增加,在一定程度上增加了系统的复杂度;同时,有更多的类需要编译和运行,会给系统带来一些额外的开销;
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
- 虽然保证了工厂方法内的对修改关闭,但对于使用工厂方法的类,如果要更换另外一种产品,仍然需要修改实例化的具体工厂类;
- 一个具体工厂只能创建一种具体产品。
应用场景
- 当一个类不知道它所需要的对象的类时
在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可; - 当一个类希望通过其子类来指定创建对象时
在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。 - 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
抽象工厂模式
工厂方法模式存在一个严重的问题:一个具体工厂只能创建一类产品;
而在实际过程中,一个工厂往往需要生产多类产品;
为了解决上述的问题,我们又使用了一种新的设计模式:抽象工厂模式。
定义
抽象工厂模式,即Abstract Factory Pattern,提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类;具体的工厂负责实现具体的产品实例。
抽象工厂模式与工厂方法模式最大的区别:抽象工厂中每个工厂可以创建多种类的产品;而工厂方法每个工厂只能创建一类。
作用
允许使用抽象的接口来创建一组相关产品,而不需要知道或关心实际生产出的具体产品是什么,这样就可以从具体产品中被解耦。
解决的问题
每个工厂只能创建一类产品
即工厂方法模式的缺点
原理
模式组成
组成(角色) | 关系 | 作用 |
---|---|---|
抽象产品族(AbstractProduct) | 抽象产品的父类 | 描述抽象产品的公共接口 |
抽象产品(Product) | 具体产品的父类 | 描述具体产品的公共接口 |
具体产品(Concrete Product) | 抽象产品的子类;工厂类创建的目标类 | 描述生产的具体产品 |
抽象工厂(Creator) | 具体工厂的父类 | 描述具体工厂的公共接口 |
具体工厂(Concrete Creator) | 抽象工厂的子类;被外界调用 | 描述具体工厂;实现FactoryMethod工厂方法创建产品的实例 |
UML类图
使用步骤
- 步骤1: 创建抽象工厂类,定义具体工厂的公共接口;
- 步骤2: 创建抽象产品族类 ,定义抽象产品的公共接口;
- 步骤3: 创建抽象产品类(继承抽象产品族类),定义具体产品的公共接口;
- 步骤4: 创建具体产品类(继承抽象产品类) & 定义生产的具体产品;
- 步骤5:创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;
- 步骤6:客户端通过实例化具体的工厂类,并调用其创建不同目标产品的方法创建不同具体产品类的实例。
实例
背景
公司目前有两个厂,一个厂能生产A类袜子,一个厂能生产B类鞋子;
现在要求两个厂都能生产鞋子和袜子。
使用步骤
- 创建抽象工厂类,定义具体工厂的公共接口
abstract class Factory{
public abstract Product getShose();
public abstract Product getSocks();
}
- 创建抽象产品族类 ,定义具体产品的公共接口;
abstract class AbstractProduct{
public abstract void write();
}
- 创建抽象产品类 ,定义具体产品的公共接口;
//鞋子产品抽象类
abstract class ShoseProduct extends AbstractProduct{
@Override
public abstract void write();
}
//袜子产品抽象类
abstract class SocksProduct extends AbstractProduct{
@Override
public abstract void write();
}
- 创建具体产品类(继承抽象产品类), 定义生产的具体产品;
//鞋子产品A类
class ShoseProductA extends ShoseProduct {
@Override
public void Show() {
System.out.println("鞋子产品A");
}
}
//鞋子产品B类
class ShoseProductB extends ShoseProduct {
@Override
public void Show() {
System.out.println("鞋子产品B");
}
}
//袜子产品A类
class SocksProductA extends SocksProduct {
@Override
public void Show() {
System.out.println("袜子产品A");
}
}
//袜子产品B类
class SocksProductB extends SocksProduct {
@Override
public void Show() {
System.out.println("袜子产品B");
}
}
- 创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;
//A厂 - 生产鞋子+袜子产品
class FactoryA extends Factory{
@Override
public Product getShose() {
return new ShoseProductA();
}
@Override
public Product getSocks() {
return new SocksProductA();
}
}
//B厂 - 生产鞋子+袜子产品
class FactoryB extends Factory{
@Override
public Product getShose() {
return new ShoseProductB();
}
@Override
public Product getSocks() {
return new SocksProductB();
}
}
- 客户端通过实例化具体的工厂类,并调用其创建不同目标产品的方法创建不同具体产品类的实例。
//生产工作流程
public class AbstractFactoryPattern {
public static void main(String[] args){
FactoryA mFactoryA = new FactoryA();
FactoryB mFactoryB = new FactoryB();
//A厂当地客户需要鞋子产品A
mFactoryA.getShose().write();
//A厂当地客户需要袜子产品A
mFactoryA.getSocks().write();
//B厂当地客户需要鞋子产品B
mFactoryB.getShose().write();
//B厂当地客户需要袜子产品B
mFactoryB.getSocks().write();
}
}
结果输出:
鞋子产品A
袜子产品A
鞋子产品B
袜子产品B
优缺点
优点
-
降低耦合
抽象工厂模式将具体产品的创建延迟到具体工厂的子类中,这样将对象的创建封装起来,可以减少客户端与具体产品类之间的依赖,从而使系统耦合度低,这样更有利于后期的维护和扩展; -
更符合开-闭原则
新增一种产品类时,只需要增加相应的具体产品类和相应的工厂子类即可简单工厂模式需要修改工厂类的判断逻辑
-
符合单一职责原则
每个具体工厂类只负责创建对应的产品简单工厂中的工厂类存在复杂的switch逻辑判断
-
不使用静态工厂方法,可以形成基于继承的等级结构。
简单工厂模式的工厂类使用静态工厂方法
缺点
抽象工厂模式很难支持新种类产品的变化。
这是因为抽象工厂接口中已经确定了可以被创建的产品集合,如果需要添加新产品,此时就必须去修改抽象工厂的接口,这样就涉及到抽象工厂类的以及所有子类的改变,这样也就违背了“开发——封闭”原则。
对于新的产品族符合开-闭原则;对于新的产品种类不符合开-闭原则,这一特性称为开-闭原则的倾斜性。
应用场景
- 一个系统不要求依赖产品类实例如何被创建、组合和表达的表达,这点也是所有工厂模式应用的前提。
- 这个系统有多个系列产品,而系统中只消费其中某一系列产品。
- 系统要求提供一个产品类的库,所有产品以同样的接口出现,客户端不需要依赖具体实现。
建造者模式
定义
隐藏创建对象的建造过程 & 细节,使得用户在不知对象的建造过程 & 细节的情况下,就可直接创建复杂的对象
- 用户只需要给出指定复杂对象的类型和内容;
- 建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)
作用
降低创建复杂对象的复杂度
隔离了创建对象的构建过程 & 表示
从而:
1.方便用户创建复杂的对象(不需要知道实现过程)
2.代码复用性 & 封装性(将对象构建过程和细节进行封装 & 复用)
比如买车,你告诉服务员你想要的车的参数,直接购买即可,而无需知道车是怎么制造的。
原理
UML类图
模式讲解
- 指挥者(Director)直接和客户(Client)进行需求沟通;
- 沟通后指挥者将客户创建产品的需求划分为各个部件的建造请求(Builder);
- 将各个部件的建造请求委派到具体的建造者(ConcreteBuilder);
- 各个具体建造者负责进行产品部件的构建;
- 最终构建成具体产品(Product)。
实例
背景
你希望买一台笔记本电脑,配置要求:显卡3090、内存64G、硬盘2T。
使用步骤
- 定义组装的过程(Builder):组装电脑的过程
public abstract class Builder {
//第一步:装显卡
//声明为抽象方法,具体由子类实现
public abstract void buildDC();
//第二步:装内存条
//声明为抽象方法,具体由子类实现
public abstract void buildMB();
//第三步:装硬盘
//声明为抽象方法,具体由子类实现
public abstract void buildHD();
//返回产品的方法:获得组装好的电脑
public abstract Computer getComputer();
}
- 电脑城老板委派任务给装机人员(Director)
public class Director{
//指挥装机人员组装电脑
public void Construct(Builder builder) {
builder.buildDC();
builder.buildMB();
builder.buildHD();
}
}
- 创建具体的建造者(ConcreteBuilder):装机人员
//装机人员1
public class ConcreteBuilder extend Builder{
//创建产品实例
Computer computer = new Computer();
//组装产品
@Override
public void buildDC(){
computer.Add("显卡")
}
@Override
public void buildMB(){
computer.Add("内存条")
}
@Override
public void buildHD(){
computer.Add("硬盘")
}
//返回组装成功的电脑
@Override
public Computer getComputer(){
return computer
}
}
- 定义具体产品类(Product):电脑
public class Computer{
//电脑组件的集合
private List<String> parts = new ArrayList<String>();
//用于将组件组装到电脑里
public void Add(String part){
parts.add(part);
}
public void Show(){
for (int i = 0;i<parts.size();i++){
System.out.println(“组件”+parts.get(i)+“装好了”);
}
System.out.println(“电脑组装完成,请验收”);
}
}
- 客户端调用-你到电脑城找老板买电脑
public class Builder Pattern{
public static void main(String[] args){
//找到该店的老板和装机人员
Director director = new Director();
Builder builder = new ConcreteBuilder();
//沟通需求后,老板叫装机人员去装电脑
director.Construct(builder);
//装完后,组装人员搬来组装好的电脑
Computer computer = builder.getComputer();
//组装人员展示电脑给小成看
computer.Show();
}
}
输出结果:
显卡装好了
内存条装好了
硬盘装好了
电脑组装完成,请验收
优缺点
优点
- 易于解耦
将产品本身与产品创建过程进行解耦,可以使用相同的创建过程来得到不同的产品。也就说细节依赖抽象。 - 易于精确控制对象的创建
将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰 - 易于拓展
增加新的具体建造者无需修改原有类库的代码,易于拓展,符合“开闭原则“。
每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。
缺点
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
应用场景
- 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性;
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
实际使用场景及总结
在java中StringBuilder 和StringBuffer,安卓的Dialog、Notification的构建,网络框架OkHttp等等都有使用。
很好理解,如果一个类的构造需要很多参数,而且这些参数并不都是必须的,此时你需要写出各种组合的构造参数就很麻烦,那么这种情况下就比较适合Builder。
使用方法简单归纳:
1.义一个静态内部类Builder,内部成员变量跟外部一样;
2.Builder通过一系列方法给成员变量赋值,并返回当前对象(this);
3.Builder类内部提供一个build方法方法或者create方法用于创建对应的外部类,该方法内部调用了外部类的一个私有化构造方法,该构造方法的参数就是内部类Builder;
4.外部类提供一个私有化的构造方法供内部类调用,在该构造函数中完成成员变量的赋值。
public class Person {
private String name;
private int age;
private double height;
private double weight;
private Person(Builder builder) {
this.name=builder.name;
this.age=builder.age;
this.height=builder.height;
this.weight=builder.weight;
}
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 double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
static class Builder{
private String name;
private int age;
private double height;
private double weight;
public Builder name(String name){
this.name=name;
return this;
}
public Builder age(int age){
this.age=age;
return this;
}
public Builder height(double height){
this.height=height;
return this;
}
public Builder weight(double weight){
this.weight=weight;
return this;
}
public Person build(){
return new Person(this);
}
}
}
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();
结构型
适配器模式
定义
定义一个包装类,用于包装不兼容接口的对象
包装类 = 适配器Adapter;
被包装对象 = 适配者Adaptee = 被适配的类
作用
把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法一起工作的两个类能够在一起工作。
适配器模式的形式分为:类的适配器模式 & 对象的适配器模式
解决的问题
原本由于接口不兼容而不能一起工作的那些类可以在一起工作。
原理
一、类的适配器模式
类的适配器模式是把适配的类的API转换成为目标类的API。
UML类图 & 组成
冲突:Target期待调用Request方法,而Adaptee并没有(这就是所谓的不兼容了)。
解决方案:为使Target能够使用Adaptee类里的SpecificRequest方法,故提供一个中间环节Adapter类(继承Adaptee & 实现Target接口),把Adaptee的API与Target的API衔接起来(适配)。
Adapter与Adaptee是继承关系,这决定了这个适配器模式是类的。
使用步骤
- 创建Target接口;
- 创建源类(Adaptee);
- 创建适配器类(Adapter)
- 定义具体使用目标类,并通过Adapter类调用所需要的方法从而实现目标
实例
有一个手机需要充电,但是目前只有220V的交流电,你需要一个电源适配器来将电转化为5V的电。
使用步骤
- 创建Target接口(期待得到的插头):能输出5V(将220V转换成5V)
public interface Target {
//将220V转换输出5V(原有插头(Adaptee)没有的)
public void convert5v();
}
- 创建源类(原有的插头) ;
class PowerPort220V{
//原有插头只能输出220V
public void output220v(){
}
}
- 创建适配器类(Adapter)
class Adapter220V extends PowerPort220V implements Target{
//期待的插头要求调用convert5v(),但原有插头没有
//因此适配器补充上这个方法名
//但实际上convert5v()只是调用原有插头的output220v()方法的内容
//所以适配器只是将output220v()作了一层封装,封装成Target可以调用的convert5v()而已
@Override
public void convert5v(){
this.output220v;
}
}
- 定义具体使用目标类,并通过Adapter类调用所需要的方法从而实现目标(不需要通过原有插头)
//手机类
class Phone {
@Override
public void Work() {
System.out.println("手机正常充电");
}
}
//通过Adapter类从而调用所需要的方法
public class AdapterPattern {
public static void main(String[] args){
Target mAdapter220V = new Adapter220V();
Phone phone = new Phone();
//用户拿着进口机器插上适配器(调用convert5v()方法)
//再将适配器插上原有插头(convert5v()方法内部调用output220v()方法输出220V)
//适配器只是个外壳,对外提供5V,但本质还是220V进行供电
mAdapter220V.convert5v();
phone.Work();
}
}
二、对象的适配器模式
与类的适配器模式相同,对象的适配器模式也是把适配的类的API转换成为目标类的API。
与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用委派关系连接到Adaptee类。
UML类图
冲突:Target期待调用Request方法,而Adaptee并没有(这就是所谓的不兼容了)。
解决方案:为使Target能够使用Adaptee类里的SpecificRequest方法,故提供一个中间环节Adapter类(包装了一个Adaptee的实例),把Adaptee的API与Target的API衔接起来(适配)。
Adapter与Adaptee是委派关系,这决定了适配器模式是对象的。
使用步骤
- 创建Target接口;
public interface Target {
//这是源类Adapteee没有的方法
public void Request();
}
- 创建源类(Adaptee) ;
public class Adaptee {
public void SpecificRequest(){
}
}
- 创建适配器类(Adapter)(不适用继承而是委派)
class Adapter implements Target{
// 直接关联被适配类
private Adaptee adaptee;
// 可以通过构造函数传入具体需要适配的被适配类对象
public Adapter (Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void Request() {
// 这里是使用委托的方式完成特殊功能
this.adaptee.SpecificRequest();
}
}
- 定义具体使用目标类,并通过Adapter类调用所需要的方法从而实现目标
public class AdapterPattern {
public static void main(String[] args){
//需要先创建一个被适配类的对象作为参数
Target mAdapter = new Adapter(new Adaptee());
mAdapter.Request();
}
}
优缺点
优点
- 更好的复用性
系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。 - 透明、简单
客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单 & 更直接 - 更好的扩展性
在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。 - 解耦性
将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码 - 符合开放-关闭原则
同一个适配器可以把适配者类和它的子类都适配到目标接口;可以为不同的目标接口实现不同的适配器,而不需要修改待适配类
缺点
过多的使用适配器,会让系统非常零乱,不易整体进行把握
类适配器优缺点
优点
- 使用方便,代码简化
仅仅引入一个对象,并不需要额外的字段来引用Adaptee实例
缺点
- 高耦合,灵活性低
使用对象继承的方式,是静态的定义方式
类适配器优缺点
优点
- 灵活性高、低耦合
采用 “对象组合”的方式,是动态组合方式
缺点
- 使用复杂
需要引入对象实例
特别是需要重新定义Adaptee行为时需要重新定义Adaptee的子类,并将适配器组合适配
应用场景
比如GridView、ListView与Adapter;
适配器的使用场景
- 系统需要复用现有类,而该类的接口不符合系统的需求,可以使用适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
- 多个组件功能类似,但接口不统一且可能会经常切换时,可使用适配器模式,使得客户端可以以统一的接口使用它们
类和对象适配器模式的使用场景
1.灵活使用时:选择对象的适配器模式
类适配器使用对象继承的方式,是静态的定义方式;而对象适配器使用对象组合的方式,是动态组合的方式。
2.需要同时配源类和其子类:选择对象的适配器
- 对于类适配器,由于适配器直接继承了Adaptee,使得适配器不能和Adaptee的子类一起工作,因为继承是静态的关系,当适配器继承了Adaptee后,就不可能再去处理 Adaptee的子类了;
- 对于对象适配器,一个适配器可以把多种不同的源适配到同一个目标。换言之,同一个适配器可以把源类和它的子类都适配到目标接口。因为对象适配器采用的是对象组合的关系,只要对象类型正确,是不是子类都无所谓。
3.需要重新定义Adaptee的部分行为:选择类适配器
- 对于类适配器,适配器可以重定义Adaptee的部分行为,相当于子类覆盖父类的部分实现方法。
- 对于对象适配器,要重定义Adaptee的行为比较困难,这种情况下,需要定义Adaptee的子类来实现重定义,然后让适配器组合子类。虽然重定义Adaptee的行为比较困难,但是想要增加一些新的行为则方便的很,而且新增加的行为可同时适用于所有的源。
4.仅仅希望使用方便时:选择类适配器
- 对于类适配器,仅仅引入了一个对象,并不需要额外的引用来间接得到Adaptee。
- 对于对象适配器,需要额外的引用来间接得到Adaptee。
总结
建议尽量使用对象的适配器模式,多用合成/聚合、少用继承。
当然,具体问题具体分析,根据需要来选用合适的实现方式
外观模式
定义
定义了一个高层、统一的接口,外部与通过这个统一的接口对子系统中的一群接口进行访问。
通过创建一个统一的外观类,用来包装子系统中一个 / 多个复杂的类,客户端可通过调用外观类的方法来调用内部子系统中所有方法
作用
- 实现客户类与子系统类的松耦合
- 降低原有系统的复杂度
- 提高了客户端使用的便捷性,使得客户端无须关心子系统的工作细节,通过外观角色即可调用相关功能。
1.引入外观角色之后,用户只需要与外观角色交互;
2.用户与子系统之间的复杂逻辑关系由外观角色来实现
解决的问题
- 避免了系统与系统之间的高耦合度
- 使得复杂的子系统用法变得简单
原理
UML类图
实例
小米智能家居:当你外出时,让小爱关掉空调、灯,同时叫醒扫地机器人开始工作,从而省去自己手动操作这一系列麻烦的事。
使用步骤
- 家居电器类
//灯类
public class Light {
public void on(){
System.out.println("打开了灯....");
}
public void off(){
System.out.println("关闭了灯....");
}
}
//空调类
public class AirCondition {
public void on(){
System.out.println("打开了空调....");
}
public void off(){
System.out.println("关闭了空调....");
}
}
//扫地机器人
public class CleanRobot {
public void on(){
System.out.println("启动了扫地机器人....");
}
public void off(){
System.out.println("关闭了扫地机器人....");
}
}
- 外观类:小爱同学
public class Facade{
Light light;
AirCondition airCondition;
CleanRobot cleanRobot ;
//传参
public Facade(Light light, AirCondition airCondition, CleanRobot cleanRobot){
this.light = light;
this.airCondition = airCondition ;
this.cleanRobot = cleanRobot;
}
//起床后一键开电器
public void on{
System.out.prinln("我回来了,小爱同学");
light.on();
television.on();
aircondition.off();
}
//睡觉时一键关电器
public void off {
System.out.prinln("我出去了,小爱同学");
light.off();
television.off();
aircondition.on();
}
}
- 客户端调用:你命令小爱同学
public class Facade Pattern{
public static void main(String[] args){
//实例化电器类
Light light = new SubSystemA_Light();
AirCondition airCondition = new AirCondition();
CleanRobot cleanRobot = new CleanRobot();
//传参
Facade facade = new Facade(light,airCondition,cleanRobot);
//客户端直接与外观对象进行交互
facade.on();
facade.off();
}
}
结果输出:
我回来了,小爱同学
打开了灯....
打开了空调....
关闭了扫地机器人....
我出去了,小爱同学
关闭了灯....
关闭了空调....
启动了扫地机器人....
优缺点
优点
- 降低了客户类与子系统类的耦合度,实现了子系统与客户之间的松耦合关系
1.只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类
2.减少了与子系统的关联对象,实现了子系统与客户之间的松耦合关系,松耦合使得子系统的组件变化不会影响到它的客户。
- 外观模式对客户屏蔽了子系统组件,从而简化了接口,减少了客户处理的对象数目并使子系统的使用更加简单。
1.引入外观角色之后,用户只需要与外观角色交互;
2.用户与子系统之间的复杂逻辑关系由外观角色来实现
- 降低原有系统的复杂度和系统中的编译依赖性,并简化了系统在不同平台之间的移植过程
因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
缺点
- 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
- 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。
应用场景
- 要为一个复杂的子系统对外提供一个简单的接口
- 提供子系统的独立性
- 客户程序与多个子系统之间存在很大的依赖性
引入外观类将子系统与客户以及其他子系统解耦,可以提高子系统的独立性和可移植性。
- 在层次化结构中,可以使用外观模式定义系统中每一层的入口
层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。
与适配器模式的区别
适配器模式是将一个对象包装起来以改变其接口,而外观模式是将一群对象 ”包装“起来以简化其接口。它们的意图是不一样的,适配器是将接口转换为不同接口,而外观模式是提供一个统一的接口来简化接口。
静态代理模式
定义
给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用。
1.代理对象:起到中介作用,连接客户端和目标对象
例子:电脑桌面的快捷方式。电脑对某个程序提供一个快捷方式(代理对象),快捷方式连接客户端和程序,客户端通过操作快捷方式就可以操作那个程序。
作用
通过引入代理对象的方式来间接访问目标对象
解决的问题
防止直接访问目标对象给系统带来的不必要复杂性。
原理
UML类图 & 组成
实例
背景
你想买一台iphone 14 pro max,但是目前国内还没有,需要找代购从国外购买。
代购(代理对象) 代替 我(真实对象) 去买Mac(间接访问的操作)
使用步骤
- 创建抽象对象接口(Subject):声明你(真实对象)需要让代购(代理对象)帮忙做的事(买Mac)
public interface Subject {
public void buyIphone();
}
- 创建真实对象类(RealSubject),即”我“
public class RealSubject implement Subject{
@Override
public void buyIphone() {
System.out.println(”买一台iphone“);
}
}
- 创建代理对象类(Proxy),即”代购“,并通过代理类创建真实对象实例并访问其方法
public class Proxy implements Subject{
@Override
public void buyIphone{
//引用并创建真实对象实例,即”我“
RealSubject realSubject = new RealSubject();
//调用真实对象的方法,进行代理购买iphone
realSubject.buyIphone();
//代理对象额外做的操作
this.WrapIphone();
}
public void WrapIphone(){
System.out.println(”用盒子包装好Iphone“);
}
}
- 客户端调用
public class ProxyPattern {
public static void main(String[] args){
Subject proxy = new Proxy();
proxy.buyIphone();
}
}
结果输出:
买一台iphone
用盒子包装好Iphone
优缺点
优点
- 协调调用者和被调用者,降低了系统的耦合度
- 代理对象作为客户端和目标对象之间的中介,起到了保护目标对象的作用
缺点
- 由于在客户端和真实主题之间增加了代理对象,因此会造成请求的处理速度变慢;
- 实现代理模式需要额外的工作(有些代理模式的实现非常复杂),从而增加了系统实现的复杂度。
应用场景
动态代理模式
为什么要使用动态代理
背景
代理模式中的静态代理模式存在一些特点:
- 1个静态代理 只服务1种类型的目标对象
- 若要服务多类型的目标对象,则需要为每种目标对象都实现一个静态代理对象
冲突
在目标对象较多的情况下,若采用静态代理,则会出现 静态代理对象量多、代码量大,从而导致代码复杂的问题。
解决方案
采用 动态代理模式。
原理
- 设计动态代理类(DynamicProxy)时,不需要显式实现与目标对象类(RealSubject)相同的接口,而是将这种实现推迟到程序运行时由 JVM来实现
1.即:在使用时再创建动态代理类 & 实例
2.静态代理则是在代理类实现时就指定与目标对象类(RealSubject)相同的接口
- 通过Java 反射机制的method.invoke(),通过调用动态代理类对象方法,从而自动调用目标对象的方法
优缺点
优点
- 只需要1个动态代理类就可以解决创建多个静态代理的问题,避免重复、多余代码
- 更强的灵活性
1.设计动态代理类(DynamicProxy)时,不需要显式实现与目标对象类(RealSubject)相同的接口,而是将这种实现推迟到程序运行时由 JVM来实现
2.在使用时(调用目标对象方法时)才会动态创建动态代理类 & 实例,不需要事先实例化
缺点
- 效率低
相比静态代理中 直接调用目标对象方法,动态代理则需要先通过Java反射机制 从而 间接调用目标对象方法 - 应用场景局限
因为 Java 的单继承特性(每个代理类都继承了 Proxy 类),即只能针对接口 创建 代理类,不能针对类 创建代理类
即只能动态代理 实现了接口的类
应用场景
- 基于静态代理应用场景下,需要代理对象数量较多的情况下使用动态代理
- AOP 领域
1.定义:即 Aspect Oriented Programming = 面向切面编程,是OOP的延续、函数式编程的一种衍生范型
2.作用:通过预编译方式和运行期动态代理实现程序功能的统一维护。
3.优点:降低业务逻辑各部分之间的耦合度 、 提高程序的可重用性 & 提高了开发的效率
4.具体应用场景:日志记录、性能统计、安全控制、异常处理等
与静态代理模式的区别
实例
背景:小成 希望买一台最新的顶配 Mac 电脑;小何希望买一台 iPhone
冲突:国内还没上,只有美国才有
解决方案:寻找一个代购一起进行购买
1.即1个代购(动态代理对象)同时 代替 小成 & 小何(目标对象) 去买Mac(间接访问的操作)
2.该代购是代购任何商品 = 什么人有什么需求就会去代购任何东西(动态代理)
使用步骤
- 声明 调用处理器类
- 声明目标对象类的抽象接口
- 声明目标对象类
- 通过动态代理对象,调用目标对象的方法
实际使用
- 声明 调用处理器类
<-- 作用 -->
// 1. 生成 动态代理对象
// 2. 指定 代理对象运行目标对象方法时需要完成的 具体任务
// 注:需实现InvocationHandler接口 = 调用处理器 接口
// 所以称为 调用处理器类
public class DynamicProxy implements InvocationHandler {
// 声明代理对象
// 作用:绑定关系,即关联到哪个接口(与具体的实现类绑定)的哪些方法将被调用时,执行invoke()
private Object ProxyObject;
public Object newProxyInstance(Object ProxyObject){
this.ProxyObject =ProxyObject;
return Proxy.newProxyInstance(ProxyObject.getClass().getClassLoader(),
ProxyObject.getClass().getInterfaces(),this);
// Proxy类 = 动态代理类的主类
// Proxy.newProxyInstance()作用:根据指定的类装载器、一组接口 & 调用处理器 生成动态代理类实例,并最终返回
// 参数说明:
// 参数1:指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
// 参数2:指定目标对象的实现接口
// 即要给目标对象提供一组什么接口。若提供了一组接口给它,那么该代理对象就默认实现了该接口,这样就能调用这组接口中的方法
// 参数3:指定InvocationHandler对象。即动态代理对象在调用方法时,会关联到哪个InvocationHandler对象
}
// 复写InvocationHandler接口的invoke()
// 动态代理对象调用目标对象的任何方法前,都会调用调用处理器类的invoke()
@Override
public Object invoke(Object proxy, Method method, Object[] args)
// 参数说明:
// 参数1:动态代理对象(即哪个动态代理对象调用了method()
// 参数2:目标对象被调用的方法
// 参数3:指定被调用方法的参数
throws Throwable {
System.out.println("代购出门了");
Object result = null;
// 通过Java反射机制调用目标对象方法
result = method.invoke(ProxyObject, args);
return result;
}
}
- 声明目标对象的抽象接口
public interface Subject {
// 定义目标对象的接口方法
// 代购物品
public void buybuybuy();
}
- 声明目标对象类
// 小成,真正的想买Mac的对象 = 目标对象 = 被代理的对象
// 实现抽象目标对象的接口
public class Buyer1 implements Subject {
@Override
public void buybuybuy() {
System.out.println("小成要买Mac");
}
}
// 小何,真正的想买iPhone的对象 = 目标对象 = 被代理的对象
// 实现抽象目标对象的接口
public class Buyer2 implements Subject {
@Override
public void buybuybuy() {
System.out.println("小何要买iPhone");
}
}
- 通过动态代理对象,调用目标对象的方法
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1. 创建调用处理器类对象
DynamicProxy DynamicProxy = new DynamicProxy();
// 2. 创建目标对象对象
Buyer1 mBuyer1 = new Buyer1();
// 3. 创建动态代理类 & 对象:通过调用处理器类对象newProxyInstance()
// 传入上述目标对象对象
Subject Buyer1_DynamicProxy = (Subject) DynamicProxy.newProxyInstance(mBuyer1);
// 4. 通过调用动态代理对象方法从而调用目标对象方法
// 实际上是调用了invoke(),再通过invoke()里的反射机制调用目标对象的方法
Buyer1_DynamicProxy.buybuybuy();
// 以上代购为小成代购Mac
// 以下是代购为小何代购iPhone
Buyer2 mBuyer2 = new Buyer2();
Subject Buyer2_DynamicProxy = (Subject) DynamicProxy.newProxyInstance(mBuyer2);
Buyer2_DynamicProxy.buybuybuy();
}
}
结果输出:
总结
Java动态代理的两个核心:Proxy类和InvocationHandler接口。
一、InvocationHandler
作用: 提供动态执行增强(加)逻辑的方法。
invoke()方法:
1.Object proxy: 代理类代理的真实代理对象。
2.Method method: 所要调用某个对象真实的方法的Method对象。
3.Object[] args: 代理类对象方法传递的参数。
注:invoke()方法返回值通常境况下返回真实对象方法的返回结果,但也可以返回proxy,因为它是真实代理对象,所以可以通过这个返回对象对真实的对象做各种操作。
二、Proxy
作用: 用来创建代理对象。
newProxyInstance()方法:
1.真实对象的类加载器
2.代理类实现的接口
3.代理对象调用方法时会关联到哪一个InvocationHandler上,并最终由其调用。
可以参考:https://blog.csdn.net/yaomingyang/article/details/80981004
https://blog.csdn.net/yaomingyang/article/details/81040390
简单理解:
1.常见impl对象
2.将impl对象与handler绑定
3.通过handler获得增强后的impl对象
4.调用增强对象的实际方法
应用场景
1.通过代理WindowManager来获取锁屏状态
import android.app.KeyguardManager;
import android.content.Context;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class WindowManagerProxy implements InvocationHandler{
private static final String TAG = WindowManagerProxy.class.getSimpleName();
private final Object realObject;
private static WindowManagerProxy proxy;
private WindowManagerProxy(Object realObject) {
this.realObject = realObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("inKeyguardRestrictedInputMode")) {
return false;
}
if (method.getName().equals("isKeyguardLocked")) {
return false;
}
return Reflections.invokeMethod(method, realObject, args);
}
public static boolean inKeyguardRestrictedInputMode(Context context) {
if (proxy == null) {
KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
return km.inKeyguardRestrictedInputMode();
}
Method method = Reflections.findMethodNoThrow(
proxy.realObject.getClass(), "isKeyguardLocked");
if (!checkNonNull(method, "can not find isKeyguardLocked method in " + proxy.realObject)) {
method = Reflections.findMethodNoThrow(
proxy.realObject.getClass(), "inKeyguardRestrictedInputMode");
}
return checkNonNull(method, "can not find inKeyguardRestrictedInputMode method in " + proxy.realObject)
&& (boolean) Reflections.invokeMethodNoThrow(method, proxy.realObject);
}
static boolean startProxy() {
Class<?> WindowManagerCls = Reflections.loadClassNoThrow("android.view.WindowManagerGlobal");
if (!checkNonNull(WindowManagerCls, "WindowManagerCls load failed")) {
return false;
}
Method windowManagerServiceMethod = Reflections.findMethodNoThrow(WindowManagerCls,
"getWindowManagerService");
if (!checkNonNull(windowManagerServiceMethod, "can not find windowManagerService method")) {
return false;
}
Object targetWm = Reflections.invokeMethodNoThrow(windowManagerServiceMethod, null);
if (!checkNonNull(targetWm, "targetWm can not be null")) {
return false;
}
Class[] interfaces = Reflections.collectInterfaces(targetWm.getClass());
proxy = new WindowManagerProxy(targetWm);
Object proxyWm = Proxy.newProxyInstance(targetWm.getClass().getClassLoader(), interfaces, proxy);
Field field = Reflections.findFieldNoThrow(WindowManagerCls, "sWindowManagerService");
Reflections.setValueNoThrow(field, null, proxyWm);
return true;
}
private static boolean checkNonNull(Object object, String message) {
if (object == null) {
if (LogHelper.LOGGABLE) {
Log.e(TAG, message);
}
return false;
}
return true;
}
}
2.通过代理ActivityManager判断栈顶activity
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.os.Build;
import android.text.TextUtils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class IActivityManagerHook {
private static final String TAG = IActivityManagerHook.class.getSimpleName();
@SuppressLint("PrivateApi")
public static void iActivityManagerHook() {
if (Build.VERSION.SDK_INT < 24 || Build.VERSION.SDK_INT > 28) {
return;
}
try {
Class singletonCls = Class.forName("android.util.Singleton");
Object iActivityManagerSingleton = null;
for (Field field : ActivityManager.class.getDeclaredFields()) {
if (field.getType() == singletonCls) {
field.setAccessible(true);
iActivityManagerSingleton = field.get(null);
break;
}
}
if (iActivityManagerSingleton == null) {
return;
}
Field instanceField = singletonCls.getDeclaredField("mInstance");
instanceField.setAccessible(true);
Object iActivityManager = instanceField.get(iActivityManagerSingleton);
if (iActivityManager == null) {
return;
}
IActivityManagerProxy proxy = new IActivityManagerProxy(iActivityManager);
Class iActivityManagerCls = Class.forName("android.app.IActivityManager");
Object iActivityManageProxy =
Proxy.newProxyInstance(iActivityManagerCls.getClassLoader(),
new Class[]{iActivityManagerCls}, proxy);
instanceField.set(iActivityManagerSingleton, iActivityManageProxy);
} catch (Exception e) {
}
}
private static class IActivityManagerProxy implements InvocationHandler {
private Object instance;
public IActivityManagerProxy(Object instance) {
super();
this.instance = instance;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (args == null) {
args = new Object[]{};
}
if (TextUtils.equals(method.getName(), "isTopOfTask")) {
try {
boolean result = (boolean) method.invoke(instance, args);
return result;
} catch (Exception e) {
return false;
}
}
if (TextUtils.equals(method.getName(), "reportSizeConfigurations")) {
try {
return method.invoke(instance, args);
} catch (Exception e) {
return null;
}
}
return method.invoke(instance, args);
}
}
}
3.通过代理WifiManager解决mac获取的隐私收集问题
public class HookUtils {
private static WifiInfo cacheWifiInfo = null;
public static void hookMacAddress(String tag, Context context) {
try {
Class<?> iWifiManager = Class.forName("android.net.wifi.IWifiManager");
Field serviceField = WifiManager.class.getDeclaredField("mService");
serviceField.setAccessible(true);
WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
// real mService
Object realIwm = serviceField.get(wifi);
// replace mService with Proxy.newProxyInstance
serviceField.set(wifi, Proxy.newProxyInstance(iWifiManager.getClassLoader(),
new Class[]{iWifiManager},
new InvocationHandler(tag, "getConnectionInfo", realIwm)));
Log.i(tag, "wifiManager hook success");
} catch (Exception e) {
Log.e(tag, "printStackTrace:" + e.getMessage());
e.printStackTrace();
}
}
public static class InvocationHandler implements java.lang.reflect.InvocationHandler {
private final String tag;
private final String methodName;
private Object real;
public InvocationHandler(String tag, String methodName, Object real) {
this.real = real;
this.methodName = methodName;
this.tag = tag;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d(tag, "method invoke " + method.getName());
if (methodName.equals(method.getName())) {
if (cacheWifiInfo != null) {
Log.d(tag, "cacheWifiInfo:" + cacheWifiInfo);
return cacheWifiInfo;
}
WifiInfo wifiInfo = null;
try {
Class cls = WifiInfo.class;
wifiInfo = (WifiInfo) cls.newInstance();
Field mMacAddressField = cls.getDeclaredField("mMacAddress");
mMacAddressField.setAccessible(true);
mMacAddressField.set(wifiInfo, "");
cacheWifiInfo = wifiInfo;
Log.d(tag, "wifiInfo:" + wifiInfo);
} catch (Exception e) {
Log.e(tag, "WifiInfo error:" + e.getMessage());
}
return wifiInfo;
} else {
return method.invoke(real, args);
}
}
}
}
行为型
策略模式
定义
定义一系列算法,将每个算法封装到具有公共接口的一系列策略类中,从而使它们可以相互替换 & 让算法可在不影响客户端的情况下发生变化。
简单来说:准备一组算法 & 将每一个算法封装起来,让外部按需调用 & 使得互换
作用
将算法的责任和本身进行解耦,使得:
1.算法可独立于使用外部而变化
2.客户端方便根据外部条件选择不同策略来解决不同问题
策略模式仅仅封装算法(包括添加 & 删除),但策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定
原理
UML类图
实例
背景
你开发了一个app,不同节日使用配套的图标。
使用步骤
- 定义抽象策略角色(Strategy):app所有图标的共同接口
public abstract class Strategy {
public abstract void show();
}
- 定义具体策略角色(Concrete Strategy):每个节日具体使用的图标
class StrategyA extends Strategy{
@Override
public void show() {
System.out.println("春节使用图标A");
}
}
class StrategyB extends Strategy{
@Override
public void show() {
System.out.println("中秋节使用图标B");
}
}
class StrategyC extends Strategy{
@Override
public void show() {
System.out.println("圣诞节使用图标C");
}
}
- 定义环境角色(Context):用于连接上下文,即不同节日
class Context {
//持有抽象策略角色的引用
private Strategy strategy;
public Context(String festival) {
switch ( festival) {
case "A":
strategy = new StrategyA();
break;
case "B":
strategy = new StrategyB();
break;
case "C":
strategy = new StrategyC();
break;
}
}
//向客户展示促销活动
public void show(){
strategy.show();
}
}
- 客户端调用-让app替换图标
public class StrategyPattern{
public static void main(String[] args){
Context context;
//春节来了,使用春节图标
System.out.println("对于春节:");
context = new Context("A");
context.show();
//中秋节来了,使用中秋节图标
System.out.println("对于中秋节:");
context = new Context("B");
context.show();
//圣诞节来了,使用圣诞节图标
System.out.println("对于圣诞节:");
context = new Context("C");
context.show();
}
}
结果输出:
对于春节:
春节使用图标A
对于中秋节:
中秋节使用图标B
对于圣诞节:
圣诞节使用图标C
优缺点
优点
- 策略类之间可以自由切换
由于策略类都实现同一个接口,所以使它们之间可以自由切换。 - 易于扩展
增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“ - 避免使用多重条件选择语句(if else),充分体现面向对象设计思想。
缺点
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
应用场景
动态选择多种复杂行为
该行为可理解为:
1.复杂的算法 / 数据结构
2.类的行为 / 方法
注:提高了行为的保密
策略模式的精髓就在于,你传入一个类,后面的处理就能按照这个类的实现去做。以动画为例,设置不同的插值器对象,就可以得到不同的变化曲线;以返回值解析为例,传入什么样的解析器,就可以把二进制数据转换成什么格式的数据,比如String、Json、XML。
Android中的应用
下面说说在Android里面的应用。在Android里面策略模式的其中一个典型应用就是Adapter,在我们平时使用的时候,一般情况下我们可能继承BaseAdapter,然后实现不同的View返回,GetView里面实现不同的算法。外部使用的时候也可以根据不同的数据源,切换不同的Adapter。
观察者模式
定义
- 定义对象间的一种一对多的依赖关系;
- 当1个对象的状态发生改变时,所有依赖于它的对象都将得到通知 & 自动更新对应操作。
又称:发布 / 订阅模式
作用
常变对象 与不常变对象之间存在依赖关系的前提下,不常变对象 需随 常变对象经常改变逻辑的问题。即解耦 常变对象 与不常变对象之间的依赖关系
如:常变的UI层 与 基本不变的具体业务逻辑
原理
UML类图
注:Java API有内置的观察者模式类:java.util.Observable 类和 java.util.Observer 接口
对应: Subject 和 Observer 的角色;
实例
背景
顾客到饭店吃饭。
即可总结为:被观察者 (Observable) 通过 订阅(Subscribe) 按顺序发送事件 给观察者 (Observer), 观察者(Observer) 按顺序接收事件 & 作出对应的响应动作。具体如下图:(类似流水线般流动 & 处理。)
应用场景
Android中的观察者模式应该是用的非常频繁的一种模式了,对于这个模式的使用场景就一句话:你想在某个对象发生变化时,立刻收到通知。
Android里面的各种监听器,也都属于观察者模式,比如触摸、点击、按键等,在数据库变化或者listview内容有变化时使用notifyDataSetChanged()方法,ContentProvider和广播接收者也有观察者模式的身影,可以说是无处不在。
除此之外,现在很多基于观察者模式的第三方框架也是非常多,比如EventBus、RxJava等等,都是对观察者模式的深入使用,感兴趣的同学可以研究一下。
模板方法模式
定义
定义一个模板结构,将具体内容延迟到子类去实现。
作用
在不改变模板结构的前提下在子类中重新定义模板中的内容。
模板方法模式是基于”继承“的;
解决的问题
- 提高代码复用性
将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中 - 实现了反向控制
通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 & 符合“开闭原则”
原理
UML类图
实例
背景
背景:小成希望学炒菜:手撕包菜 & 蒜蓉炒菜心
冲突:两道菜的炒菜步骤有的重复有的却差异很大,记不住
解决方案:利用代码记录下来
使用步骤
- 创建抽象模板结构(Abstract Class):炒菜的步骤
public abstract class Abstract Class {
//模板方法,用来控制炒菜的流程 (炒菜的流程是一样的-复用)
//申明为final,不希望子类覆盖这个方法,防止更改流程的执行顺序
final void cookProcess(){
//第一步:倒油
this.pourOil();
//第二步:热油
this.HeatOil();
//第三步:倒蔬菜
this.pourVegetable();
//第四步:倒调味料
this.pourSauce();
//第五步:翻炒
this.fry();
}
//定义结构里哪些方法是所有过程都是一样的可复用的,哪些是需要子类进行实现的
//第一步:倒油是一样的,所以直接实现
void pourOil(){
System.out.println("倒油");
}
//第二步:热油是一样的,所以直接实现
void HeatOil(){
System.out.println("热油");
}
//第三步:倒蔬菜是不一样的(一个下包菜,一个是下菜心)
//所以声明为抽象方法,具体由子类实现
abstract void pourVegetable();
//第四步:倒调味料是不一样的(一个下辣椒,一个是下蒜蓉)
//所以声明为抽象方法,具体由子类实现
abstract void pourSauce();
//第五步:翻炒是一样的,所以直接实现
void fry();{
System.out.println("炒啊炒啊炒到熟啊");
}
}
- 创建具体模板(Concrete Class),即”手撕包菜“和”蒜蓉炒菜心“的具体步骤
//炒手撕包菜的类
public class ConcreteClass_BaoCai extend Abstract Class{
@Override
public void pourVegetable(){
System.out.println(”下锅的蔬菜是包菜“);
}
@Override
public void pourSauce(){
System.out.println(”下锅的酱料是辣椒“);
}
}
//炒蒜蓉菜心的类
public class ConcreteClass_CaiXin extend Abstract Class{
@Override
public void pourVegetable(){
System.out.println(”下锅的蔬菜是菜心“);
}
@Override
public void pourSauce(){
System.out.println(”下锅的酱料是蒜蓉“);
}
}
- 客户端调用-炒菜了
public class Template Method{
public static void main(String[] args){
//炒 - 手撕包菜
ConcreteClass_BaoCai BaoCai = new ConcreteClass_BaoCai();
BaoCai.cookProcess();
//炒 - 蒜蓉菜心
ConcreteClass_ CaiXin = new ConcreteClass_CaiXin();
CaiXin.cookProcess();
}
}
结果输出:
倒油
热油
下锅的蔬菜是包菜
下锅的酱料是辣椒
炒啊炒啊炒到熟
倒油
热油
下锅的蔬菜是菜心
下锅的酱料是蒜蓉
炒啊炒啊炒到熟
优缺点
优点
- 提高代码复用性
将相同部分的代码放在抽象的父类中 - 提高了拓展性
将不同的代码放入不同的子类中,通过对子类的扩展增加新的行为 - 实现了反向控制
通过一个父类调用其子类的操作,通过对子类的扩展增加新的行为,实现了反向控制 & 符合“开闭原则”
缺点
引入了抽象类,每一个不同的实现都需要一个子类来实现,导致类的个数增加,从而增加了系统实现的复杂度。
应用场景
- 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现;
- 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复;
- 控制子类的扩展。
模板方法模式的使用场景也是一句话:流程确定,具体实现细节由子类完成。
这种模式也相当常见,比如我们的activity或者fragment,在base中定义了一些列的周期函数,基本把整个activity的框架全部定义好了,所以我们只需要进程基类的activity或者fragment方法,然后在定义好的周期函数中实现我们需要的内容就可以,而不用关心整个activity的启动所有过程。
转载至:
作者:Carson带你学安卓
链接:https://www.jianshu.com/p/6e5eda3a51af
链接:https://www.jianshu.com/p/b8c578b07fbc
链接:https://www.jianshu.com/p/e55fbddc071c
链接:https://www.jianshu.com/p/d0c444275827
链接:https://www.jianshu.com/p/7deb64f902db
链接:https://www.jianshu.com/p/be290ccea05a
链接:https://www.jianshu.com/p/9d0575311214
链接:https://www.jianshu.com/p/1b027d9fc005
链接:https://www.jianshu.com/p/a8aa6851e09e
链接:https://www.jianshu.com/p/5dc416ea58a2
链接:https://www.jianshu.com/p/0c62bf587b9c
链接:https://www.jianshu.com/p/8b3152c77245
链接:https://www.jianshu.com/p/a3474f4fee57
来源:简书
作者:闲庭
链接:https://www.jianshu.com/p/731dfac62070
来源:简书
关于Android项目中可以用到的设计模式,可以参见:
https://github.com/simple-android-framework-exchange/android_design_patterns_analysis
https://www.cnblogs.com/qianxudetianxia/category/312863.html