[TOC]
行为型模式是对在不同的对象之间划分责任和算法的抽象化,重点关注类(或者对象)之间的相互作用。
行为型模式分为类行为型模式和对象行为型模式两种。包含
责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模版方法模式及访问者模式。
# 1 观察者模式
观察者模式又称发布-订阅模式、模型-视图模式、源-监听器模式、从属者模式
## 1.1 模式定义
定义了对象间的一种一对多的依赖关系。
一个对象(观察目标)发生改变时,将自动通知其他对象(观察者),其他对象做出相应的反应。
## 1.2 模式结构
包含如下角色:
* Subject:抽象目标(又称主题)
把所有观察者对象的引用保存在一个集合中,并且提供接口可以添加、删除观察者
* ConcreteSubject:具体目标
抽象目标类的子类,通常包含有经常发生改变的数据或状态
* Observer:抽象观察者
定义一个更新接口,在得到目标通知时更新自己
* ConcreteObserver:具体观察者
存储与目标状态一致的状态,如果需要还可以持有一个指向具体目标对象的引用
## 1.3 代码示例
抽象目标:
```java
public abstract class Nexus6P {
// 所有观察者对象保存在集合中
private List<Customer> mCustomerList = new ArrayList<>();
// 添加观察者
public void attach(Customer customer) {
if (customer == null) {
throw new NullPointerException();
}
if (!mCustomerList.contains(customer)) {
mCustomerList.add(customer);
}
}
// 删除观察者
public void detach(Customer customer) {
mCustomerList.remove(customer);
}
// 通知所有注册的观察者
public void notifyCustomers(float nowPrice) {
for (Customer customer : mCustomerList) {
customer.update(nowPrice);
}
}
}
```
具体目标:
```java
public class ConcreteNexus6P extends Nexus6P {
// 包含经常发生改变的数据或状态
private float mNowPrice;
public float getNowPrice() {
return mNowPrice;
}
// 改变数据或状态
public void changePrice(float nowPrice) {
mNowPrice = nowPrice;
// 状态改变,通知各个观察者
this.notifyCustomers(mNowPrice);
}
}
```
抽象观察者:
```java
public interface Customer {
//状态更新接口
void update(float nowPrice);
}
```
具体观察者:
```java
public class ConcreteCustomer implements Customer {
private static final String TAG = "ConcreteCustomer";
// 存储与目标一致的状态
private float mNowPrice;
@Override
public void update(float nowPrice) {
// 更新观察者状态,使其与观察目标状态保持一致
mNowPrice = nowPrice;
if (mNowPrice < 3000) {
buyIt();
} else {
Log.i(TAG, "Too expensive!");
}
}
private void buyIt() {
// buy it
}
}
```
客户端类:
```java
public class JingDongStore {
public static void main(String[] args) {
ConcreteNexus6P nexus6P = new ConcreteNexus6P();
Customer Mark = new ConcreteCustomer();
nexus6P.attach(Mark);
// nexus6P.attach(Tom);
// ...
nexus6P.changePrice(3200);
nexus6P.changePrice(2800);
}
}
```
观察者模式又分为推模型和拉模型两种,以上例子属于推模型。
* 推模型:由观察目标通过更新方法向观察者推送全部或部分数据
* 拉模型:由观察者主动从观察目标中拉数据,一般在更新方法中观察目标会把自身传递过去,观察者持有观察目标引用
拉模型的例子如下:
```java
// 抽象目标
public abstract class Nexus6P {
private List<Customer> mCustomerList = new ArrayList<>();
public void attach(Customer customer) {
if (customer == null) {
throw new NullPointerException();
}
if (!mCustomerList.contains(customer)) {
mCustomerList.add(customer);
}
}
public void detach(Customer customer) {
mCustomerList.remove(customer);
}
public void notifyCustomers() {
for (Customer customer : mCustomerList) {
customer.update(this);
}
}
}
// 具体目标
public class ConcreteNexus6P extends Nexus6P {
private float mNowPrice;
public float getNowPrice() {
return mNowPrice;
}
public void changePrice(float nowPrice) {
mNowPrice = nowPrice;
this.notifyCustomers();
}
}
// 抽象观察者
public interface Customer {
void update(Nexus6P nexus6P);
}
// 具体观察者
public class ConcreteCustomer implements Customer {
private static final String TAG = "ConcreteCustomer";
// 可以持有具体观察目标的引用
private ConcreteNexus6P mConcreteNexus6P;
private float mNowPrice;
@Override
public void update(Nexus6P nexus6P) {
// 更新观察者状态,使其与观察目标状态保持一致
mConcreteNexus6P = (ConcreteNexus6P) nexus6P;
mNowPrice = mConcreteNexus6P.getNowPrice();
if (mNowPrice < 3000) {
buyIt();
} else {
Log.i(TAG, "Too expensive!");
}
}
private void buyIt() {
// buy it
}
}
```
## 1.4 总结
* 优点在于可以实现表示层和数据逻辑层的分离,在观察目标和观察者之间建立一个抽象的耦合
* 缺点在于一个观察目标有很多直接或间接的观察者时,将所有的观察者全部通知到会花费很多时间;且观察者和观察目标之间有循环依赖时,可能会导致崩溃
# 2 命令模式
又称动作(Action)模式、事物(Transaction)模式
## 2.1 模式定义
命令模式把一个请求或者操作封装到一个对象中。
## 2.2 模式结构
* 接收者
负责具体实施和执行请求。
* 抽象命令类
声明了给所有具体命令类的接口方法(execute 方法等),通常叫做执行方法。
* 具体命令类
实现 execute 方法,负责调用接收者的相应操作。
* 请求者
负责调用命令对象(的 execute 方法)执行请求,相关的方法称为行动方法。
* 客户端
负责创建具体命令对象,并确定其接收者。
## 2.3 代码示例
接收者:
```java
public class Nexus6P {
// 真正执行命令的相应操作
public void playMusic() {
// play music
}
public void playVideo() {
// play video
}
public void playGames() {
// play games
}
}
```
抽象命令类:
```java
public interface Command {
// 执行方法
void execute();
}
```
具体命令类:
```java
public class PlayMusicCommand implements Command {
// 接收者
private Nexus6P mNexus6P;
public PlayMusicCommand(Nexus6P nexus6P) {
mNexus6P = nexus6P;
}
// 执行方法
@Override
public void execute() {
mNexus6P.playMusic();
}
}
public class PlayVideoCommand implements Command {
// 接收者
private Nexus6P mNexus6P;
public PlayVideoCommand(Nexus6P nexus6P) {
mNexus6P = nexus6P;
}
// 执行方法
@Override
public void execute() {
mNexus6P.playVideo();
}
}
public class PlayGamesCommand implements Command {
// 接收者
private Nexus6P mNexus6P;
public PlayGamesCommand(Nexus6P nexus6P) {
mNexus6P = nexus6P;
}
// 执行方法
@Override
public void execute() {
mNexus6P.playGames();
}
}
```
请求者:
```java
public class PhoneScreen {
// 具体命令对象
private Command mPlayMusicCommand;
private Command mPlayVideoCommand;
private Command mPlayGamesCommand;
public void setPlayMusicCommand(Command playMusicCommand) {
mPlayMusicCommand = playMusicCommand;
}
public void setPlayVideoCommand(Command playVideoCommand) {
mPlayVideoCommand = playVideoCommand;
}
public void setPlayGamesCommand(Command playGamesCommand) {
mPlayGamesCommand = playGamesCommand;
}
// 行动方法
public void playMusic() {
mPlayMusicCommand.execute();
}
// 行动方法
public void playVideo() {
mPlayVideoCommand.execute();
}
// 行动方法
public void playGames() {
mPlayGamesCommand.execute();
}
}
```
客户端:
```java
public class CustomerMark {
public static void main(String[] args) {
// 创建接收者
Nexus6P nexus6P = new Nexus6P();
// 创建具体命令对象
Command playMusicCommand = new PlayMusicCommand(nexus6P);
Command playVideoCommand = new PlayVideoCommand(nexus6P);
Command playGamesCommand = new PlayGamesCommand(nexus6P);
// 创建请求者
PhoneScreen phoneScreen = new PhoneScreen();
phoneScreen.setPlayMusicCommand(playMusicCommand);
phoneScreen.setPlayVideoCommand(playVideoCommand);
phoneScreen.setPlayGamesCommand(playGamesCommand);
// 测试
phoneScreen.playMusic();
phoneScreen.playVideo();
phoneScreen.playGames();
}
}
```
## 2.4 总结
* 命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
* 优点在于降低系统的耦合度,增加新的命令很方便,而且可以比较容易地设计一个命令队列和宏命令,并方便地实现对请求的撤销和恢复
* 缺点在于可能会导致某些系统有过多的具体命令类
* 命令模式适用情况:
* 需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互;
* 需要在不同的时间指定请求、将请求排队和执行请求;
* 需要支持命令的撤销操作和恢复操作,需要将一组操作组合在一起,即支持宏命令。
# 3 策略模式
## 3.1 模式定义
策略模式是对算法的包装,针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。客户端类可自由选择使用哪一种策略(算法)。
## 3.2 模式结构
* 抽象策略角色(Strategy)
给出所有具体策略类所需要实现的接口
* 具体策略角色(ConcreteStrategy)
包装了相关的算法或行为
* 环境角色(Context)
环境类在解决某个问题时可以采用多种策略,在环境类中维护一个对抽象策略类的引用实例
## 3.3 代码示例
抽象策略:
```java
public interface ConsumerStrategy {
double calculatePrice(double originalPrice);
}
```
具体策略类:
```java
public class GoldMedalConsumerStrategy implements ConsumerStrategy {
@Override
public double calculatePrice(double originalPrice) {
return originalPrice * 0.8;
}
}
public class SilverMedalConsumerStrategy implements ConsumerStrategy {
@Override
public double calculatePrice(double originalPrice) {
return originalPrice * 0.9;
}
}
public class BronzeMedalConsumerStartegy implements ConsumerStrategy {
@Override
public double calculatePrice(double originalPrice) {
return originalPrice * 0.95;
}
}
```
环境类:
```java
public class Price {
private ConsumerStrategy mStrategy;
public Price(ConsumerStrategy strategy) {
mStrategy = strategy;
}
public double calculate(double phonePrice) {
return mStrategy.calculatePrice(phonePrice);
}
}
```
客户端类:
```java
public class JingDongstore {
public static void main(String[] args) {
ConsumerStrategy silverMedalConsumerStrategy = new SilverMedalConsumerStrategy();
Price price = new Price(silverMedalConsumerStrategy);
double dealPrice = price.calculate(5000);
System.out.print("手机成交价格为" + dealPrice);
}
}
```
## 3.4 总结
* 策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。
* 对于一系列具体的策略算法,大家的地位是完全一样的,正因为这个平等性,才能实现算法之间可以相互替换。所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的。
* 策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同的策略实现中切换,但是同时只能使用一个。
* 在策略模式中,应当由客户端自己决定在什么情况下使用什么具体策略角色。
* 策略模式适用情况包括:
* 在一个系统里面有许多类,它们之间的区别仅在于它们的行为,使用策略模式可以动态地让一个对象在许多行为中选择一种行为;
* 一个系统需要动态地在几种算法中选择一种;
* 避免使用难以维护的多重条件选择语句;
* 希望在具体策略类中封装算法和与相关的数据结构。
# 4 状态模式
又称状态对象模式
## 4.1 模式定义
状态模式允许一个对象在其内部状态改变的时候改变其行为,这个对象看上去就像改变了它的类一样
状态模式把所研究的对象(环境角色)的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。
## 4.2 模式结构
* 环境角色
定义客户端感兴趣的接口,并持有一个具体状态类的实例
* 抽象状态类
定义一个接口,用以封装环境(Context)对象的一个特定的状态所对应的行为
* 具体状态类
每一个具体状态类都实现了环境(Context)的一个状态所对应的行为
## 4.3 代码示例
抽象状态类:
```java
public interface State {
// 特定状态所对应的行为
void handle(String sampleParameter);
}
```
具体状态类:
```java
public class ConcreteStateA implements State {
private static final String TAG = "ConcreteStateA";
@Override
public void handle(String sampleParameter) {
Log.e(TAG, "handle: " + sampleParameter);
}
}
public class ConcreteStateB implements State {
private static final String TAG = "ConcreteStateB";
@Override
public void handle(String sampleParameter) {
Log.e(TAG, "handle: " + sampleParameter);
}
}
```
环境角色类:
```java
public class Context {
// 持有一个具体状态类的实例
private State mState;
public void setState(State state) {
mState = state;
}
// 定义客户端感兴趣的接口
public void request(String sampleParameter) {
mState.handle(sampleParameter);
}
}
```
客户端:
```java
public class Client {
public static void main(String[] args) {
Context context = new Context();
State state = new ConcreteStateA();
context.setState(state);
context.request("test");
}
}
```
## 4.4 总结
* 在具体的状态处理类中经常需要获取环境 (Context) 自身的数据,甚至在必要的时候会回调环境 (Context) 的方法,因此,通常将环境 (Context) 自身当作一个参数传递给具体的状态处理类。
* 客户端一般只和环境 (Context) 交互。客户端可以用状态对象来配置一个环境(Context),一旦配置完毕,就不再需要和状态对象打交道了。
* 策略模式与状态模式的区别
* 可以通过环境类状态的个数来决定是使用策略模式还是状态模式。
* 策略模式的环境类自己选择一个具体策略类,具体策略类无须关心环境类;而状态模式的环境类由于外在因素需要放进一个具体状态中,以便通过其方法实现状态的切换,因此环境类和状态类之间存在一种双向的关联关系。
* 使用策略模式时,客户端需要知道所选的具体策略是哪一个,而使用状态模式时,客户端无须关心具体状态,环境类的状态会根据用户的操作自动转换。
* 如果系统中某个类的对象存在多种状态,不同状态下行为有差异,而且这些状态之间可以发生转换时使用状态模式;如果系统中某个类的某一行为存在多种实现方式,而且这些实现方式可以互换时使用策略模式。
- 导读
- Java知识
- Java基本程序设计结构
- 【基础知识】Java基础
- 【源码分析】Okio
- 【源码分析】深入理解i++和++i
- 【专题分析】JVM与GC
- 【面试清单】Java基本程序设计结构
- 对象与类
- 【基础知识】对象与类
- 【专题分析】Java类加载过程
- 【面试清单】对象与类
- 泛型
- 【基础知识】泛型
- 【面试清单】泛型
- 集合
- 【基础知识】集合
- 【源码分析】SparseArray
- 【面试清单】集合
- 多线程
- 【基础知识】多线程
- 【源码分析】ThreadPoolExecutor源码分析
- 【专题分析】volatile关键字
- 【面试清单】多线程
- Java新特性
- 【专题分析】Lambda表达式
- 【专题分析】注解
- 【面试清单】Java新特性
- Effective Java笔记
- Android知识
- Activity
- 【基础知识】Activity
- 【专题分析】运行时权限
- 【专题分析】使用Intent打开三方应用
- 【源码分析】Activity的工作过程
- 【面试清单】Activity
- 架构组件
- 【专题分析】MVC、MVP与MVVM
- 【专题分析】数据绑定
- 【面试清单】架构组件
- 界面
- 【专题分析】自定义View
- 【专题分析】ImageView的ScaleType属性
- 【专题分析】ConstraintLayout 使用
- 【专题分析】搞懂点九图
- 【专题分析】Adapter
- 【源码分析】LayoutInflater
- 【源码分析】ViewStub
- 【源码分析】View三大流程
- 【源码分析】触摸事件分发机制
- 【源码分析】按键事件分发机制
- 【源码分析】Android窗口机制
- 【面试清单】界面
- 动画和过渡
- 【基础知识】动画和过渡
- 【面试清单】动画和过渡
- 图片和图形
- 【专题分析】图片加载
- 【面试清单】图片和图形
- 后台任务
- 应用数据和文件
- 基于网络的内容
- 多线程与多进程
- 【基础知识】多线程与多进程
- 【源码分析】Handler
- 【源码分析】AsyncTask
- 【专题分析】Service
- 【源码分析】Parcelable
- 【专题分析】Binder
- 【源码分析】Messenger
- 【面试清单】多线程与多进程
- 应用优化
- 【专题分析】布局优化
- 【专题分析】绘制优化
- 【专题分析】内存优化
- 【专题分析】启动优化
- 【专题分析】电池优化
- 【专题分析】包大小优化
- 【面试清单】应用优化
- Android新特性
- 【专题分析】状态栏、ActionBar和导航栏
- 【专题分析】应用图标、通知栏适配
- 【专题分析】Android新版本重要变更
- 【专题分析】唯一标识符的最佳做法
- 开源库源码分析
- 【源码分析】BaseRecyclerViewAdapterHelper
- 【源码分析】ButterKnife
- 【源码分析】Dagger2
- 【源码分析】EventBus3(一)
- 【源码分析】EventBus3(二)
- 【源码分析】Glide
- 【源码分析】OkHttp
- 【源码分析】Retrofit
- 其他知识
- Flutter
- 原生开发与跨平台开发
- 整体归纳
- 状态及状态管理
- 零碎知识点
- 添加Flutter到现有应用
- Git知识
- Git命令
- .gitignore文件
- 设计模式
- 创建型模式
- 结构型模式
- 行为型模式
- RxJava
- 基础
- Linux知识
- 环境变量
- Linux命令
- ADB命令
- 算法
- 常见数据结构及实现
- 数组
- 排序算法
- 链表
- 二叉树
- 栈和队列
- 算法时间复杂度
- 常见算法思想
- 其他技术
- 正则表达式
- 编码格式
- HTTP与HTTPS
- 【面试清单】其他知识
- 开发归纳
- Android零碎问题
- 其他零碎问题
- 开发思路