〇.引言
(0.1)Java重载、重写(覆盖)
1、同一个类中方法重载
(1.1)简介
对于同一个类,如果这个类中有两个或多个重名的方法,但是方法的参数个数、类型、顺序至少有一个不一样,这时候方法重载。
(1.2)特点
方法重载是对不同数据类型的数据实时相似的操作
(1.3)示例
public class Test{
public void a( ){
};
public void a( int i){
};
public void a(String j){
};
}
在Test类中有两个方法,名字都相同,都是a。在调用方法a时,如果不传参数,则系统会自动调用第一个方法a;如果传入一个 int 类型的参数,则系统调用第二个方法a;如果传入一个String类型的参数,则系统调用第三个方法a。
2、子类对父类方法重写/覆盖
(2.1)简介
当一个子类继承一父类,而子类中的方法与父类中的方法的名称,参数个数、类型都完全一致时,就称子类中的这个方法重写了父类中的方法。
重写也是覆盖 override。
(2.2)特点
- 重写必须要有继承关系,且函数名、参数列表、返回值必须相同
- 当子类重写父类的函数,那么子类的对象如果调用该函数,一定是重写后的函数。如果想调用父类的函数,可通过super关键字进行调用。
- 继承可以使子类对父类方法进行增强。
(2.3)示例
class Animal{
int x=1;
String name;
void eat(){
System.out.println("吃东西");
}
void shout(){
System.out.println("我是动物");
}
}
class Dog extends Animal{
void eat(){
System.out.println("啃骨头");
}
void shout(){
System.out.println("旺旺");
}
void eat(String food){
System.out.println("吃:"+food);
}
}
class Cat extends Animal{
void eat(){
System.out.println("吃老鼠");
}
void shout(){
System.out.println("喵喵");
}
}
class Demo9{
public static void main(String[] args){
Dog d=new Dog();
d.shout();
d.eat();
Cat c=new Cat();
c.shout();
c.eat();
System.out.println();
}
3、重载和重写的区别
重载 | 重写 | |
---|---|---|
位置 | 所有重载函数必须在同一个类中 | 继承关系中,子类重写父类的方法 |
特点 | 函数名相同,参数列表不同,其他无关 | 函数名相同、参数列表相同、子类的返回值类型小鱼等于父类返回值类型 |
一.定义
设计模式是某类特定问题的代码设计解决方案,是一套代码设计的经验总结。是前任针对某类问题的代码设计经验
二.作用
- 提高代码复用率,降低开发成本和周期
- 提高代码可维护性、可拓展性
- 使代码更加优雅
- 让代码更容易被他人理解
三.七大设计原则
(3.1)单一职责原则
一个类=只有一个引起它变化的原因。(只负责担任一个职责)
如果一个类承担的职责过多,即耦合性太高=一个职责的变化可能会影响到其他的职责
(3.2)开放封闭原则
一个实体(类、函数、模块等)应该对外扩展开放,对内修改关闭
1、即每次发生变化时,要通过添加新的代码来增强现有类型的行为,而不是修改原有的代码。
2、符合开放封闭原则的最好方式是提供一个固有的接口,然后让所有可能发生变化的类实现该接口,让固定的接口与相关对象进行交互。
(3.3)里氏代替原则
子类必须替换掉它们的父类型。
1、在软件开发过程中,子类替换父类后,程序的行为是一样的。
2、只有当子类替换掉父类后软件的功能不受影响时,父类才能真正地被复用,而子类也可以在父类的基础上添加新的行为。
(3.4)依赖倒置原则
细节应该依赖于抽象,而抽象不应该依赖于细节。
依赖倒置的本质原则就是 :通过抽象(接口或抽象类)使各个类或模块实现彼此独立,互不影响,实现模块间的松耦合。
- 每个类尽量都要有接口或抽象类,或者抽象类和接口两者都具备。
- 变量的显示类型 尽量是接口或者抽象类。
- 任何类尽量不从具体类派生。
- 尽量不要覆写基类的方法
- 结合里氏替换原则。 父类出现的地方子类就能出现
所谓的的 “面向接口编程,而不是面向实现编程”。这样可以降低客户与具体实现的耦合。
(3.5)接口隔离原则
使用多个专门功能的接口,而不是使用单一的总接口。
不要让一个单一的接口承担过多的职责,而应把每个职责分离到多个专门的接口中,进行接口分离。
(3.6)合成复用原则
在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分。
新对象通过向这些对象的委派达到复用已用功能的目的。简单地说,就是要尽量使用合成/聚合,尽量不要使用继承。
(3.7)最少知识原则(迪米特法则)
一个模块或对象应尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立,这样当一个模块修改时,影响的模块就会越少,扩展起来更加容易。
1、关于迪米特法则的其他描述:只与你直接的朋友们通信;不要跟“陌生人”说话。
2、外观模式(Facade Pattern)和中介者模式(Mediator Pattern)就使用了迪米特法则。
四.三大类设计模式(23种)
(4.1)创建型
本质
创建对象的模式:对类的实例化进行抽象。
特点
1、封装了具体类的信息
2、隐藏了类的实例化过程
(4.1.1)单例模式(Singleton)
1、模式定义
实现1个类只有1个实例化对象 & 提供一个全局访问点。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
2、解决问题
保证1个类只有1个对象,降低对象之间的耦合度。其他对象访问的都是该类同一个实例。
3、工作原理
通过**类的实例化方法(构造方法)**实现一个类只有一个实例化对象。
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;
}
}
注重三点:
- 单例类只能有一个实例:创建静态私有变量 ourInstance 为Singleton 的唯一实例
- 单例类必须自己创建自己的唯一实例:把类的构造方法私有化,内部进行实例化,不让外部调用构造方法实例化
- 单例类必须给所有其他对象提供这一实例:定义共有方法提供该类全局唯一访问点,外部通过调用getInstance()方法来返回唯一实例
4、特点
4.1 优点
- 提供了对唯一实例的受控访问;
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能;
- 可以根据实际情况需要,在单例模式的基础上扩展做出双例模式,多例模式;
4.2 缺点
- 单例类的职责过重,里面的代码可能会过于复杂,在一定程度上违背了“单一职责原则”。
- 如果实例化的对象长时间不被利用,会被系统认为是垃圾而被回收,这将导致对象状态的丢失。
5、实现方式
- 初始化单例类时即创建单例——饿汉式
简介:
最简单的单例实现方式。依赖 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;
}
}
应用场景:
除了初始化单例类时 即 创建单例外,继续延伸出来的是:单例对象 要求初始化速度快 & 占用内存小
- 按需、延迟创建单例——懒汉式
原理:
最简单的单例实现方式。有需要时才手动创建单例,即 线程不安全。
与 饿汉式 最大的区别是:单例创建的时机
饿汉式:单例创建时机不可控,即类加载时 自动创建 单例
懒汉式:单例创建时机可控,即有需要时,才手动创建 单例
因此,
饿汉式是线程安全的,在多线程下使用,因为JVM值加载一次单例类。
但是懒汉式是线程不安全的,在多线程下不使用。因为可能存在多个线程并发调用new Instance(),从而重复创建单例对象。如:线程A执行到singleton = new Singleton(),但还没获取对象(对象初始化需要时间)。此时线程B也在执行,执行到if(singleton==null
判断为真,预示继续运行创建单例对象。最终线程A、B分别同时获得了一个单例对象,在内存中出现两个单例类的对象。
实现:
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;
}
}
针对懒汉式线程不安全进行优化
(1)同步锁
使用同步锁 synchronized锁住 创建单例getInstance()的方法 ,防止多个线程同时调用,从而避免造成单例被多次创建。即,getInstance()方法块只能运行在1个线程中。
若该段代码已在1个线程中运行,另外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)双重校验锁
针对同步锁的缺点:每次访问都要进行线程同步(即 调用synchronized锁),造成过多的同步开销(加锁 = 耗时、耗能)
实际上只需在第1次调用该方法时才需要同步,一旦单例创建成功后,就没必要进行同步
(2.1)原理
在同步锁的基础上,添加1层 if判断:若单例已创建,则不需再执行加锁操作就可获取实例,从而提高性能。
(2.2)具体实现
class Singleton {
private static Singleton ourInstance = null;
private Singleton() {
}
public static Singleton newInstance() {
// 加入双重校验锁
// 校验锁1:第1个if
if( ourInstance == null){ // ①
synchronized (Singleton.class){ // ②
// 校验锁2:第2个 if
if( ourInstance == null){
ourInstance = new Singleton();
}
}
}
return ourInstance;
}
}
// 说明
// 校验锁1:第1个if
// 作用:若单例已创建,则直接返回已创建的单例,无需再执行加锁操作
// 即直接跳到执行 return ourInstance
// 校验锁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 判断 = 为空(单例已创建),因此也不会创建多余的实例
(3)静态内部类
(3.1)原理
根据 静态内部类 的特性,同时解决了按需加载、线程安全的问题,同时实现简洁
1、在静态内部类里创建单例,在装载该内部类时才会去创建单例
2、线程安全:类是由 JVM加载,而JVM只会加载1遍,保证只有1个单例
(3.2)具体实现
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个单例
6、实例
有一个塑料生产厂,里面只有一个仓库。实现工人对仓库的管理。创建工人类和仓库类。仓库类有quantitiy=商品属性。工人类有搬运方法MoveIn(int i)和MoveOut(int i)。为了使工人类操作为同一个仓库实例,需要将仓库类设置为单例类。
//单例仓库类
class StoreHouse {
//仓库商品数量
private int quantity = 100;
//自己在内部实例化
private static StoreHouse ourInstance = new StoreHouse();;
//让外部通过调用getInstance()方法来返回唯一的实例。
public static StoreHouse getInstance() {
return ourInstance;
}
//封闭构造函数
private StoreHouse() {
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public int getQuantity() {
return quantity;
}
}
//搬货工人类
class Carrier{
public StoreHouse mStoreHouse;
public Carrier(StoreHouse storeHouse){
mStoreHouse = storeHouse;
}
//搬货进仓库
public void MoveIn(int i){
mStoreHouse.setQuantity(mStoreHouse.getQuantity()+i);
}
//搬货出仓库
public void MoveOut(int i){
mStoreHouse.setQuantity(mStoreHouse.getQuantity()-i);
}
}
7、总结
8、Android中应用
应用的Application
图片加载框架对象,比如我们的ImageLoader,常用的图片加载框架Glide,universal-image-loader等
数据请求管理类,比如可以用一个类来统一所有的数据请求处理,访问数据库,网络请求等,这样的类肯定只需要一个实例
(4.1.2)简单工厂模式(SimpleFactory Pattern)
1、含义
通过在工厂类定义一个静态方法负责生产产品对象实例。(类似现实生活中工厂生产产品)
2、解决问题
将“类实例化的操作”与“使用对象的操作”分开,让使用者不用知道具体参数就可以实例化出所需要的“产品”类,从而避免了在客户端代码中显式指定,实现了解耦。
即使用者可直接消费产品而不需要知道其生产(实例化)的细节。
3、模式原理
(3.1)模式组成
组成(角色 | 关系 | 作用 |
---|---|---|
抽象产品(Product) | 具体产品的父类 | 描述产品的公共接口 |
具体产品(Concrete Product) | 抽象产品的子类;工厂类创建的目标类 | 描述生产的具体产品 |
工厂(Factory) | 被外界调用 | 根据传入不同参数从而创建不同具体产品类的实例 |
(3.2)UML类图
(3.3)使用步骤
- 创建抽象产品类 & 定义具体产品的公共接口;
- 创建具体产品类(继承抽象产品类) & 定义生产的具体产品;
- 创建工厂类,通过创建静态方法根据传入不同参数从而创建不同具体产品类的实例;
- 外界通过调用工厂类的静态方法,传入不同参数从而创建不同具体产品类的实例
4、特点
4.1 优点
将创建实例的工作与使用实例的工作分开,使用者不必关心类对象如何创建,实现了解耦;
把初始化实例时的工作放到工厂里进行,使代码更容易维护。 更符合面向对象的原则 & 面向接口编程,而不是面向实现编程。
4.2 缺点
工厂类集中了所有实例(产品)的创建逻辑,一旦这个工厂不能正常工作,整个系统都会受到影响;
违背“开放 - 关闭原则”,一旦添加新产品就不得不修改工厂类的逻辑,这样就会造成工厂逻辑过于复杂。
简单工厂模式由于使用了静态工厂方法,静态方法不能被继承和重写,会造成工厂角色无法形成基于继承的等级结构。
5、应用场景
客户如果只知道传入工厂类的参数,对于如何创建对象的逻辑不关心时;
当工厂类负责创建的对象(具体产品)比较少时。
6、实例
步骤1. 创建抽象产品类,定义具体产品的公共接口
abstract class Product{
public abstract void Show();
}
步骤2. 创建具体产品类(继承抽象产品类),定义生产的具体产品
//具体产品类A
class ProductA extends Product{
@Override
public void Show() {
System.out.println("生产出了产品A");
}
}
//具体产品类B
class ProductB extends Product{
@Override
public void Show() {
System.out.println("生产出了产品C");
}
}
//具体产品类C
class ProductC extends Product{
@Override
public void Show() {
System.out.println("生产出了产品C");
}
}
步骤3. 创建工厂类,通过创建静态方法从而根据传入不同参数创建不同具体产品类的实例
class Factory {
public static Product Manufacture(String ProductName){
//工厂类里用switch语句控制生产哪种商品;
//使用者只需要调用工厂类的静态方法就可以实现产品类的实例化。
switch (ProductName){
case "A":
return new ProductA();
case "B":
return new ProductB();
case "C":
return new ProductC();
default:
return null;
}
}
}
步骤4. 外界通过调用工厂类的静态方法,传入不同参数从而创建不同具体产品类的实例
//工厂产品生产流程
public class SimpleFactoryPattern {
public static void main(String[] args){
Factory mFactory = new Factory();
//客户要产品A
try {
//调用工厂类的静态方法 & 传入不同参数从而创建产品实例
mFactory.Manufacture("A").Show();
}catch (NullPointerException e){
System.out.println("没有这一类产品");
}
//客户要产品B
try {
mFactory.Manufacture("B").Show();
}catch (NullPointerException e){
System.out.println("没有这一类产品");
}
//客户要产品C
try {
mFactory.Manufacture("C").Show();
}catch (NullPointerException e){
System.out.println("没有这一类产品");
}
//客户要产品D
try {
mFactory.Manufacture("D").Show();
}catch (NullPointerException e){
System.out.println("没有这一类产品");
}
}
}
(4.1.3)工厂方法模式(Factory Method)
1、介绍
工厂方法模式可理解为多态工厂模式,通过定义工厂父类负责定义创建对象的公共接口,而子类则负责生成具体的对象。将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定该实例化(创建)哪一个类。
解决了简单工厂的缺点:工厂一旦要生产新产品就需要修改工厂类的方法逻辑,违背了"开放-关闭"原则。
之所以可以解决简单工厂的问题,是因为工厂方法模式把具体产品的创建推迟到工厂类的子类(具体工厂)中,此时工厂类不再负责所有产品的创建,而只是给出具体工厂必须实现的接口,这样工厂方法模式在添加新产品的时候就不修改工厂类逻辑而是添加新的工厂子类,符合开放封闭原则,克服了简单工厂模式中缺点
2、模式原理
(2.1)模式组成
组成(角色 | 关系 | 作用 |
---|---|---|
抽象产品(Product) | 具体产品的父类 | 描述产品的公共接口 |
具体产品(Concrete Product) | 抽象产品的子类;工厂类创建的目标类 | 描述生产的具体产品 |
工厂(Factory) | 被外界调用 | 根据传入不同参数从而创建不同具体产品类的实例 |
具体工厂(Concrete Creator) | 抽象工厂的子类;被外界调用 | 描述具体工厂;实现FactoryMethod工厂方法创建产品的实例 |
(2.2)UML类图
(2.3)使用步骤
步骤1: 创建抽象工厂类,定义具体工厂的公共接口;
步骤2: 创建抽象产品类 ,定义具体产品的公共接口;
步骤3: 创建具体产品类(继承抽象产品类) & 定义生产的具体产品;
步骤4:创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;
步骤5:外界通过调用具体工厂类的方法,从而创建不同具体产品类的实例
3、特点
3.1 优点
- 更符合开-闭原则
新增一种产品时,只需要增加相应的具体产品类和相应的工厂子类即可(简单工厂模式需要修改工厂类的判断逻辑) - 符合单一职责原则
每个具体工厂类只负责创建对应的产品(简单工厂中的工厂类存在复杂的switch逻辑判断
) - 不使用静态工厂方法,可以形成基于继承的等级结构。
简单工厂模式的工厂类使用静态工厂方法
总结:工厂模式可以说是简单工厂模式的进一步抽象和拓展,在保留了简单工厂的封装优点的同时,让扩展变得简单,让继承变得可行,增加了多态性的体现。
3.2 缺点
添加新产品时,除了增加新产品类外,还要提供与之对应的具体工厂类,系统类的个数将成对增加,在一定程度上增加了系统的复杂度;同时,有更多的类需要编译和运行,会给系统带来一些额外的开销;
由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
虽然保证了工厂方法内的对修改关闭,但对于使用工厂方法的类,如果要更换另外一种产品,仍然需要修改实例化的具体工厂类;
一个具体工厂只能创建一种具体产品
4、应用场景
当一个类不知道它所需要的对象的类时
在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可;
当一个类希望通过其子类来指定创建对象时
在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
5、实例
实例概况
背景:小成有一间塑料加工厂(仅生产A类产品);随着客户需求的变化,客户需要生产B类产品;
冲突:改变原有塑料加工厂的配置和变化非常困难,假设下一次客户需要再发生变化,再次改变将增大非常大的成本;
解决方案:小成决定置办塑料分厂B来生产B类产品;
使用步骤
步骤1: 创建抽象工厂类,定义具体工厂的公共接口
abstract class Factory{
public abstract Product Manufacture();
}
步骤2: 创建抽象产品类 ,定义具体产品的公共接口;
abstract class Product{
public abstract void Show();
}
步骤3: 创建具体产品类(继承抽象产品类), 定义生产的具体产品;
//具体产品A类
class ProductA extends Product{
@Override
public void Show() {
System.out.println("生产出了产品A");
}
}
//具体产品B类
class ProductB extends Product{
@Override
public void Show() {
System.out.println("生产出了产品B");
}
}
步骤4:创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;
//工厂A类 - 生产A类产品
class FactoryA extends Factory{
@Override
public Product Manufacture() {
return new ProductA();
}
}
//工厂B类 - 生产B类产品
class FactoryB extends Factory{
@Override
public Product Manufacture() {
return new ProductB();
}
}
步骤5:外界通过调用具体工厂类的方法,从而创建不同具体产品类的实例
//生产工作流程
public class FactoryPattern {
public static void main(String[] args){
//客户要产品A
FactoryA mFactoryA = new FactoryA();
mFactoryA.Manufacture().Show();
//客户要产品B
FactoryB mFactoryB = new FactoryB();
mFactoryB.Manufacture().Show();
}
}
(4.1.4)抽象工厂模式(Abastract Factory)
1、介绍
抽象工厂模式,即Abstract Factory Pattern,提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类;具体的工厂负责实现具体的产品实例。
抽象工厂模式与工厂方法模式最大的区别:抽象工厂中每个工厂可以创建多种类的产品;而工厂方法每个工厂只能创建一类。
允许使用抽象的接口来创建一组相关产品,而不需要知道或关心实际生产出的具体产品是什么,这样就可以从具体产品中被解耦。
可解决工厂模式缺点:每个工厂只能创建一类产品。
2、模式原理
(2.1)模式组成
组成(角色 | 关系 | 作用 |
---|---|---|
抽象产品族(Abstarct Product) | 抽象产品的父类 | 描述抽象产品公共接口 |
抽象产品(Product) | 具体产品的父类 | 描述产品的公共接口 |
具体产品(Concrete Product) | 抽象产品的子类;工厂类创建的目标类 | 描述生产的具体产品 |
工厂(Factory) | 被外界调用 | 根据传入不同参数从而创建不同具体产品类的实例 |
具体工厂(Concrete Creator) | 抽象工厂的子类;被外界调用 | 描述具体工厂;实现FactoryMethod工厂方法创建产品的实例 |
理解抽象产品族、抽象产品、具体产品
(2.2)UML类图
(2.3)使用步骤
步骤1: 创建抽象工厂类,定义具体工厂的公共接口;
步骤2: 创建抽象产品族类 ,定义抽象产品的公共接口;
步骤3: 创建抽象产品类 (继承抽象产品族类),定义具体产品的公共接口;
步骤4: 创建具体产品类(继承抽象产品类) & 定义生产的具体产品;
步骤5:创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;
步骤6:客户端通过实例化具体的工厂类,并调用其创建不同目标产品的方法创建不同具体产品类的实例
3、特点
3.1 优点
- 降低耦合
抽象工厂模式将具体产品的创建延迟到具体工厂的子类中,这样将对象的创建封装起来,可以减少客户端与具体产品类之间的依赖,从而使系统耦合度低,这样更有利于后期的维护和扩展; - 更符合开-闭原则
新增一种产品类时,只需要增加相应的具体产品类和相应的工厂子类即可。简单工厂模式需要修改工厂类的判断逻辑 - 符合单一职责原则
每个具体工厂类只负责创建对应的产品,简单工厂中的工厂类存在复杂的switch逻辑判断 - 不使用静态工厂方法,可以形成基于继承的等级结构。简单工厂模式的工厂类使用静态工厂方法
3.2 缺点
抽象工厂模式很难支持新种类产品的变化。
这是因为抽象工厂接口中已经确定了可以被创建的产品集合,如果需要添加新产品,此时就必须去修改抽象工厂的接口,这样就涉及到抽象工厂类的以及所有子类的改变,这样也就违背了“开发——封闭”原则。
4、应用场景
一个系统不要求依赖产品类实例如何被创建、组合和表达的表达,这点也是所有工厂模式应用的前提。
这个系统有多个系列产品,而系统中只消费其中某一系列产品
系统要求提供一个产品类的库,所有产品以同样的接口出现,客户端不需要依赖具体实现。
5、实例
实例概况
背景:小成有两间塑料加工厂(A厂仅生产容器类产品;B厂仅生产模具类产品);随着客户需求的变化,A厂所在地的客户需要也模具类产品,B厂所在地的客户也需要容器类产品;
冲突:没有资源(资金+租位)在当地分别开设多一家注塑分厂
解决方案:在原有的两家塑料厂里增设生产需求的功能,即A厂能生产容器+模具产品;B厂间能生产容器+模具产品。
使用步骤
步骤1: 创建抽象工厂类,定义具体工厂的公共接口
abstract class Factory{
public abstract Product ManufactureContainer();
public abstract Product ManufactureMould();
}
步骤2: 创建抽象产品族类 ,定义具体产品的公共接口;
abstract class AbstractProduct{
public abstract void Show();
}
步骤3: 创建抽象产品类 ,定义具体产品的公共接口;
//容器产品抽象类
abstract class ContainerProduct extends AbstractProduct{
@Override
public abstract void Show();
}
//模具产品抽象类
abstract class MouldProduct extends AbstractProduct{
@Override
public abstract void Show();
}
步骤4: 创建具体产品类(继承抽象产品类), 定义生产的具体产品;
//容器产品A类
class ContainerProductA extends ContainerProduct{
@Override
public void Show() {
System.out.println("生产出了容器产品A");
}
}
//容器产品B类
class ContainerProductB extends ContainerProduct{
@Override
public void Show() {
System.out.println("生产出了容器产品B");
}
}
//模具产品A类
class MouldProductA extends MouldProduct{
@Override
public void Show() {
System.out.println("生产出了模具产品A");
}
}
//模具产品B类
class MouldProductB extends MouldProduct{
@Override
public void Show() {
System.out.println("生产出了模具产品B");
}
}
步骤5:创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;
//A厂 - 生产模具+容器产品
class FactoryA extends Factory{
@Override
public Product ManufactureContainer() {
return new ContainerProductA();
}
@Override
public Product ManufactureMould() {
return new MouldProductA();
}
}
//B厂 - 生产模具+容器产品
class FactoryB extends Factory{
@Override
public Product ManufactureContainer() {
return new ContainerProductB();
}
@Override
public Product ManufactureMould() {
return new MouldProductB();
}
}
步骤6:客户端通过实例化具体的工厂类,并调用其创建不同目标产品的方法创建不同具体产品类的实例
//生产工作流程
public class AbstractFactoryPattern {
public static void main(String[] args){
FactoryA mFactoryA = new FactoryA();
FactoryB mFactoryB = new FactoryB();
//A厂当地客户需要容器产品A
mFactoryA.ManufactureContainer().Show();
//A厂当地客户需要模具产品A
mFactoryA.ManufactureMould().Show();
//B厂当地客户需要容器产品B
mFactoryB.ManufactureContainer().Show();
//B厂当地客户需要模具产品B
mFactoryB.ManufactureMould().Show();
}
}
6、简单工厂模式、工厂模式、抽象工厂模式总结
- 简单工厂 : 用来生产同一等级结构中的任意产品。(不支持拓展增加产品)
- 工厂方法 :用来生产同一等级结构中的固定产品。(支持拓展增加产品)
- 抽象工厂 :用来生产不同产品族的全部产品。(不支持拓展增加产品;支持增加产品族)
(4.1.5)建造者模式(Builder Pattern)
1、介绍
适用于一个类内部数据结构过于复杂时(用于很多数据,且组织装配复杂),通过构建者模式可以对类中的数据按部就班地创建与设置。即Builder模式可以将一个类的构建和表示进行分离。
创建者模式又叫建造者模式,是将一个复杂的对象的构建与它的表示分离,使
得同样的构建过程可以创建不同的表示。创建者模式隐藏了复杂对象的创建过程,它把复杂对象的创建过程加以抽象,通过子类继承或者重载的方式,动态地创建具有复合属性的对象。
2、模式原理
(2.1)UML类图
(2.2)模式角色
角色 | 说明 |
---|---|
抽象建造者(Builder) | 给出一个抽象接口,以规范产品对象的各个组成成分的建造。 |
具体建造者(ConcreteBuilder) | 担任这个角色的是于应用程序紧密相关的类,它们在应用程序调用下创建产品实例。 |
指挥者(Director) | 调用具体建造者角色以创建产品对象。 |
产品角色(Productor) | 产品便是建造中的复杂对象。 |
(2.3)模式讲解
- 指挥者(Director)直接和客户(Client)进行需求沟通;
- 沟通后指挥者(Director)将客户(Client)创建产品的需求划分为各个部件的建造请求(Builder);
- 将各个部件的建造请求(Builder)委派到具体的建造者(ConcreteBuilder);
- 各个具体建造者负责进行产品各个部件进行构建,但不为客户(Client)所知;
- 最终构建成具体产品(Product)。
3、特点
隐藏创建对象的建造过程 & 细节,使得用户在不知对象的建造过程 & 细节的情况下,就可直接创建复杂的对象。用户只需要给出指定复杂对象的类型和内容,无须知道其内部的具体构造细节;建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)
方便用户创建复杂对象(而不需要知道实现过程);代码复用性&封装性(将对象构建过程和细节进行封装&复用)
4、应用场景
- 隔离复杂对象的创建和使用,相同的方法,不同执行顺序,产生不同事件结果
- 多个部件都可以装配到一个对象中,但产生的运行结果不相同
- 产品类非常复杂或者产品类因为调用顺序不同而产生不同作用
- 初始化一个对象时,参数过多,或者很多参数具有默认值
- Builder模式不适合创建差异性很大的产品类
产品内部变化复杂,会导致需要定义很多具体建造者类实现变化,增加项目中类的数量,增加系统的理解难度和运行成本 - 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性;
5、实例
下面的程序代码演示了Shop对象使用VehicleBuilders来建造不同的交通工具。该例子使用了Builder模式顺序建造交通工具的不同部分。
Builder:抽象建造者 VehicleBuilder.java 定义组装的过程:组装交通工具
public abstract class VehicleBuilder
{
// Fields
protected Vehicle vehicle = null;
// Properties
public Vehicle Vehicle
{
get { return vehicle; }
}
// Methods 声明为抽象方法,具体由子类实现
public abstract public void BuildFrame();
public abstract public void BuildEngine();
public abstract public void BuildWheels();
public abstract public void BuildDoors();
}
ConcreteBuilder1:具体建造者 MotorCycleBuilder.java 创建具体实例
// "ConcreteBuilder1"
class MotorCycleBuilder extend VehicleBuilder
{
vehicle = new Vehicle("MotorCycle");//创建产品实例
@override
public void BuildFrame()
{
vehicle.setFrame("MotorCycle Frame");
}
@override
public void BuildEngine()
{
vehicle.setEngine("500 cc");
}
@override
public void BuildWheels()
{
vehicle.setWheels("2");
}
@override
public void BuildDoors()
{
vehicle.setDoors("0");
}
}
ConcreteBuilder2:具体建造者 CarBuilder.cs
// "ConcreteBuilder2"
class CarBuilder extend VehicleBuilder
{
vehicle = new Vehicle("Car");
@override
public void BuildFrame()
{
vehicle.setFrame( "Car Frame");
}
@override
public void BuildEngine()
{
vehicle.setEngine("2500 cc") ;
}
@override
public void BuildWheels()
{
vehicle.setWheels("4") ;
}
@override
public void BuildDoors()
{
vehicle.setDoors("4") ;
}
}
ConcreteBuilder3:具体建造者 ScooterBuilder.java
class ScooterBuilder extend VehicleBuilder
{
vehicle = new Vehicle("Scooter");
@override
public void BuildFrame()
{
vehicle.setFrame("Scooter Frame") ;
}
@override
public void BuildEngine()
{
vehicle.setEngine("none");
}
@override
public void BuildWheels()
{
vehicle.setWheels("4");
}
@override
public void BuildDoors()
{
vehicle.setDoors("0");
}
}
Director:指挥者 Shop.java 客户端委派任务给指挥者进行组装
// "Director"
class Shop
{
// Methods
public void Construct(VehicleBuilder vehicleBuilder)
{
vehicleBuilder.BuildFrame();
vehicleBuilder.BuildEngine();
vehicleBuilder.BuildWheels();
vehicleBuilder.BuildDoors();
}
}
Product:产品角色 Vehicle.java
// "Product"
class Vehicle
{
// Fields
private string type;
private String frame;
private String engine;
private String wheels;
private String doors;
// Constructors
public Vehicle(string type)
{
this.type = type;
}
// getter&setter
public void setFrame(String frame){
this.frame = frame;
}
public String gerFrame(){
return this.frame;
}
public void setEngine(String engine){
this.engine = engine;
}
public String gerEngine(){
return this.engine;
}
public void setWheels(String wheels){
this.wheels = wheels;
}
public String getWheels(){
return this.wheels;
}
public void setDoors(String doors){
this.doors = doors;
}
public String getDoors(){
return this.doors;
}
public void Show()
{
System.out.println("Vehicle Type: " + type);
System.out.println("Frame : " + frame);
System.out.println("Engine : " + engine);
System.out.println("Wheels: " + wheels);
System.out.println("Doors : " + doors);
}
}
客户端代码 Client.java 寻找指挥者组装需要的交通工具
class Client
{
public static void main(string[] args)
{
// Create shop and vehicle builders 找到指挥者和具体的组装者
Shop shop = new Shop();
VehicleBuilder b1 = new ScooterBuilder();
VehicleBuilder b2 = new CarBuilder();
VehicleBuilder b3 = new MotorCycleBuilder();
//指挥者让组装者进行组装交通工具
shop.Construct(b1);
//交通工具组装好后进行展示
b1.Vehicle.Show();
shop.Construct(b2);
b2.Vehicle.Show();
shop.Construct(b3);
b3.Vehicle.Show();
}
}
当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用 builder模式进行重构,重构前示例代码:
// 省略 getter 和 setter 方法
public class Computer {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Computer(String cpu, String screen, String memory, String mainboard) {
this.cpu = cpu;
this.screen = screen;
this.memory = memory;
this.mainboard = mainboard;
}
}
public class NewComputer {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public NewComputer() {
throw new RuntimeException(“can’t init”);
}
private NewComputer(Builder builder) {
cpu = builder.cpu;
screen = builder.screen;
memory = builder.memory;
mainboard = builder.mainboard;
}
public static final class Builder {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Builder() {}
public Builder cpu(String val) {
cpu = val;
return this;
}
public Builder screen(String val) {
screen = val;
return this;
}
public Builder memory(String val) {
memory = val;
return this;
}
public Builder mainboard(String val) {
mainboard = val;
return this;
}
public NewComputer build() {
return new NewComputer(this);}
}
}
客户端调用
public class Click {
public static void main(String[] args) {
// 非 Builder 模式
Computer computer = new Computer(“cpu”, “screen”, “memory”, “mainboard”);
// Builder 模式
NewComputer newComputer = new NewComputer.Builder()
.cpu(“cpu”)
.screen(“screen”)
.memory(“memory”)
.mainboard(“mainboard”)
.build();
}
}
上面的示例代码只是传入四个参数,如果参数是十四个甚至更多,builder 模式的优势将会更加明显,传递参数更加灵活,代码具有更高的可读性.
6、Android中应用
AlertDialog
universal-image-loader
OkHttp中HttpClient和Request建造
下面以我们的图片加载框架ImageLoader为例来看看Builder模式的好处
未采用Builder模式的ImageLoader
public class ImageLoader {
//图片加载配置
private int loadingImageId;
private int loadingFailImageId;
// 图片缓存,依赖接口
ImageCache mImageCache = new MemoryCache();
// 线程池,线程数量为CPU的数量
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//省略单例模式实现
/**
* 设置图片缓存
* @param cache
*/
public void setImageCache(ImageCache cache) {
mImageCache = cache;
}
/**
* 设置图片加载中显示的图片
* @param resId
*/
public Builder setLoadingPlaceholder(int resId) {
loadingImageId = resId;
}
/**
* 设置加载失败显示的图片
* @param resId
*/
public Builder setLoadingFailPlaceholder(int resId) {
loadingFailImageId = resId;
}
/**
* 显示图片
* @param imageUrl
* @param imageView
*/
public void displayImage(String imageUrl, ImageView imageView) {
Bitmap bitmap = mImageCache.get(imageUrl);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
// 图片没有缓存,提交到线程池下载
submitLoadRequest(imageUrl, imageView);
}
/**
* 下载图片
* @param imageUrl
* @param imageView
*/
private void submitLoadRequest(final String imageUrl, final ImageView imageView) {
imageView.setImageResource(loadingImageId);
imageView.setTag(imageUrl);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(imageUrl);
if (bitmap == null) {
imageView.setImageResource(loadingFailImageId);
return;
}
if (imageUrl.equals(imageView.getTag())) {
imageView.setImageBitmap(bitmap);
}
mImageCache.put(imageUrl, bitmap);
}
});
}
/**
* 下载图片
* @param imageUrl
* @return
*/
private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
//省略下载部分代码
return bitmap;
}
}
从上面的代码中我们可以看出,每当需要增加一个设置选项的时候,就需要修改ImageLoader的代码,违背了开闭原则,而且ImageLoader中的代码会越来越多,不利于维护
下面我们来看看如何用Builder模式来改造ImageLoader
首先是把ImageLoader的设置都放在单独的配置类里,每个set方法都返回this,从而达到链式调用的目的
public class ImageLoaderConfig {
// 图片缓存,依赖接口
public ImageCache mImageCache = new MemoryCache();
//加载图片时的loading和加载失败的图片配置对象
public DisplayConfig displayConfig = new DisplayConfig();
//线程数量,默认为CPU数量+1;
public int threadCount = Runtime.getRuntime().availableProcessors() + 1;
private ImageLoaderConfig() {
}
/**
* 配置类的Builder
*/
public static class Builder {
// 图片缓存,依赖接口
ImageCache mImageCache = new MemoryCache();
//加载图片时的loading和加载失败的图片配置对象
DisplayConfig displayConfig = new DisplayConfig();
//线程数量,默认为CPU数量+1;
int threadCount = Runtime.getRuntime().availableProcessors() + 1;
/**
* 设置线程数量
* @param count
* @return
*/
public Builder setThreadCount(int count) {
threadCount = Math.max(1, count);
return this;
}
/**
* 设置图片缓存
* @param cache
* @return
*/
public Builder setImageCache(ImageCache cache) {
mImageCache = cache;
return this;
}
/**
* 设置图片加载中显示的图片
* @param resId
* @return
*/
public Builder setLoadingPlaceholder(int resId) {
displayConfig.loadingImageId = resId;
return this;
}
/**
* 设置加载失败显示的图片
* @param resId
* @return
*/
public Builder setLoadingFailPlaceholder(int resId) {
displayConfig.loadingFailImageId = resId;
return this;
}
void applyConfig(ImageLoaderConfig config) {
config.displayConfig = this.displayConfig;
config.mImageCache = this.mImageCache;
config.threadCount = this.threadCount;
}
/**
* 根据已经设置好的属性创建配置对象
* @return
*/
public ImageLoaderConfig create() {
ImageLoaderConfig config = new ImageLoaderConfig();
applyConfig(config);
return config;
}
}
}
ImageLoader的修改
public class ImageLoader {
//图片加载配置
ImageLoaderConfig mConfig;
// 图片缓存,依赖接口
ImageCache mImageCache = new MemoryCache();
// 线程池,线程数量为CPU的数量
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//省略单例模式实现
//初始化ImageLoader
public void init(ImageLoaderConfig config) {
mConfig = config;
mImageCache = mConfig.mImageCache;
}
/**
* 显示图片
* @param imageUrl
* @param imageView
*/
public void displayImage(String imageUrl, ImageView imageView) {
Bitmap bitmap = mImageCache.get(imageUrl);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
// 图片没有缓存,提交到线程池下载
submitLoadRequest(imageUrl, imageView);
}
/**
* 下载图片
* @param imageUrl
* @param imageView
*/
private void submitLoadRequest(final String imageUrl, final ImageView imageView) {
imageView.setImageResource(mConfig.displayConfig.loadingImageId);
imageView.setTag(imageUrl);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(imageUrl);
if (bitmap == null) {
imageView.setImageResource(mConfig.displayConfig.loadingFailImageId);
return;
}
if (imageUrl.equals(imageView.getTag())) {
imageView.setImageBitmap(bitmap);
}
mImageCache.put(imageUrl, bitmap);
}
});
}
/**
* 下载图片
* @param imageUrl
* @return
*/
private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
//省略下载部分代码
return bitmap;
}
}
调用形式,是不是很熟悉?
ImageLoaderConfig config = new ImageLoaderConfig.Builder()
.setImageCache(new MemoryCache())
.setThreadCount(2)
.setLoadingFailPlaceholder(R.drawable.loading_fail)
.setLoadingPlaceholder(R.drawable.loading)
.create();
ImageLoader.getInstance().init(config);
(4.1.6)原型模式(Prototype Pattern)
1、介绍
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
适用于:
- 类初始化需要消耗非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗。
- 通过new产生一个对象需要非常繁琐的数据准备或访问权限,这时可以使用原型模式。
- 一个对象需要供给其他对象访问,而且各个对象都需要修改其值时,可以拷贝多个对象供调用者访问,即保护性拷贝。
2、模式原理
(2.1)UML图
我们将创建一个抽象类 Shape 和扩展了 Shape 类的实体类。下一步是定义类 ShapeCache,该类把 shape 对象存储在一个 Hashtable 中,并在请求的时候返回它们的克隆。
(2.2)角色
角色 | 说明 |
---|---|
抽象类或接口 | 声明具备clone的能力 |
具体的原型类 | 实现抽象类 |
3、特点
- 原型模式本质上就是对象拷贝,容易出现的问题是深拷贝和浅拷贝
- 使用原型模式可以解决构建复杂对象的资源消耗问题,能够在某些场景下提升创建对象的效率
- 原型模式还有一个用途就是保护性拷贝,也就是某个对象可能是只读的,为了防止外部对这个只读对象修改,通常可以返回一个对象拷贝的形式实现只读的限制
- 原型模式需要注意构造函数不会执行的问题
4、实例
下面以文档拷贝的例子来演示一下简单的原型模式
浅拷贝和深拷贝
浅拷贝又叫影子拷贝,上面我们在拷贝文档时并没有把原文档中的字段都重新构造了一遍,而只是拷贝了引用,也就是副文档的字段引用原始文档的字段,这样的话修改副文档中的内容就会连原始文档也改掉了,这就是浅拷贝
深拷贝就是在浅拷贝的基础上,对于引用类型的字段也要采用拷贝的形式,比如上面的images,而像String、int这些基本数据类型则没关系
所以在运用原型模式时建议大家还是用深拷贝,下面我们把上面的浅拷贝改成深拷贝
文档类
public class WordDocument implements Cloneable {
//文本
private String text;
//图片
private ArrayList<String> images = new ArrayList<String>();
public WordDocument() {
System.out.println("---WordDocument 构造函数---");
}
@Override
protected WordDocument clone() {
try {
WordDocument document = (WordDocument) super.clone();
document.text = this.text;
//document.images = this.images;
//改成深拷贝
document.images = (ArrayList<String>) this.images.clone();
return document;
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
//省略其他代码
/**
* 打印文档内容
*/
public void showDocument() {
//省略打印
}
}
上面模拟创建了一个文档,里面包含文字和图片。拷贝时通过clone方法生成新的文档,并把文字和图片都拷贝到新的文档中
下面是测试代码
WordDocument doc1 = new WordDocument();
doc1.setText("这是一篇文档");
doc1.addImage("图片1");
doc1.addImage("图片2");
doc1.addImage("图片3");
doc1.showDocument();
WordDocument doc2 = doc1.clone();
doc2.showDocument();
doc2.setText("这是修改过的文档");
doc2.addImage("图片4");
doc2.showDocument();
doc1.showDocument();
此外还要注意的是,拷贝的时候不会调用构造函数!
Intent中的原型模式
@Override
public Object clone() {
return new Intent(this);
}
private Intent(Intent o, boolean all) {
this.mAction = o.mAction;
this.mData = o.mData;
this.mType = o.mType;
this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
if (o.mCategories != null) {
this.mCategories = new ArraySet<String>(o.mCategories);
}
}
从上面的源码中可以看出,intent的clone方法实际上是通过new的方法来实现的,并没有调用super.clone()。在实现原型模式时,使用clone方法还是new需要根据构造对象的成本来考虑,如果对象的构造成本比较高或者构造比较麻烦,那么使用clone()函数效率较高
4、Android应用
我们常用的Intent,ArrayList等
登录模块中保存的用户信息类需要通过服务器更新用户信息,但是有很多地方需要调用,需要设置为对其他用到的模块只读,这个时候可以考虑用原型模式进行保护性拷贝
5、总结
- 原型模式本质上就是对象拷贝,容易出现的问题是深拷贝和浅拷贝
- 使用原型模式可以解决构建复杂对象的资源消耗问题,能够在某些场景下提升创建对象的效率
- 原型模式还有一个用途就是保护性拷贝,也就是某个对象可能是只读的,为了防止外部对这个只读对象修改,通常可以返回一个对象拷贝的形式实现只读的限制
- 原型模式需要注意构造函数不会执行的问题
(4.2)结构型
本质
处理类或对象的组合
类型
1、类结构型:采用继承机制来组合接口实现
2、对象结构型:组合对象的方式来实现新功能
(4.2.1)适配器模式(Adapter)
1、简介
适配器模式把一种接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
通过定义一个包装类,用于包装不兼容接口的对象。包装类为适配器Adapter,被包装对象为适配者(被适配的类)Adaptee。
2、原理
(1)UML类图
(2)模式角色
角色 | 说明 |
---|---|
目标角色(Target) | 期待得到的接口。 |
源角色(Adaptee) | 需要适配的接口 |
适配器(Adapter) | 适配器模式的核心。适配器将源接口转换成目标接口。 |
(3)使用步骤
步骤1:创建Target接口
public interface Target {
//这是源类Adapteee没有的方法
public void Request();
}
步骤2:创建源类(Adaptee)
public class Adaptee {
public void SpecificRequest(){
}
}
步骤3:创建适配器类(Adapter)
class Adapter implements Target{
// 直接关联被适配类
private Adaptee adaptee;
// 可以通过构造函数传入具体需要适配的被适配类对象
public Adapter (Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void Request() {
// 这里是使用委托的方式完成特殊功能
this.adaptee.SpecificRequest();
}
}
步骤4:定义具体使用目标类,并通过Adapter类调用所需要的方法从而实现目标
public class AdapterPattern {
public static void main(String[] args){
//需要先创建一个被适配类的对象作为参数
Target mAdapter = new Adapter(new Adaptee());
mAdapter.Request();
}
}
3、特点
优点
- 更好的复用性
系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。 - 透明、简单
客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单 & 更直接 - 更好的扩展性
在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。 - 解耦性
将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码 - 符合开放-关闭原则
同一个适配器可以把适配者类和它的子类都适配到目标接口;可以为不同的目标接口实现不同的适配器,而不需要修改待适配类
缺点
- 过多的使用适配器,会让系统非常零乱,不易整体进行把握
4、应用场景
- 系统需要使用现有的类,而此类的接口不符合系统的需要,即接口不兼容
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的一些类一起工作
- 需要一个统一的输出接口,而输入端的接口不可预知
5、实例
实例背景
我们给手机充电需要5V电压,而我们家里的电压都是220V的,下面我们通过日常生活中的电源电压适配的问题来简单应用下适配器模式。(将家里电压通过适配器适配手机充电所需的电压)
解决方案
- 创建5V电压接口(适配器模式中的目标接口)
public interface FiveVolt {
/**
* 返回5V电压
* @return 电压值
*/
public int getVolt5();
}
- 系统支持的日常的220V电压(适配器模式中需要被适配的接口)
public class Volt220 {
public int getVolt220() {
return 220;
}
}
- 适配器类(将目标电压接口5V适配系统电压接口220V)
public class VoltAdapter implements FiveVolt {
Volt220 mVolt220;
public VoltAdapter(Volt220 volt220) {
mVolt220 = volt220;
}
@Override
public int getVolt5() {
return mVolt220.getVolt220();
}
public int getVolt220() {
return mVolt220.getVolt220();
}
}
- 定义具体使用目标类,并通过Adapter类调用所需要的方法从而实现目标
public class AdapterPattern {
public static void main(String[] args){
//需要先创建一个被适配类的对象作为参数
FiveVolt voltAdapter = new VoltAdapter(new Volt220());
voltAdapter.getVolt5();
}
}
6、Android应用
最常见的ListView、GridView、RecyclerView等的Adapter。
我们在使用ListView时,每一项的布局和数据都不一样,但是最后输出都可以看作是一个View,这就对应了上面的适配器模式应用场景的第三条:需要一个统一的输出接口,而输入端的接口不可预知。下面我们来看看ListView中的适配器模式。
ListView通过引入Adapter适配器类把那些多变的布局和数据交给用户处理,然后通过适配器中的接口获取需要的数据来完成自己的功能,从而达到了很好的灵活性。这里面最重要的接口莫过于getView()接口了,该接口返回一个View对象,而千变万化的UI视图都是View的子类,通过这样一种处理就将子View的变化隔离了,保证了AbsListView类族的高度可定制化。
当然这里的Adapter并不是经典的适配器模式,却是对象适配器模式的优秀示例。
首先我们来看看一般我们的Adapter类的结构
class Adapter extends BaseAdapter {
private List<String> mDatas;
public Adapter(List<String> datas) {
mDatas = datas;
}
@Override
public int getCount() {
return mDatas.size();
}
@Override
public long getItemId(int position) { return position; }
@Override
public Object getItem(int position) { return mDatas.get(position);}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
//初始化View
}
//初始化数据
return convertView;
}
}
可以看出Adapter里面的接口主要是getCount()返回子View的数量,以及getView()返回我们填充好数据的View,ListView则通过这些接口来执行具体的布局、缓存等工作。下面我们来简单看看ListView的实现。
首先这些getCount()等接口都在一个接口类Adapter里
public interface Adapter {
//省略其他的接口
int getCount();
Object getItem(int position);
long getItemId(int position);
View getView(int position, View convertView, ViewGroup parent);
//省略其他的接口
}
中间加了一个过渡的接口ListAdapter
public interface ListAdapter extends Adapter {
//接口省略
}
我们在编写我们自己的Adapter时都会继承一个BaseAdapter,我们来看看BaseAdapter
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
//BaseAdapter里面实现了ListAdapter的接口以及部分Adapter中的接口
//而像getCount()以及getView()这些接口则需要我们自己去实现
}
ListView的父类AbsListView中有ListAdapter接口,通过这个接口来调用getCount()等方法获取View的数量等
public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
ViewTreeObserver.OnTouchModeChangeListener,
RemoteViewsAdapter.RemoteAdapterConnectionCallback {
/**
* The adapter containing the data to be displayed by this view
*/
ListAdapter mAdapter;
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
final ViewTreeObserver treeObserver = getViewTreeObserver();
treeObserver.addOnTouchModeChangeListener(this);
if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
treeObserver.addOnGlobalLayoutListener(this);
}
if (mAdapter != null && mDataSetObserver == null) {
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
// Data may have changed while we were detached. Refresh.
mDataChanged = true;
mOldItemCount = mItemCount;
//通过getCount()获取View元素的个数
mItemCount = mAdapter.getCount();
}
}
}
从上面我们可以看出,AbsListView是一个抽象类,它里面封装了一些固定的逻辑,如Adapter模式的应用逻辑、布局的复用逻辑和布局子元素逻辑等。而具体的实现则是在子类ListView中。下面我们来看看ListView中是怎么处理每一个子元素View的。
@Override
protected void layoutChildren() {
//省略其他代码
case LAYOUT_FORCE_BOTTOM:
sel = fillUp(mItemCount - 1, childrenBottom);
adjustViewsUpOrDown();
break;
case LAYOUT_FORCE_TOP:
mFirstPosition = 0;
sel = fillFromTop(childrenTop);
adjustViewsUpOrDown();
break;
//省略其他代码
}
在ListView中会覆写AbsListView中的layoutChildren()函数,在layoutChildren()中会根据不同的情况进行布局,比如从上到下或者是从下往上。下面我们看看具体的布局方法fillUp方法。
private View fillUp(int pos, int nextBottom) {
//省略其他代码
while (nextBottom > end && pos >= 0) {
// is this the selected item?
boolean selected = pos == mSelectedPosition;
View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
nextBottom = child.getTop() - mDividerHeight;
if (selected) {
selectedView = child;
}
pos--;
}
mFirstPosition = pos + 1;
setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
return selectedView;
}
这里我们看到fillUp方法里面又会通过makeAndAddView()方法来获取View,下面我们来看看makeAndAddView()方法的实现
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
if (!mDataChanged) {
// Try to use an existing view for this position.
final View activeView = mRecycler.getActiveView(position);
if (activeView != null) {
// Found it. We're reusing an existing child, so it just needs
// to be positioned like a scrap view.
setupChild(activeView, position, y, flow, childrenLeft, selected, true);
return activeView;
}
}
// Make a new view for this position, or convert an unused view if
// possible.
final View child = obtainView(position, mIsScrap);
// This needs to be positioned and measured.
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
不知道大家看到这里想到了什么?
makeAndAddView()方法里面就出现了缓存机制了,这是提升ListView加载效率的关键方法。我们看到,在获取子View时会先从缓存里面找,也就是会从mRecycler中找,mRecycler是AbsListView中的一个用于缓存的RecycleBin类,来,我们看看缓存的实现
class RecycleBin {
private View[] mActiveViews = new View[0];
/**
* Get the view corresponding to the specified position. The view will be removed from
* mActiveViews if it is found.
*
* @param position The position to look up in mActiveViews
* @return The view if it is found, null otherwise
*/
View getActiveView(int position) {
int index = position - mFirstActivePosition;
final View[] activeViews = mActiveViews;
if (index >=0 && index < activeViews.length) {
final View match = activeViews[index];
activeViews[index] = null;
return match;
}
return null;
}
}
由上可见,缓存的View保存在一个View数组里面,然后我们来看看如果没有找到缓存的View,ListView是怎么获取子View的,也就是上面的obtainView()方法。需要注意的是obtainView()方法是在AbsListView里面。
View obtainView(int position, boolean[] outMetadata) {
//省略其他代码
final View scrapView = mRecycler.getScrapView(position);
final View child = mAdapter.getView(position, scrapView, this);
if (scrapView != null) {
if (child != scrapView) {
// Failed to re-bind the data, return scrap to the heap.
mRecycler.addScrapView(scrapView, position);
} else if (child.isTemporarilyDetached()) {
outMetadata[0] = true;
// Finish the temporary detach started in addScrapView().
child.dispatchFinishTemporaryDetach();
}
}
//省略其他代码
return child;
}
可以看到没有缓存的View直接就是从我们编写的Adapter的getView()方法里面获取。
7、总结
- 适配器模式的经典实现在于把原本不兼容的接口融合在了一起,使之能更好的合作。但在实际开发中也可以有一些灵活的实现,比如ListView。
- 当然过多的使用适配器会让系统显得过于凌乱。如果不是很有必要,可以不适用适配器而是直接对系统进行重构
(4.2.2)桥梁模式(Bridge)
1、定义
将抽象部分与实现部分分离,使它们都可以独立地变化
2、原理
(2.1)UML类图
(2.2)角色
角色 | 说明 |
---|---|
抽象部分 | 这部分保持对实现部分对象的引用,抽象部分中的方法需要调用实现部分的对象来实现 |
优化的抽象部分 | 抽象部分的具体实现,一般是对抽象部分的方法进行完善和扩展 |
实现部分 | 可以为接口或抽象类,其方法不一定要与抽象部分中的一致,一般情况下是由实现部分提供基本的操作,而由抽象部分定义的则是基于实现部分这些基本操作的业务方法 |
实现部分的具体实现 | 完善实现部分中的方法定义的具体逻辑 |
- 桥接模式定义中的“抽象”和“实现”实质上是对应2个独立变化的维度,而不能仅仅简单理解为抽象就是抽象类和接口,实现就是对抽象的实现
- 桥接模式的实现关键在于对抽象和实现的分离的把握
- 在分离出抽象和实现之后,抽象部分抽象出业务方法,由子类去实现;实现部分抽象出基本的操作,也由子类去实现
- 抽象部分的抽象类持有实现部分的抽象引用,以便在抽象部分中加入实现部分的具体操作逻辑,这样便在抽象层建立了一个关联关系
3、使用场景
- 任何多维度变化类或者说多个树状类之间的耦合都可以使用桥接模式来实现解耦
- 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在2个层次之间建立静态的继承关系,可以通过桥接模式使它们在抽象层建立一个关联关系
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,也可以考虑使用桥接模式
- 一个类存在2个独立变化的维度,且这2个维度都需要进行扩展
4、实例
桥接模式中泡咖啡实现:
咖啡简单分大杯小杯(维度1),加不加糖(维度2)。泡咖啡就是业务逻辑,而加不加糖则是基本操作。这样我们把泡咖啡定为抽象部分,子类具体实现是大杯还是小杯;加不加糖则为实现部分,对应子类则实现加不加糖。
1、抽象部分,也就是咖啡类,CoffeeAdditives为实现部分也就是加不加糖,impl为实现部分的引用。
public abstract class Coffee {
protected CoffeeAdditives impl;
public Coffee (CoffeeAdditives impl) {
this.impl = impl;
}
/**
* 咖啡具体是什么样的由子类决定
*/
public abstract void makeCoffee();
}
咖啡的子类,大杯和小杯,加不加糖的操作通过impl添加进来
//大杯咖啡
public class LargeCoffee extends Coffee {
public LargeCoffee(CoffeeAdditives coffeeAdditives) {
super(coffeeAdditives);
}
@Override
public void makeCoffee() {
System.out.println("大杯的" + impl.addSomething() + "咖啡");
}
}
//小杯咖啡
public class SmallCoffee extends Coffee {
public SmallCoffee(CoffeeAdditives coffeeAdditives) {
super(coffeeAdditives);
}
@Override
public void makeCoffee() {
System.out.println("小杯的" + impl.addSomething() + "咖啡");
}
}
下面我们看看实现部分,也就是加不加糖
public abstract class CoffeeAdditives {
/**
* 具体添加什么东西由子类决定
*
* @return 添加的东西,比如加糖
*/
public abstract String addSomething();
}
加糖和不加糖的子类
//加糖
public class Sugar extends CoffeeAdditives {
@Override
public String addSomething() {
return "加糖";
}
}
//不加糖,这里就是原味了
public class Ordinary extends CoffeeAdditives {
@Override
public String addSomething() {
return "原味";
}
}
以上抽象部分和实现部分都完成了,现在我们来看看怎么“泡咖啡”
//原味
Ordinary ordinary = new Ordinary();
//加糖
Sugar sugar = new Sugar();
//大杯咖啡,原味
LargeCoffee largeCoffee = new LargeCoffee(ordinary);
largeCoffee.makeCoffee();
//小杯咖啡,原味
SmallCoffee smallCoffee = new SmallCoffee(ordinary);
smallCoffee.makeCoffee();
//大杯咖啡,加糖
LargeCoffee largeCoffeeSugar = new LargeCoffee(sugar);
largeCoffeeSugar.makeCoffee();
//小杯咖啡,加糖
SmallCoffee smallCoffeeSugar = new SmallCoffee(sugar);
smallCoffeeSugar.makeCoffee();
通过以上的例子我们可以看出桥接模式能够分离抽象与实现,而且扩展也很灵活。比如我们需要添加个中杯的咖啡,只需要在抽象部分,也就是写个Coffee的子类MiddleCoffee就行了,而实现部分,CoffeeAdditives则不受影响。如果咖啡需要加盐或是其他什么的同样也很简单。
5、Android应用
- Adapter与AdapterView
- View的视图层级中,TextView等控件和View之间构成一个继承关系的视图层级,而将控件绘制到屏幕的是与View相关的功能实现类,如Canvas、DisplayList等,这2部分之间的关系可以看作是对桥接模式的应用
- 对于Android来说,应用层与Native层之间的交互也是桥接模式的很好的例子
6、总结
- 桥接模式的关键在于分离抽象部分和实现部分,这2部分的分离也不是绝对的。比如上面的例子,我们当然也可以把添加东西作为抽象部分,而把大杯小杯作为实现部分
- 桥接模式通过依赖抽象达到解耦的目的,而且扩展性也很好,可以应用的地方有很多。
- 桥接模式需要分离抽象和实现,需不需要分离、怎么分离?还是需要权衡。
(4.2.3)代理模式(Proxy)
(4.2.3.1)静态代理
1、介绍
给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用。代理对象:起到中介作用,连接客户端和目标对象。
通过引入代理对象的方式来间接访问目标对象,从而防止直接访问目标对象给系统带来的不必要复杂性。
2、模式原理
(1)UML类图
(2)角色
角色 | 说明 |
---|---|
抽象对象 (Subject) | 声明了真实对象和代理对象的公共接口 |
真实对象(RealSubject) | 代理对象所代表的真实对象,最终引用的对象 |
代理对象(Proxy) | 包含对真实对象的引用从而操作真实主题对象,相当于对真实对象进行封装 |
3、特点
优点
协调调用者和被调用者,降低了系统的耦合度
代理对象作为客户端和目标对象之间的中介,起到了保护目标对象的作用
缺点
由于在客户端和真实主题之间增加了代理对象,因此会造成请求的处理速度变慢;
实现代理模式需要额外的工作(有些代理模式的实现非常复杂),从而增加了系统实现的复杂度。
4、应用场景
5、实例
5.1 实例背景
背景:小成希望买一台最新的顶配Mac电脑
冲突:国内还没上,只有美国才有
解决方案:寻找代购进行购买
代购(代理对象) 代替 我(真实对象) 去买Mac(间接访问的操作)
5.2 解决方案
步骤1: 创建抽象对象接口(Subject):声明你(真实对象)需要让代购(代理对象)帮忙做的事(买Mac)
public interface Subject {
public void buyMac();
}
步骤2: 创建真实对象类(RealSubject),即”我“
public class RealSubject implement Subject{
@Override
public void buyMac() {
System.out.println(”买一台Mac“);
}
}
步骤3:创建代理对象类(Proxy),即”代购“,并通过代理类创建真实对象实例并访问其方法
public class Proxy implements Subject{
@Override
public void buyMac{
//引用并创建真实对象实例,即”我“
RealSubject realSubject = new RealSubject();
//调用真实对象的方法,进行代理购买Mac
realSubject.buyMac();
//代理对象额外做的操作
this.WrapMac();
}
public void WrapMac(){
System.out.println(”用盒子包装好Mac“);
}
}
步骤4:客户端调用
public class ProxyPattern {
public static void main(String[] args){
Subject proxy = new Proxy();
proxy.buyMac();
}
}
6、Android应用
Android源码中的ActivityManagerProxy代理ActivityManagerService类
举例:通过适配不同API版本下发送消息Notification来应用代理模式
步骤1:抽象主题类
public abstract class Notify {
protected Context context;
protected NotificationManager notificationManager;
protected NotificationCompat.Builder builder;
public Notify(Context context) {
this.context = context;
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
builder = new NotificationCompat.Builder(context);
builder.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(PendingIntent.getActivity(context, 0,
new Intent(context, NotifyActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT));
}
/**
* 发送一条通知
*/
public abstract void send();
/**
* 取消一条通知
*/
public abstract void cancel();
}
步骤2:真实主题类(被代理类)
常规的通知的构建
public class NotifyNormal extends Notify {
public NotifyNormal(Context context) {
super(context);
}
@Override
public void send() {
builder.setContent(new RemoteViews(context.getPackageName(), R.layout.layout_notify_normal));
Notification notification = builder.build();
notificationManager.notify(0, notification);
}
@Override
public void cancel() {
notificationManager.cancel(0);
}
}
大视图的通知的构建
public class NotifyBig extends Notify {
public NotifyBig (Context context) {
super(context);
}
@Override
public void send() {
builder.setContent(new RemoteViews(context.getPackageName(), R.layout.layout_notify_normal));
builder.setCustomBigContentView(new RemoteViews(context.getPackageName(), R.layout.layout_notify_normal));
Notification notification = builder.build();
notificationManager.notify(0, notification);
}
@Override
public void cancel() {
notificationManager.cancel(0);
}
}
浮动展示的通知的构建
public class NotifyHeadersUp extends Notify {
public NotifyHeadersUp (Context context) {
super(context);
}
@Override
public void send() {
builder.setContent(new RemoteViews(context.getPackageName(), R.layout.layout_notify_normal));
builder.setCustomBigContentView(new RemoteViews(context.getPackageName(), R.layout.layout_notify_normal));
builder.setCustomHeadsUpContentView(new RemoteViews(context.getPackageName(), R.layout.layout_notify_normal));
Notification notification = builder.build();
notificationManager.notify(0, notification);
}
@Override
public void cancel() {
notificationManager.cancel(0);
}
}
步骤3:代理类
public class NotifyProxy extends Notify {
private Notify notify;
public NotifyProxy (Context context) {
super(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notify = new NotifyHeadersUp(context);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
notify = new NotifyBig(context);
} else {
notify = new NotifyNormal(context);
}
}
@Override
public void send() {
notify.send();
}
@Override
public void cancel() {
notify.cancel();
}
}
步骤4:调用
new NotifyProxy(MainActivity.this).send();
(4.2.3.2)动态代理
1、介绍
(1)为什么使用动态代理
代理模式中的静态代理模式存在一些特点:
- 1个静态代理 只服务1种类型的目标对象
- 若要服务多类型的目标对象,则需要为每种目标对象都实现一个静态代理对象
在目标对象较多的情况下,若采用静态代理,则会出现 静态代理对象量多、代码量大,从而导致代码复杂的问题。故采用动态代理模式解决问题。
(2)介绍
动态代理其实是一种方便运行时候动态的处理代理方法的调用机制。
2、模式原理
- 设计动态代理类(DynamicProxy)时,不需要显式实现与目标对象类(RealSubject)相同的接口,而是将这种实现推迟到程序运行时由 JVM来实现。即:在使用时再创建动态代理类 & 实例;静态代理则是在代理类实现时就指定与目标对象类(RealSubject)相同的接口。
- 通过Java 反射机制的method.invoke(),通过调用动态代理类对象方法,从而自动调用目标对象的方法
3、特点
(1)优点
只需要1个动态代理类就可以解决创建多个静态代理的问题,避免重复、多余代码
更强的灵活性
- 设计动态代理类(DynamicProxy)时,不需要显式实现与目标对象类(RealSubject)相同的接口,而是将这种实现推迟到程序运行时由 JVM来实现
- 在使用时(调用目标对象方法时)才会动态创建动态代理类 & 实例,不需要事先实例化
(2)缺点
- 效率低
相比静态代理中 直接调用目标对象方法,动态代理则需要先通过Java反射机制 从而 间接调用目标对象方法 - 应用场景局限
因为 Java 的单继承特性(每个代理类都继承了 Proxy 类),即只能针对接口 创建 代理类,不能针对类 创建代理类,即只能动态代理 实现了接口的类
4、应用场景
- 基于静态代理应用场景下,需要代理对象数量较多的情况下使用动态代理
- AOP 领域
定义:即 Aspect Oriented Programming = 面向切面编程,是OOP的延续、函数式编程的一种衍生范型
作用:通过预编译方式和运行期动态代理实现程序功能的统一维护。
优点:降低业务逻辑各部分之间的耦合度 、 提高程序的可重用性 & 提高了开发的效率
具体应用场景:日志记录、性能统计、安全控制、异常处理等
5、与静态代理区别
6、实例
(6.1)实例概述
背景:小成 希望买一台最新的顶配 Mac 电脑;小何希望买一台 iPhone
冲突:国内还没上,只有美国才有
解决方案:寻找一个代购一起进行购买。即1个代购(动态代理对象)同时 代替 小成 & 小何(目标对象) 去买Mac(间接访问的操作),该代购是代购任何商品 = 什么人有什么需求就会去代购任何东西(动态代理)
(6.2)解决策略
步骤1: 声明 调用处理器类
DynamicProxy.java
<-- 作用 -->
// 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;
}
}
步骤2: 声明目标对象的抽象接口
Subject.java
public interface Subject {
// 定义目标对象的接口方法
// 代购物品
public void buybuybuy();
}
步骤3: 声明目标对象类
Buyer1.java
// 小成,真正的想买Mac的对象 = 目标对象 = 被代理的对象
// 实现抽象目标对象的接口
public class Buyer1 implements Subject {
@Override
public void buybuybuy() {
System.out.println("小成要买Mac");
}
}
Buyer2.java
// 小何,真正的想买iPhone的对象 = 目标对象 = 被代理的对象
// 实现抽象目标对象的接口
public class Buyer2 implements Subject {
@Override
public void buybuybuy() {
System.out.println("小何要买iPhone");
}
}
步骤4: 通过动态代理对象,调用目标对象的方法
MainActivity.java
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();
}
}
7、源码分析
目标对象 通过 动态代理对象调用方法的使用 来进行动态代理模式的源码分析
// 步骤4:通过动态代理对象,调用目标对象的方法
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);
// ->>关注1
// 4. 通过调用动态代理对象方法从而调用目标对象方法
// ->>关注2
Buyer1_DynamicProxy.buybuybuy();
// 以上代购为小成代购Mac
// 以下是代购为小何代购iPhone
Buyer2 mBuyer2 = new Buyer2();
Subject Buyer2_DynamicProxy = (Subject) DynamicProxy.newProxyInstance(mBuyer2);
Buyer2_DynamicProxy.buybuybuy();
}
}
关注一:创建动态代理类 & 对象:通过调用处理器类对象newProxyInstance()
// 使用代码
Subject Buyer1_DynamicProxy = (Subject) DynamicProxy.newProxyInstance(mBuyer1);
即,动态代理类 及其对象实例是如何生成的?
下面,我们直接进入DynamicProxy.newProxyInstance()
<-- 关注1:调用处理器 类的newProxyInstance() -->
// 即步骤1中实现的类:DynamicProxy
// 作用:
// 1. 生成 动态代理对象
// 2. 指定 代理对象运行目标对象方法时需要完成的 具体任务
// 注:需实现InvocationHandler接口 = 调用处理器 接口
public class DynamicProxy implements InvocationHandler {
// 声明代理对象
private Object ProxyObject;
public Object newProxyInstance(Object ProxyObject){
this.ProxyObject =ProxyObject;
return Proxy.newProxyInstance(ProxyObject.getClass().getClassLoader(),
ProxyObject.getClass().getInterfaces(),this);
// Proxy.newProxyInstance()作用:根据指定的类装载器、一组接口 & 调用处理器 生成动态代理类实例,并最终返回
// ->>关注2
}
// 以下暂时忽略,下文会详细介绍
// 复写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;
}
// 至此,关注1分析完毕,跳出
}
<-- 关注2:newProxyInstance()源码解析-->
// 作用:根据指定的类装载器、一组接口 & 调用处理器 生成动态代理类及其对象实例,并最终返回
public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h) throws IllegalArgumentException {
// 参数说明:
// 参数1:指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
// 参数2:指定目标对象的实现接口
// 即要给目标对象提供一组什么接口。若提供了一组接口给它,那么该代理对象就默认实现了该接口,这样就能调用这组接口中的方法
// 参数3:指定InvocationHandler对象。即动态代理对象在调用方法时,会关联到哪个InvocationHandler对象
...
// 仅贴出核心代码
// 1. 通过 为Proxy类指定类加载器对象 & 一组interface,从而创建动态代理类
// >>关注3
Class cl = getProxyClass(loader, interfaces);
// 2. 通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
Constructor cons = cl.getConstructor(constructorParams);
// 3. 通过动态代理类的构造函数 创建 代理类实例(传入调用处理器对象
return (Object) cons.newInstance(new Object[] { h });
// 特别注意
// 1. 动态代理类继承 Proxy 类 & 并实现了在Proxy.newProxyInstance()中提供的一系列接口(接口数组)
// 2. Proxy 类中有一个映射表
// 映射关系为:(<ClassLoader>,(<Interfaces>,<ProxyClass>) )
// 即:1级key = 类加载器,根据1级key 得到 2级key = 接口数组
// 因此:1类加载器对象 + 1接口数组 = 确定了一个代理类实例
...
// 回到调用关注2的原处
}
<-- 关注3:getProxyClass()源码分析 -->
// 作用:创建动态代理类
public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces)
throws IllegalArgumentException {
...
// 仅贴出关键代码
<-- 1. 将目标类所实现的接口加载到内存中 -->
// 遍历目标类所实现的接口
for (int i = 0; i < interfaces.length; i++) {
// 获取目标类实现的接口名称
String interfaceName = interfaces[i].getName();
Class interfaceClass = null;
try {
// 加载目标类实现的接口到内存中
interfaceClass = Class.forName(interfaceName, false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != interfaces[i]) {
throw new IllegalArgumentException(
interfaces[i] + " is not visible from class loader");
}
}
<-- 2. 生成动态代理类 -->
// 根据传入的接口 & 代理对象 创建动态代理类的字节码
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
// 根据动态代理类的字节码 生成 动态代理类
proxyClass = defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
}
// 最终返回动态代理类
return proxyClass;
}
// 回到调用关注3的原处
总结
- 通过调用处理器类对象的.newProxyInstance()创建动态代理类 及其实例对象(需传入目标类对象)
- 具体过程如下:
- 通过 为Proy类指定类加载器对象 & 一组接口,从而创建动态代理类的字节码;再根据类字节码创建动态代理类
- 通过反射机制获取动态代理类的构造函数(参数类型 = 调用处理器接口类型
- 通过动态代理类的构造函数 创建 代理类实例(传入调用处理器对象
关注二:通过调用动态代理对象方法从而调用目标对象方法
即,如何通过调用动态代理对象方法,从而调用目标对象方法?
// 使用代码
Buyer1_DynamicProxy.buybuybuy();
在关注1中的 DynamicProxy.newProxyInstance()生成了一代理类 及其实例
该动态代理类记为 :$Proxy0
下面我们直接看该类实现及其 buybuybuy(),该方法的逻辑如下:
<-- 动态代理类 $Proxy0 实现-->
// 继承:Java 动态代理机制的主类:java.lang.reflect.Proxy
// 实现:与目标对象一样的接口(即上文例子的Subject接口)
public final class $Proxy0 extends Proxy implements Subject {
// 构造函数
public ProxySubject(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
// buybuybuy()是目标对象实现接口(Subject)中的方法
// 即$Proxy0类必须实现
// 所以在使用动态代理类对象时,才可以调用目标对象的同名方法(即上文的buybuybuy())
public final void buybuybuy() {
try {
super.h.invoke(this, m3, null);
// 该方法的逻辑实际上是调用了父类Proxy类的h参数的invoke()
// h参数即在Proxy.newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h)传入的第3个参数InvocationHandler对象
// 即调用了调用处理器的InvocationHandler.invoke()
// 而复写的invoke()利用反射机制:Object result=method.invoke(proxied,args)
// 从而调用目标对象的的方法 ->>关注4
return;
} catch (Error e) {
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
<-- 关注4:调用处理器 类复写的invoke() -->
// 即步骤1中实现的类:DynamicProxy
// 内容很多都分析过了,直接跳到复写的invoke()中
public class DynamicProxy implements InvocationHandler {
private Object ProxyObject;
public Object newProxyInstance(Object ProxyObject){
this.ProxyObject =ProxyObject;
return Proxy.newProxyInstance(ProxyObject.getClass().getClassLoader(),
ProxyObject.getClass().getInterfaces(),this);
}
// 复写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;
}
总结
- 动态代理类实现了与目标类一样的接口,并实现了需要目标类对象需要调用的方法
该方法的实现逻辑 = 调用父类 Proxy类的 h.invoke() - 其中h参数 = 在创建动态代理实例中newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h)传入的第3个参数InvocationHandler对象
- 在 InvocationHandler.invoke() 中通过反射机制,从而调用目标类对象的方法
(4.2.4)装饰模式(Decorate)
1、定义
动态的给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类(继承)更为灵活的方案。
2、场景
需要透明且动态地扩展类的功能
3、模式原理
(3.1)UML类图
(3.2)角色
角色 | 说明 |
---|---|
抽象组件 | 可以是抽象类或接口,是被装饰类的原始对象 |
组件具体实现类 | 该类是抽象组件的具体实现,也是我们装饰的具体对象 |
抽象装饰者 | 为了装饰我们的组件对象,其内部一定要有一个指向组件对象的引用。在大多数情况下,该类为抽象类,需要根据不同的装饰逻辑实现不同的子类。如果装饰逻辑单一,只有一个的情况下我们可以省略该类直接作为具体的装饰者 |
具体的装饰者 | 对抽象装饰做具体的实现 |
4、装饰者模式和代理模式区别
- 装饰模式是对客户端以透明的方式扩展对象的功能,是继承关系的一种替代;而代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用
- 装饰模式应该为所装饰的对象增加功能,而代理对象对所代理的对象施加控制,但不对对象本身的功能进行增强
5、实例
抽象主题类Notify
public abstract class Notify {
protected Context context;
protected NotificationManager notificationManager;
protected NotificationCompat.Builder builder;
public Notify(Context context) {
this.context = context;
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
builder = new NotificationCompat.Builder(context);
builder.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(PendingIntent.getActivity(context, 0,
new Intent(context, NotifyActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT));
}
/**
* 发送一条通知
*/
public abstract void send();
/**
* 取消一条通知
*/
public abstract void cancel();
}
被装饰类NotifyNormal
public class NotifyNormal extends Notify {
public NotifyNormal(Context context) {
super(context);
}
@Override
public void send() {
Notification notification = builder.build();
notificationManager.notify(0, notification);
}
@Override
public void cancel() {
notificationManager.cancel(0);
}
}
抽象装饰者
public abstract class NotifyDecorator extends Notify {
private Notify notify;
public NotifyDecorator (Context context, Notify mNotify) {
super(context);
this.notify = mNotify;
}
@Override
public void send() {
notify.send();
}
@Override
public void cancel() {
notify.cancel();
}
}
具体装饰者
均是在被装饰对象NotifyNormal的基础上增加或更改了通知的布局
NotifyNormalDecorator
public class NotifyNormalDecorator extends NotifyDecorator {
public NotifyNormalDecorator (Context context, Notify notify) {
super(context, notify);
}
@Override
public void send() {
builder.setContent(new RemoteViews(context.getPackageName(), R.layout.layout_notify_normal));
super.send();
}
}
NotifyBigDecorator
public class NotifyBigDecorator extends NotifyDecorator {
public NotifyBigDecorator(Context context, Notify notify) {
super(context, notify);
}
@Override
public void send() {
builder.setContent(new RemoteViews(context.getPackageName(), R.layout.layout_notify_normal));
builder.setCustomBigContentView(new RemoteViews(context.getPackageName(), R.layout.layout_notify_normal));
super.send();
}
}
NotifyHeadsUpDecorator
public class NotifyHeadsUpDecorator extends NotifyDecorator {
public NotifyHeadsUpDecorator(Context context, Notify notify) {
super(context, notify);
}
@Override
public void send() {
builder.setContent(new RemoteViews(context.getPackageName(), R.layout.layout_notify_normal));
builder.setCustomBigContentView(new RemoteViews(context.getPackageName(), R.layout.layout_notify_normal));
builder.setCustomHeadsUpContentView(new RemoteViews(context.getPackageName(), R.layout.layout_notify_normal));
super.send();
}
}
具体使用
public class NotifyProxy extends Notify{
private NotifyDecorator notifyDecorator;
public NotifyProxy (Context context) {
super(context);
Notify notify = new NotifyNormal(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notifyDecorator = new NotifyHeadsUpDecorator(context, notify);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
notifyDecorator = new NotifyBigDecorator(context, notify);
} else {
notifyDecorator = new NotifyNormalDecorator(context, notify);
}
}
@Override
public void send() {
notifyDecorator.send();
}
@Override
public void cancel() {
notifyDecorator.cancel();
}
}
调用
new NotifyProxy(MainActivity.this).send();
从上面我们对代理模式中的示例代码进行改造的过程我们可以看出,装饰模式主要在于扩展了类的功能。
装饰模式通过在被装饰组件的方法执行之前或之后加入新的方法来实现功能的扩展
6、Android应用
Android源码中的ContextWrapper
(4.2.5)外观模式(Facade Pattern)
1、介绍
外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。
用于为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
2、模式原理
(2.1)UML类图
(2.2)角色
角色 | 说明 |
---|---|
外观接口/类 | |
其他内部子系统 |
主要是把所有需要对外暴露的方法都统一在外观接口或类里面,隐藏其他的子模块
3、特点
- 外观模式是一个高频率使用的设计模式,关键就在于封装
- 外观模式对外隐藏了子系统的实现细节,减少了客户端对子系统的耦合,能拥抱变化
- 外观类对子系统的接口封装,使得系统更易于使用
4、实例
先通过一个简单的例子来了解外观模式。我们的手机可以看作是一个外观类,而手机中的功能,比如打电话、相机则是各种子模块。
打电话的功能
//电话功能接口
public interface Phone {
//打电话
public void dail();
//挂断
public void hangup();
}
//电话的实现类
public class PhoneImpl implements Phone {
@Override
public void dail() {
System.out.println("打电话");
}
@Override
public void hangup() {
System.out.println("挂断");
}
}
相机的功能
//相机功能接口
public interface Camera {
//打开相机
public void open();
//拍照
public void takePhoto();
//关闭相机
public void close();
}
//相机功能的实现类
public class MiCamera implements Camera {
@Override
public void open() {
System.out.println("打开相机");
}
@Override
public void takePhoto() {
System.out.println("拍照");
}
@Override
public void close() {
System.out.println("关闭相机");
}
}
手机
public class MobilePhone {
private Phone mPhone = new PhoneImpl();
private Camera mCamera = new MiCamera();
//拍照
public void takePhoto() {
mCamera.open();
mCamera.takePhoto();
mCamera.close();
}
//视频聊天
public void videoChat() {
mCamera.open();
mPhone.dail();
}
}
我们使用手机时,需要用到拍照和视频聊天的功能,但我们不需要知道相机的信息,也不需要知道视频聊天要用到了哪些类,Phone的接口和实现等,只需要用到手机MobilePhone这个类和它提供的接口takePhoto和videoChat
MobilePhone mobilePhone = new MobilePhone();
mobilePhone.takePhoto();
mobilePhone.videoChat();
下面我们再通过我们的ImageLoader来看看外观模式的运用
外观类ImageLoader,所有的功能都通过调用ImageLoader类来实现
public class ImageLoader {
//图片加载配置
ImageLoaderConfig mConfig;
// 图片缓存,依赖接口
ImageCache mImageCache = new MemoryCache();
//请求队列
private RequestQueue requestQueue;
private static ImageLoader mImageLoader = null;
private ImageLoader () {}
public static ImageLoader getInstance() {
//省略单例实现
}
public void init(ImageLoaderConfig config) {
mConfig = config;
mImageCache = config.mImageCache;
checkConfig();
requestQueue = new RequestQueue(config.threadCount);
requestQueue.start();
}
private void checkConfig() {
//省略部分代码
}
public ImageLoaderConfig getConfig() {
return mConfig;
}
public void displayImage(final ImageView imageView, String url) {
displayImage(imageView, url, null);
}
public void displayImage(final ImageView imageView, String url, DisplayConfig config) {
ImageRequest request = new ImageRequest(imageView, url, config);
request.displayConfig = request.displayConfig != null ? request.displayConfig : mConfig.displayConfig;
requestQueue.addRequest(request);
}
}
ImageLoader类里面封装了配置类ImageLoaderConfig和请求队列RequestQueue。请求队列RequestQueue里面又封装了线程模型等
调用ImageLoader的init方法以后,用户就可以直接用display方法来加载显示图片了,而不用管网络请求、队列这些细节
所有的实现细节都被封装在ImageLoader类下面,用户只需要操作ImageLoader的接口就可以完成图片的加载操作,这样就避免暴露了过多的实现细节,而且用户使用起来也更加简单
5、Android应用
- 很多的第三方SDK,比如友盟统计
- 我们平时开发过程中封装的模块,比如网络模块、ImageLoader模块等
(4.3)行为型
本质
对在不同的对象之间划分责任和算法的抽象画
类型
1、类的行为模式:使用继承关系在几个类之间分配行为
2、对象的行为模式:使用对象聚合的方式来分配行为
(4.3.1)模板方法模式(Template Method)
定义一个模板结构,将具体内容延迟到子类去实现。在不改变模板结构的前提下在子类中重新定义模板中的内容。
模板方法模式是基于”继承“的;
(4.3.2)观察者模式(Observer Pattern)
1、介绍
定义对象间的一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
适用于事件多级触发场景、跨系统消息交换场景,如消息队列、事件总线的处理机制。
2、模式原理
(2.1)UML图
(2.2)角色
角色 | 说明 |
---|---|
抽象主题/被观察者(Observable) | 抽象主题把所有的观察者对象的引用保存在一个集合里,每个主题可以有任意数量的观察者,抽象主题提供接口,可以增加和删除观察者对象 |
具体的主题(具体的被观察者) | 也就是抽象主题的子类,该角色将有关状态存入具体观察者对象,在具体主题内部状态发生改变时,通知所有注册过的观察者 |
抽象观察者 | 观察者的抽象类,定义了一个更新的接口 |
具体的观察者 | 实现了抽象观察者的更新接口,在被观察者状态发生变化时更新自身的状态 |
(2.3)基本步骤
步骤1:创建Subject类(被观察者)
public class Subject {
private List<Observer> observers
= new ArrayList<Observer>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
//状态改变时,通知所有观察者
this.state = state;
notifyAllObservers();
}
public void attach(Observer observer){
observers.add(observer);
}
public void notifyAllObservers(){
//遍历观察者列表,通知每一个观察者
for (Observer observer : observers) {
observer.update();
}
}
}
步骤2:创建Observer类(抽象观察者)
public abstract class Observer {
protected Subject subject;
public abstract void update();
}
步骤3:创建实体观察者
1、BinaryObserver.java
public class BinaryObserver extends Observer{
public BinaryObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Binary String: "
+ Integer.toBinaryString( subject.getState() ) );
}
}
2、OctalObserver.java
public class OctalObserver extends Observer{
public OctalObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Octal String: "
+ Integer.toOctalString( subject.getState() ) );
}
}
3、HexaObserver.java
public class HexaObserver extends Observer{
public HexaObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Hex String: "
+ Integer.toHexString( subject.getState() ).toUpperCase() );
}
}
步骤4:使用
public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new Subject();
new HexaObserver(subject);
new OctalObserver(subject);
new BinaryObserver(subject);
System.out.println("First state change: 15");
subject.setState(15);
System.out.println("Second state change: 10");
subject.setState(10);
}
}
3、特点
一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。具体实现是通过在抽象类中用一个ArrayList存放观察者们。
观察者和被观察者是抽象耦合的,但是很耗时。
4、实例
利用JDK中Observable类和Observer接口实现
JDK中有Observable类和Observer接口,观察者实现Observer接口,被观察者继承Observable类,被观察者通过Observable类的addObserver方法添加观察者。
观察者
public class MyObserver implements Observer{
private String mName;
public MyObserver(String name) {
mName = name;
}
@Override
public void update(Observable o, Object arg) {
System.out.println(mName + "-->" + "update: " + arg);
}
}
Observer接口就一个update方法,Observable表示被观察者,Object表示被观察者更新的东西
被观察者
public class MyObservable extends Observable{
public void sendChangeMeg(String content) {
//方法继承自Observable,标示状态或是内容发生改变
setChanged();
//方法继承自Observable,通知所有观察者,最后会调用每个Observer的update方法
notifyObservers(content);
}
}
被观察者通过setChanged()方法标示改变,通过notifyObservers方法通知所有观察者
notifyObservers方法会遍历所有的观察者Observer,并调用它们的update方法,notifyObservers方法中的参数就是最后传到观察者update方法的参数Object arg。
使用
public class ObserverPatternTest {
@Test
public void test1() throws Exception {
MyObservable myObservable = new MyObservable();
MyObserver myObserver1 = new MyObserver("observer-1");
MyObserver myObserver2 = new MyObserver("observer-2");
myObservable.addObserver(myObserver1);
myObservable.addObserver(myObserver2);
//发布消息
myObservable.sendChangeMeg("发布更新啦");
}
}
源码分析
(1)Observable源码
public class Observable {
private boolean changed = false;
private final ArrayList<Observer> observers;
/** Construct an Observable with zero Observers. */
public Observable() {
observers = new ArrayList<>();
}
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!observers.contains(o)) {
observers.add(o);
}
}
public synchronized void deleteObserver(Observer o) {
observers.remove(o);
}
public void notifyObservers() {
notifyObservers(null);
}
public void notifyObservers(Object arg) {
/*
* a temporary array buffer, used as a snapshot of the state of
* current Observers.
*/
Observer[] arrLocal;
synchronized (this) {
if (!hasChanged())
return;
arrLocal = observers.toArray(new Observer[observers.size()]);
clearChanged();
}
//注意这里的起始点
for (int i = arrLocal.length-1; i>=0; i--)
arrLocal[i].update(this, arg);
}
public synchronized void deleteObservers() {
observers.clear();
}
protected synchronized void setChanged() {
changed = true;
}
protected synchronized void clearChanged() {
changed = false;
}
public synchronized boolean hasChanged() {
return changed;
}
public synchronized int countObservers() {
return observers.size();
}
}
首先在生成Observable对象时,会初始化一个ArrayList,用于保存所有的观察者Observer
当我们调用notifyObservers时,会循环遍历调用所有添加的观察者Observer,并调用Observer的update方法,而遍历的顺序是从最后添加的一个Observer开始的,所以会有我们上面测试结果图片的情况(从后往前调用)
(2)Observer源码
解耦的关键就在于,Observer是一个接口,而我们的观察者都实现了这个接口
public interface Observer {
void update(Observable o, Object arg);
}
5、Android应用
- 常见的发布-订阅模式
- ListView的Adapter的notifyDataSetChanged更新方法
- BroadcastReceiver
- 开源库EventBus
- RxJava
(4.3.3)状态模式(State Pattern)
1、介绍
在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
当代码中包含着大量与对象状态有关的条件语句。此时对象行为依赖于它的状态(属性),并可以根据它的状态改变它的相关行为。
2、模式原理
(2.1)UML图
(2.2)角色
角色 | 说明 |
---|---|
环境类 | 定义客户端感兴趣的接口,维护一个抽象状态类的子类的实例,这个实例定义了对象当前状态 |
抽象状态类或状态接口 | 定义一个或一组接口,表示该状态下的行为 |
具体状态类 | 每个具体状态类实现抽象状态类中定义的接口,从而达到不同状态下的不同行为 |
把不同状态下的行为抽象成共同的接口到抽象状态类中
根据不同的状态,具体的状态类实现抽象状态类中的接口,从而实现不同的行为
环境类,也就是对外提供服务的类通过依赖抽象状态类来实现具体的行为,同时也达到与具体状态类的解耦
3、特点
- 状态模式的关键点在于不同的状态下对于同一行为有不同的响应,这其实就是一个将if-else用多态来实现的具体实例
- 状态模式将所有与一个特定状态相关的行为都放入一个状态对象中,它提供了一个更好的方法来组织与特定状态相关的代码,将繁琐的状态判断转换成结构清晰的状态类族,在避免代码膨胀的同时也保证了可扩展性和可维护性
4、实例
下面我们以日常开发中的登录功能来简单应用下状态模式
在登录模块中一般我们会根据用户是否登录状态而有不同的操作,这里简单模拟2个操作,转发和评论
public interface UserState {
/**
* 转发
*
* @param context
*/
public void forward(Context context);
/**
* 评论
*
* @param context
*/
public void comment(Context context);
}
登录状态,调用转发和评论直接简单地弹出个Toast
public class LoginedState implements UserState {
@Override
public void forward(Context context) {
Toast.makeText(context, "转发成功", Toast.LENGTH_SHORT).show();
}
@Override
public void comment(Context context) {
Toast.makeText(context, "评论成功", Toast.LENGTH_SHORT).show();
}
}
未登录状态,调用转发和评论跳转到登录页面,这里直接简单地弹出登录的提示
public class LogoutState implements UserState {
@Override
public void forward(Context context) {
Toast.makeText(context, "未登录,跳转到登录页面", Toast.LENGTH_SHORT).show();
}
@Override
public void comment(Context context) {
Toast.makeText(context, "未登录,跳转到登录页面", Toast.LENGTH_SHORT).show();
}
}
根据不同的状态表现不一样的行为。下面我们看看对外提供服务的环境类
public class LoginContext {
/**
* 用户状态,默认为登录状态
*/
UserState userState = new LoginedState();
/**
* 单例
*/
static LoginContext loginContext = new LoginContext();
private LoginContext() {
}
public LoginContext getLoginContext() {
return loginContext;
}
public void setUserState(UserState userState) {
this.userState = userState;
}
public void forward(Context context) {
userState.forward(context);
}
public void comment(Context context) {
userState.comment(context);
}
}
以上的环境类的实现中,默认状态设置为登录状态,这个可以根据实际需要来定。下面我们看看怎么调用
//因为默认是登录状态,所以可以直接调用转发功能
LoginContext.getLoginContext().forward(MainActivity.this)
//注销登录,也就是登录状态改为未登录
LoginContext.getLoginContext().setUserState(new LogoutState());
//注销登录了以后再调用转发或是评论功能,执行的就会是弹出登录提示
LoginContext.getLoginContext().forward(MainActivity.this)
通过上面的例子我们可以看出,状态模式把对象的不同状态下的行为封装起来,并与对象的状态联系在一起。对象的状态改变了,对象的行为也会改变。在上面的例子中就是登录的状态LoginContext中的UserState状态不一样,执行的行为,比如转发功能就不一样。
(4.3.4)策略模式(Stratege Pattern)
1、介绍
策略模式定义了一系列算法,并将每一个算法封装起来,而且使它们可以相互替换。策略模式让算法独立于使用它的客户端而独立变化。适用于针对同一类型问题的多种处理方式,仅仅是具体行为有差别时。
策略模式主要用来分离算法,在相同的行为抽象下有不同的具体实现策略。
策略模式很好地展示了开闭原则,也就是定义抽象,注入不同的实现,从而达到很好的扩展性
当我们在实现一个功能时遇到很多if-else或是switch-case的时候就可以考虑下这里是不是可以用策略模式了
2、模式原理
(2.1)UML图
(2.2)角色
角色 | 说明 |
---|---|
环境类 | 用来操作策略的上下文环境 |
抽象策略类/接口 | 策略的抽象 |
具体策略类 | 具体的策略实现 |
将不同策略的相同的行为抽象到抽象策略中
具体的策略实现不同的算法
上下文环境根据需要注入需要的策略,并把相应的操作委托给注入的策略处理
3、策略模式与状态模式区别
状态模式和策略模式的结构几乎完全一样,但它们的目的、本质却完全不一样。状态模式的行为是平行的、不可替换的,策略模式的行为是彼此独立、可相互替换的。用一句话来表述,状态模式把对象的行为包装在不同的状态对象里,每一个状态对象都有一个共同的抽象状态基类。状态模式的意图是让一个对象在其内部状态发生改变的时候,其行为也随之改变
4、实例
下面我们以计算不同交通工具的车费来简单看看策略模式的实现
首先抽象出计算车费的操作,因为不同的策略最后都会计算车费并返回结果
public interface CalculateStragety {
/**
* 根据公里数计算价格
*
* @param km 公里数
* @return 价格
*/
int calculatePrice(int km);
}
然后我们分别实现具体的策略,比如计算公交车的车费的策略
public class BusStragety implements CalculateStragety {
/**
* 十公里之内一元,超过十公里每加一元钱可以坐5公里
* @param km 公里数
* @return 公交车车费
*/
@Override
public int calculatePrice(int km) {
//超过十公里的总距离
int extraTotal = km - 10;
// 超过的距离是5公里的倍数
int extraFactor = extraTotal / 5;
//超过的距离对5公里取余
int fraction = extraTotal % 5;
//价格计算
int price = 1 + extraFactor * 1 ;
return fraction > 0 ? ++price : price;
}
}
再加一个出租车的车费计算策略
public class TaxiStragety implements CalculateStragety {
/**
* 出租车车费为每公里2元
* @param km 公里数
* @return 出租车车费
*/
@Override
public int calculatePrice(int km) {
return km * 2;
}
}
然后我们看看怎么根据需要注入不同的策略,并把具体的计算委托给注入的策略
public class TrafficCalculator {
public static void main(String[] args) {
TrafficCalculator trafficCalculator = new TrafficCalculator();
trafficCalculator.setCalculateStragety(new BusStragety());
trafficCalculator.calculatePrice(66);
}
CalculateStragety mCalculateStragety;
/**
* 根据需要注入相应的策略
*
* @param calculateStragety 注入的策略
*/
public void setCalculateStragety(CalculateStragety calculateStragety) {
mCalculateStragety = calculateStragety;
}
/**
* 把具体的计算委托给注入的策略
*
* @param km 公里数
* @return 车费
*/
private int calculatePrice(int km) {
return mCalculateStragety.calculatePrice(km);
}
}
5、Android应用
Android中属性动画的时间差值器分为线性差值器、加速减速差值器等,这些差值器里面就用到了策略模式来隔离不同的动画速率计算算法
(4.3.5)责任链模式(Chain of Responsibility Pattern)
1、介绍
责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
2、作用
避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
适用于:
- 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
- 可动态指定一组对象处理请求。
3、实例
日志打印。
我们创建抽象类 AbstractLogger,带有详细的日志记录级别。然后我们创建三种类型的记录器,都扩展了 AbstractLogger。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器。
步骤 1:创建抽象的记录器类。
AbstractLogger.java
public abstract class AbstractLogger {
public static int INFO = 1;
public static int DEBUG = 2;
public static int ERROR = 3;
protected int level;
//责任链中的下一个元素
protected AbstractLogger nextLogger;
public void setNextLogger(AbstractLogger nextLogger){
this.nextLogger = nextLogger;
}
public void logMessage(int level, String message){
if(this.level <= level){
write(message);
}
if(nextLogger !=null){
nextLogger.logMessage(level, message);
}
}
abstract protected void write(String message);
}
步骤 2:创建扩展了该记录器类的实体类。
ConsoleLogger.java
public class ConsoleLogger extends AbstractLogger {
public ConsoleLogger(int level){
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Standard Console::Logger: " + message);
}
}
ErrorLogger.java
public class ErrorLogger extends AbstractLogger {
public ErrorLogger(int level){
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Error Console::Logger: " + message);
}
}
FileLogger.java
public class FileLogger extends AbstractLogger {
public FileLogger(int level){
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("File::Logger: " + message);
}
}
步骤 3:创建不同类型的记录器。赋予它们不同的错误级别,并在每个记录器中设置下一个记录器。每个记录器中的下一个记录器代表的是链的一部分。
ChainPatternDemo.java
public class ChainPatternDemo {
private static AbstractLogger getChainOfLoggers(){
AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
errorLogger.setNextLogger(fileLogger);
fileLogger.setNextLogger(consoleLogger);
return errorLogger;
}
public static void main(String[] args) {
AbstractLogger loggerChain = getChainOfLoggers();
loggerChain.logMessage(AbstractLogger.INFO, "This is an information.");
loggerChain.logMessage(AbstractLogger.DEBUG,
"This is a debug level information.");
loggerChain.logMessage(AbstractLogger.ERROR,
"This is an error information.");
}
}
步骤 4:执行程序,输出结果
Standard Console::Logger: This is an information.
File::Logger: This is a debug level information.
Standard Console::Logger: This is a debug level information.
Error Console::Logger: This is an error information.
File::Logger: This is an error information.
Standard Console::Logger: This is an error information.
4、Android中应用
OkHttp中拦截器链的使用