行为型模式
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
一、模板方法模式(Template)
1.概述
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
定义:
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
2.结构
模板方法(Template Method)模式包含以下主要角色:
-
抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
-
模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
-
基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:
-
抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。
-
具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
-
钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。
-
-
-
具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。
3.案例实现
【例】计算某个方法的执行时间
计算执行时间的开始时间与结束时间是固定的一个在方法的前面一个在方法的后面,但是方法是可变的,可以把方法抽象出去。类图如下:
代码实现:
- 使用前:
package com.javaxl.design.template.before; public class CodeTotalTime { public static void template(){ long start = System.currentTimeMillis(); //检测Operation_1方法运行的时长======33 Operation_1(); //检测Operation_2方法运行的时长======616 //Operation_2(); long end = System.currentTimeMillis(); System.out.println(end-start); } public static void Operation_1(){ for (int i = 0; i<1000 ;i++){ System.out.println("模拟耗时操作..."); } System.out.print("检测Operation_1方法运行的时长======"); } public static void Operation_2(){ for (int i = 0; i<20000 ;i++){ System.out.println("模拟耗时操作..."); } System.out.print("检测Operation_2方法运行的时长======"); } } public class Client { public static void main(String[] args) { CodeTotalTime.template(); } }
- 使用后:
abstract class CodeAbstractClass { public void template() { long start = System.currentTimeMillis(); method(); long end = System.currentTimeMillis(); System.out.println("当前方法执行时长:" + (end - start)); } public abstract void method(); } class ConcreteClassA extends CodeAbstractClass { @Override public void method() { for (int i = 0; i < 1000; i++) { System.out.println("模拟耗时操作..."); } System.out.print("检测ConcreteClassA.method方法运行的时长======"); } } class ConcreteClassB extends CodeAbstractClass { @Override public void method() { for (int i = 0; i < 20000; i++) { System.out.println("模拟耗时操作..."); } System.out.print("ConcreteClassB.method方法运行的时长======"); } } public class Client { public static void main(String[] args) { //检测ConcreteClassA.method方法运行的时长======当前方法执行时长: new ConcreteClassA().template(); //ConcreteClassB.method方法运行的时长======当前方法执行时长: new ConcreteClassB().template(); } }
钩子函数应用场景:
public abstract class CodeAbstractClass {
public void template() {
long start = System.currentTimeMillis();
if (callback()) method();
long end = System.currentTimeMillis();
System.out.println("当前方法执行时长:" + (end - start));
}
public abstract void method();
public boolean callback() {
return true;
}
}
4.优缺点
优点:
-
提高代码复用性
将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。
-
实现了反向控制
通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 ,并符合“开闭原则”。
缺点:
-
对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
-
父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
5.适用场景
-
算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
-
需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
6.JDK源码解析
InputStream类就使用了模板方法模式。在InputStream类中定义了多个 read()
方法,如下:
public abstract class InputStream implements Closeable {
//抽象方法,要求子类必须重写
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read(); //调用了无参的read方法,该方法是每次读取一个字节数据
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
}
从上面代码可以看到,无参的 read()
方法是抽象方法,要求子类必须实现。而 read(byte b[])
方法调用了 read(byte b[], int off, int len)
方法,所以在此处重点看的方法是带三个参数的方法。
在该方法中第18行、27行,可以看到调用了无参的抽象的 read()
方法。
总结:
在InputStream父类中已经定义好了读取一个字节数组数据的方法是每次读取一个字节,并将其存储到数组的第一个索引位置,读取len个字节数据。具体如何读取一个字节数据呢?由子类实现。
二、备忘录模式(Memento)
1.概述
备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件都提供了撤销(Undo)操作,如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 浏览器 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
定义:
又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。
2.结构
备忘录模式的主要角色如下:
-
发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
-
备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
-
管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
3.案例实现
【例】游戏挑战BOSS
游戏中的某个场景,一游戏角色有生命力、攻击力、防御力等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。
-
情况1:为一个对象保留一个状态
英雄类:
public class Hero {
//需要存档的属性:这里用一个state属性来表示,实际需要存档的属性可能会有很多
private String state;
public Hero(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
//将当前Hero对象实例进行备份
public HeroMemento saveHero() {
return new HeroMemento(this.state);
}
//恢复上一个英雄状态
public void getMemento(HeroMemento heroMemento) {
this.state = heroMemento.getState();
}
}
守护者类:
public class Caretaker {
private HeroMemento heroMemento;
public HeroMemento getHeroMemento() {
return heroMemento;
}
public void setHeroMemento(HeroMemento heroMemento) {
this.heroMemento = heroMemento;
}
}
英雄备忘录类:
public class HeroMemento {
private String state;
public HeroMemento(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
实现:
public class Client {
public static void main(String[] args) {
//new一个英雄状态为满血
Hero hero = new Hero("状态1,满血状态");
//守护者
Caretaker caretaker = new Caretaker();
//将上面满血状态存档
caretaker.setHeroMemento(hero.saveHero());
//覆盖上面的状态为状态下滑
hero.setState("状态2:状态下滑");
System.out.println("当前的状态===============" + hero.getState());//状态下滑
//读取上次的满血存档
hero.getMemento(caretaker.getHeroMemento());
System.out.println("当前的状态===============" + hero.getState());//满血状态
//再次进行存档
caretaker.setHeroMemento(hero.saveHero());
//状态改变
hero.setState("状态3:残血状态");
//读档
hero.getMemento(caretaker.getHeroMemento());
System.out.println("当前的状态===============" + hero.getState());//满血状态
//存档
caretaker.setHeroMemento(hero.saveHero());
//改变状态
hero.setState("状态4:临死状态");
//存档
caretaker.setHeroMemento(hero.saveHero());
}
}
-
情况2:为一个对象保留多个状态
public class Hero {
// 需要存档的属性:这里用一个state属性来表示,实际需要存档的属性可能会有很多
private String state;
public Hero(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
} // 将当前Hero对象实例进行备份
public HeroMemento saveHero() {
return new HeroMemento(this.state);
} // 恢复某一个英雄状态
public void getMemento(Caretaker caretaker, int no) {
this.state = caretaker.getMemento(no).getState();
}
}
public class HeroMemento {
private String state;
public HeroMemento(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
public class Caretaker {
private List<HeroMemento> heroMementos = new ArrayList<>();
public void addMemento(HeroMemento memento) {
heroMementos.add(memento);
}
public HeroMemento getMemento(int no) {
return heroMementos.get(no);
}
}
public class Client {
public static void main(String[] args) {
Hero hero = new Hero("状态1,满血状态");
Caretaker caretaker = new Caretaker();
//存档 满血状态
caretaker.addMemento(hero.saveHero());
hero.setState("状态2:状态下滑");
hero.setState("状态3:残血状态");
//存档 残血状态
caretaker.addMemento(hero.saveHero());
hero.setState("状态4:临死状态");
//存档 临死状态
caretaker.addMemento(hero.saveHero());
hero.setState("状态5:死亡状态");
//上面备份了3个状态,我来恢复看看
System.out.println("当前的状态===============" + hero.getState());//死亡
//读第1个存档
hero.getMemento(caretaker, 0);
System.out.println("读取存档1===============" + hero.getState());//满血
//读第2个存档
hero.getMemento(caretaker, 1);
System.out.println("读取存档2===============" + hero.getState());//残血
//读第3个存档
hero.getMemento(caretaker, 2);
System.out.println("读取存档3===============" + hero.getState());//临死
}
}
- 情况3:为多个对象保留一个状态
public class Caretaker {
private HashMap<Caretaker ,HeroMemento> mementos = new HashMap();
public void addMemento(Caretaker caretaker,HeroMemento memento) {
mementos.put(caretaker,memento);
}
public HashMap<Caretaker ,HeroMemento> getMemento() {
return this.mementos;
}
}
public class Client {
public static void main(String[] args) {
Hero hero = new Hero("状态1,满血状态");
Caretaker caretaker = new Caretaker();
Caretaker caretaker2 = new Caretaker();
HeroMemento heroMemento = new HeroMemento(hero.getState());
caretaker.addMemento(caretaker,heroMemento);
caretaker.addMemento(caretaker2,heroMemento);
HashMap<Caretaker, HeroMemento> memento = caretaker.getMemento();
Set<Map.Entry<Caretaker, HeroMemento>> entrySet = memento.entrySet();
for (Map.Entry<Caretaker, HeroMemento> entry : entrySet) {
String key = entry.getKey().toString();
String value = entry.getValue().toString();
System.out.println("key=" + key + " value=" + value);
}
}
- 情况4:为多个对象保留多个对象
public class Caretaker {
private HashMap<Caretaker , List<HeroMemento>> mementos = new HashMap();
public void addMemento(Caretaker caretaker,List<HeroMemento> memento) {
mementos.put(caretaker,memento);
}
public HashMap<Caretaker , List<HeroMemento>> getMemento() {
return this.mementos;
}
}
public class Client {
public static void main(String[] args) {
Hero hero1 = new Hero("状态1,满血状态");
Hero hero2 = new Hero("状态2,满血状态");
Hero hero3 = new Hero("状态3,满血状态");
Hero hero4 = new Hero("状态4,满血状态");
Caretaker caretaker = new Caretaker();
Caretaker caretaker2 = new Caretaker();
HeroMemento heroMemento1 = new HeroMemento(hero1.getState());
HeroMemento heroMemento2 = new HeroMemento(hero2.getState());
HeroMemento heroMemento3 = new HeroMemento(hero3.getState());
HeroMemento heroMemento4 = new HeroMemento(hero4.getState());
List<HeroMemento> lsit1=new ArrayList<>();
lsit1.add(heroMemento1);
lsit1.add(heroMemento2);
List<HeroMemento> lsit2=new ArrayList<>();
lsit2.add(heroMemento3);
lsit2.add(heroMemento4);
caretaker.addMemento(caretaker,lsit1);
caretaker.addMemento(caretaker2,lsit2);
HashMap<Caretaker, List<HeroMemento>> memento = caretaker.getMemento();
Set<Map.Entry<Caretaker, List<HeroMemento>>> entries = memento.entrySet();
for (Map.Entry<Caretaker, List<HeroMemento>> entry : entries) {
String key = entry.getKey().toString();
String value = entry.getValue().toString();
System.out.println("key=" + key + " value=" + value);
}
}
备忘录模式也是一种比较常用的模式用来保存对象的部分用于恢复的信息,和原型模式有着本质的区别,广泛运用在快照功能之中,另外我们知道了宽接口和窄接口,这里的接口就是指的方法,没有其他意思,以及类的可见性。同样的使用备忘录模式可以使得程序可以组件化,比如打算多次撤销当前的状态,以及不仅可以撤销而且可以将当前的状态保存到文件之中的时候
4.优缺点
1,优点:
-
提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
-
实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
-
简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
2,缺点:
-
资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
5.使用场景
-
需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
-
需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,idea等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。
三、命令模式(Command)
1.概述
在软件开发系统中,常常出现“方法的请求者”与“方法的实现者”之间存在紧密的耦合关系。这不利于软件功能的扩展与维护。例如,想对行为进行“撤销、重做、记录”等处理都很不方便,因此“如何将方法的请求者与方法的实现者解耦?”变得很重要,命令模式能很好地解决这个问题。
在现实生活中,这样的例子也很多,例如,电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者),还有计算机键盘上的“功能键”等。
定义:
将一封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。个请求
2.结构
命令模式包含以下主要角色:
-
抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
-
具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
-
实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
-
调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
3.案例实现
【例】万能遥控器的制作
将上面的案例用代码实现,那我们就需要分析命令模式的角色在该案例中由谁来充当。
-
Command抽象命令 执行命令 撤销命令
-
ConcreteCommandLightOnCommand 开灯
-
LightOffCommand 关灯
-
NonCommand空命令
-
Invoker调用者 遥控器聚合所有命令 Command[] ons Command[] offs Command undo
-
Receiver接受者 电灯、空调、电视
类图如下:
代码如下:
- 出现前:
class AirConditioner {
public void on() {
System.out.println("空调打开...");
}
public void off() {
System.out.println("空调关闭...");
}
}
class Television {
public void on() {
System.out.println("电视打开...");
}
public void off() {
System.out.println("电视关闭...");
}
}
public class Invoker {
private Light light = new Light();
private AirConditioner airConditioner = new AirConditioner();
private Television television = new Television();
public void lightOn() {
light.on();
}
public void lightOff() {
light.off();
}
public void airOn() {
airConditioner.on();
}
public void airOff() {
airConditioner.off();
}
public void tvOn() {
television.on();
}
public void tvOff() {
television.off();
}
}
package com.zhq.command.demo1;
public class Client {
public static void main(String[] args) {
Invoker invoker = new Invoker();
invoker.lightOn();
invoker.lightOff();
System.out.println("=============");
invoker.airOn();
invoker.airOff();
System.out.println("=============");
invoker.tvOn();
invoker.tvOff();
}
}
- 出现后:
/**
* 被操作的对象
*/
public class Light {
public void on() {
System.out.println("电灯打开...");
}
public void off() {
System.out.println("电灯关闭...");
}
}
class AirConditioner {
public void on() {
System.out.println("空调打开...");
}
public void off() {
System.out.println("空调关闭...");
}
}
class Television {
public void on() {
System.out.println("电视打开...");
}
public void off() {
System.out.println("电视关闭...");
}
}
interface Command {
void execute();
void undo();
}// 空命令
class NonCommand implements Command {
@Override
public void execute() {
}
@Override
public void undo() {
}
}
class LightOnCommand implements Command {
private Light light = new Light();
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
class LightOffCommand implements Command {
private Light light = new Light();
@Override
public void execute() {
light.off();
}
@Override
public void undo() {
light.on();
}
}
class TvOnCommand implements Command {
private Television tv = new Television();
@Override
public void execute() {
tv.on();
}
@Override
public void undo() {
tv.off();
}
}
class TvOffCommand implements Command {
private Television tv = new Television();
@Override
public void execute() {
tv.off();
}
@Override
public void undo() {
tv.on();
}
}
public class Invoker {
Command[] ons;
Command[] offs;// 记录上一个命令
Command command;
public Invoker(int n) {
ons = new Command[n];
offs = new Command[n];
command = new NonCommand();
for (int i = 0; i < n; i++) {
setCommand(i, new NonCommand(), new NonCommand());
}
}
public void setCommand(int no, Command on, Command off) {
ons[no] = on;
offs[no] = off;
}
public Command getOnCommand(int no) {
return ons[no];
}
public Command getOffCommand(int no) {
return offs[no];
}
// 执行命令
public void invoke(Command command) {
// 执行当前命令
command.execute();// 保存当前执行命令
this.command = command;
}// 撤销命令(上个操作的反操作)
public void undo() {// 这里就能体现定义一个空命令的好处了,如果第一次按撤销命令,那么应该什么都不做;// 如果没有定义空命令的话,此时就需要判断空处理了
command.undo();
}
}
public class Client {
public static void main(String[] args) {
Invoker invoker = new Invoker(2);
invoker.setCommand(0, new LightOnCommand(), new LightOffCommand());
invoker.setCommand(1, new TvOnCommand(), new TvOffCommand());
System.out.println("电灯打开关闭操作===========");
invoker.invoke(invoker.getOnCommand(0));
invoker.invoke(invoker.getOffCommand(0));
// invoker.undo();
System.out.println("电视打开关闭操作===========");
invoker.invoke(invoker.getOnCommand(1));
invoker.undo();
}
}
4.优缺点
1,优点:
-
降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
-
增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
-
可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
-
方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
2,缺点:
-
使用命令模式可能会导致某些系统有过多的具体命令类。
-
系统结构更加复杂。
5.使用场景
-
系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
-
系统需要在不同的时间指定请求、将请求排队和执行请求。
-
系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
6.JDK源码解析
Runable是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法
//命令接口(抽象命令角色)
public interface Runnable {
public abstract void run();
}
//调用者
public class Thread implements Runnable {
private Runnable target;
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
}
会调用一个native方法start0(),调用系统方法,开启一个线程。而接收者是对程序员开放的,可以自己定义接收者。
/**
* jdk Runnable 命令模式
* TurnOffThread : 属于具体
*/
public class TurnOffThread implements Runnable{
private Receiver receiver;
public TurnOffThread(Receiver receiver) {
this.receiver = receiver;
}
public void run() {
receiver.turnOFF();
}
}
/**
* 测试类
*/
public class Demo {
public static void main(String[] args) {
Receiver receiver = new Receiver();
TurnOffThread turnOffThread = new TurnOffThread(receiver);
Thread thread = new Thread(turnOffThread);
thread.start();
}
}
四、状态模式
1.概述
【例】如果要实现商店抽奖,它有很多的状态,像‘扣除50积分’,‘抽奖成功’,‘抽奖失败’,‘活动结束’等。每一种状态改变,都有可能要根据其他状态来更新处理。如图:
使用前代码如下:
import java.util.Random;
public class State {
// 当前的状态
private int state;
// 供抽奖的积分
private int score;
// 奖品的数量
private int count;
public State(int score, int count) {
this.score = score;
this.count = count;
}
public int getCount() {
return count;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
// 扣除积分
public void minus() {
// 只有一阶段可以扣积分
this.state = 1;
if (this.state == 1) {
if (this.score >= 50) {
if (this.count == 0) {
System.out.println("奖品领完....");
return;
}
this.score = this.score - 50;
System.out.println("========扣除50积分,当前积分还剩" + this.score + "========");
this.state = 2;
if (luckHit()) {
this.state = 3;
getPrize();
}
} else {
System.out.println("========积分不够,当前积分为" + this.score + "========");
}
}
}
// 十分之一抽中奖品的概率
public boolean luckHit() {
// 只有二阶段可以抽奖
return this.state == 2 ? (new Random().nextInt(10) == 6) : false;
}
public void getPrize() {
if (this.state == 3) {
if (this.count > 0) {
System.out.println("领取奖品....");
this.count = this.count - 1;
} else {
System.out.println("奖品领完....");
}
}
}
}
public class Client {
public static void main(String[] args) {
State state = new State(500,1);
// state.minus();
for (int i = 0; i < 300; i++) {
state.minus();
}
}
}
问题分析:
-
使用了大量的switch…case这样的判断(if…else也是一样),使程序的可阅读性变差。
-
上面状态只有4个,代码已经比较复杂了;状态越多,代码嵌套就越复杂,维护成本就越高;
定义:
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
2.结构
状态模式包含以下主要角色。
-
环境(Context)角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
-
抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
-
具体状态(Concrete State)角色:实现抽象状态所对应的行为。
3.案例实现
对上述抽奖的案例使用状态模式进行改进。类图如下:
使用后代码如下:
import java.util.Random;
public abstract class State {
// 扣积分
abstract void minus();
// 抽奖
abstract boolean luckHit();
// 获取奖品
abstract void getPrize();
}
class ConcreteStateA extends State{
Context context;
public ConcreteStateA(Context context) {
this.context = context;
}
@Override
void minus() {
if(context.getScore()>=50){
context.setScore(context.getScore()-50);
System.out.println("========扣除50积分,当前积分还剩"+context.getScore()+"========");
context.setState(context.getStateB());
}else{
System.out.println("========积分不够,当前积分为"+context.getScore()+"========");
}
}
@Override
boolean luckHit() {
System.out.println("还在扣费环节,不能抽奖...");
return false;
}
@Override
void getPrize() {
System.out.println("还在扣费环节,不能领取奖品...");
}
}
class ConcreteStateB extends State{
Context context;
public ConcreteStateB(Context context) {
this.context = context;
}
@Override
void minus() {
System.out.println("已经在抽奖环节...");
}
@Override
boolean luckHit() {
boolean flag = new Random().nextInt(10) == 6;
if(flag){
context.setState(context.getStateC());
}else{
context.setState(context.getStateA());
}
return flag;
}
@Override
void getPrize() {
System.out.println("还在抽奖环节,不能领取奖品...");
}
}
class ConcreteStateC extends State{
Context context;
public ConcreteStateC(Context context) {
this.context = context;
}
@Override
void minus() {
System.out.println("已经在领取奖品环节...");
}
@Override
boolean luckHit() {
System.out.println("已经在领取奖品环节...");
return false;
}
@Override
void getPrize() {
if(context.getCount()>0){
System.out.println("领取奖品成功...");
context.setState(context.getStateA());
}else {
System.out.println("活动结束,领取奖品失败...");
context.setState(context.getStateD());
// 不继续抽奖
// System.exit(0);
}
}
}
class ConcreteStateD extends State{
Context context;
public ConcreteStateD(Context context) {
this.context = context;
}
@Override
void minus() {
System.out.println("已经在活动结束,奖品送完环节...");
}
@Override
boolean luckHit() {
System.out.println("已经在活动结束,奖品送完环节...");
return false;
}
@Override
void getPrize() {
System.out.println("已经在活动结束,奖品送完环节...");
}
}
public class Context {
// 当前的状态
private State state;
// 奖品数量
public int count;
// 用户积分
private int score;
// 表示同一个对象的四种状态
private ConcreteStateA stateA = new ConcreteStateA(this);
private ConcreteStateB stateB = new ConcreteStateB(this);
private ConcreteStateC stateC = new ConcreteStateC(this);
private ConcreteStateD stateD = new ConcreteStateD(this);
public Context(int score, int count) {
this.score = score;
this.count = count;
this.state = stateA;
}
// 扣积分
public void minus() {
state.minus();
}
// 抽奖
public void luckHit() {
if (state.luckHit()) {
state.getPrize();
}
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public int getCount() {
return count--;
}
public void setCount(int count) {
this.count = count;
}
public ConcreteStateA getStateA() {
return stateA;
}
public void setStateA(ConcreteStateA stateA) {
this.stateA = stateA;
}
public ConcreteStateB getStateB() {
return stateB;
}
public void setStateB(ConcreteStateB stateB) {
this.stateB = stateB;
}
public ConcreteStateC getStateC() {
return stateC;
}
public void setStateC(ConcreteStateC stateC) {
this.stateC = stateC;
}
public ConcreteStateD getStateD() {
return stateD;
}
public void setStateD(ConcreteStateD stateD) {
this.stateD = stateD;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}
public class Client {
public static void main(String[] args) {
// 这次游戏积分500个,用完为止,总奖品数2
Context context = new Context(500,1);
// context.lunkHit();//还在扣费环节,不能抽奖...
for (int i = 0; i < 300; i++) {
context.minus();
context.luckHit();
}
System.out.println("------------------");
}
}
4.优缺点
1,优点:
-
将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
-
允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
2,缺点:
-
状态模式的使用必然会增加系统类和对象的个数。
-
状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
-
状态模式对"开闭原则"的支持并不太好。
5.使用场景
-
当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
-
一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
五、责任链模式
1.概述
在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这样的例子还有很多,如找领导出差报销、生活中的“击鼓传花”游戏等。
定义:
又名职责链模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
2.结构
职责链模式主要包含以下角色:
-
抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
-
具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
-
客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
3.案例实现
现需要开发一个请假流程控制系统。
- 学生请假1天:教员审批
- 学生请假2天:教学主管审批
- 学生请假3天:教学经理审批
- 学生请假5天:副校长审批
- 学生请假超过5天:校长审批
使用前的代码如下:
public class Request {
private String content;
private int day;
public Request(String content, int day) {
this.content = content;
this.day = day;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
}
public class Handler {
public void handle(Request request){
int day = request.getDay();
if(day <= 1){
System.out.println("教员处理了:因 " + request.getContent() + " 请假" + day + "天的请求");
}else if(day <= 2){
System.out.println("教学主管处理了:因 " + request.getContent() + " 请假" + day + "天的请求");
}else if(day <= 3){
System.out.println("教学经理处理了:因 " + request.getContent() + " 请假" + day + "天的请求");
}else if(day <= 5){
System.out.println("副校长处理了:因 " + request.getContent() + " 请假" + day + "天的请求");
}else {
System.out.println("校长处理了:因 " + request.getContent() + " 请假" + day + "天的请求");
}
}
}
public class Client {
public static void main(String[] args) {
Handler handler = new Handler();
Request request1 = new Request("小感冒",1);
handler.handle(request1);
Request request2 = new Request("做检查",2);
handler.handle(request2);
Request request3 = new Request("打点滴",3);
handler.handle(request3);
Request request4 = new Request("住院",4);
handler.handle(request4);
Request request5 = new Request("在家调养",30);
handler.handle(request5);
}
}
违背了迪米特法则,调用方清楚的知道整个处理链的存在;
使用后的代码如下:
public class Request {
private String content;
private int day;
public Request(String content, int day) {
this.content = content;
this.day = day;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
}
public abstract class Handler {
Handler next;
String name;
public Handler(String name) {
this.name = name;
}
public Handler getNext() {
return next;
}
public void setNext(Handler next) {
this.next = next;
}
public abstract void handle(Request request);
}
class HandlerA extends Handler {
public HandlerA(String name) {
super(name);
}
public void handle(Request request) {
int day = request.getDay();
if (day <= 1) {
System.out.println(this.name + "处理了:因 " + request.getContent() + " 请假" + day + "天的请求");
} else {
next.handle(request);
}
}
}
class HandlerB extends Handler {
public HandlerB(String name) {
super(name);
}
public void handle(Request request) {
int day = request.getDay();
if (day <= 2) {
System.out.println(this.name + "处理了:因 " + request.getContent() + " 请假" + day + "天的请求");
} else {
next.handle(request);
}
}
}
class HandlerC extends Handler {
public HandlerC(String name) {
super(name);
}
public void handle(Request request) {
int day = request.getDay();
if (day <= 3) {
System.out.println(this.name + "处理了:因 " + request.getContent() + " 请假" + day + "天的请求");
} else {
next.handle(request);
}
}
}
class HandlerD extends Handler {
public HandlerD(String name) {
super(name);
}
public void handle(Request request) {
int day = request.getDay();
if (day <= 5) {
System.out.println(this.name + "处理了:因 " + request.getContent() + " 请假" + day + "天的请求");
} else {
next.handle(request);
}
}
}
class HandlerE extends Handler {
public HandlerE(String name) {
super(name);
}
public void handle(Request request) {
int day = request.getDay();
System.out.println(this.name + "处理了:因 " + request.getContent() + " 请假" + day + "天的请求");
}
}
public class Client {
public static void main(String[] args) {
HandlerA handlerA = new HandlerA("教员");
HandlerB handlerB = new HandlerB("教学主管");
HandlerC handlerC = new HandlerC("教学经理");
HandlerD handlerD = new HandlerD("副校长");
HandlerE handlerE = new HandlerE("校长");
handlerA.setNext(handlerB);
handlerB.setNext(handlerC);
handlerC.setNext(handlerD);
handlerD.setNext(handlerE);
Request request1 = new Request("小感冒",1);
handlerA.handle(request1);
Request request2 = new Request("做检查",2);
handlerA.handle(request2);
Request request3 = new Request("打点滴",3);
handlerA.handle(request3);
Request request4 = new Request("住院",4);
handlerA.handle(request4);
Request request5 = new Request("在家调养",30);
handlerA.handle(request5);
}
}
4.优缺点
1,优点:
-
降低了对象之间的耦合度
该模式降低了请求发送者和接收者的耦合度。
-
增强了系统的可扩展性
可以根据需要增加新的请求处理类,满足开闭原则。
-
增强了给对象指派职责的灵活性
当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。
-
责任链简化了对象之间的连接
一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
-
责任分担
每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
2,缺点:
-
不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
-
对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
-
职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
5.源码解析
在javaWeb应用开发中,FilterChain是职责链(过滤器)模式的典型应用,以下是Filter的模拟实现分析:
-
模拟web请求Request以及web响应Response
public interface Request{ } public interface Response{ }
-
模拟web过滤器Filter
public interface Filter { public void doFilter(Request req,Response res,FilterChain c); }
-
模拟实现具体过滤器
public class FirstFilter implements Filter { @Override public void doFilter(Request request, Response response, FilterChain chain) { System.out.println("过滤器1 前置处理"); // 先执行所有request再倒序执行所有response chain.doFilter(request, response); System.out.println("过滤器1 后置处理"); } } public class SecondFilter implements Filter { @Override public void doFilter(Request request, Response response, FilterChain chain) { System.out.println("过滤器2 前置处理"); // 先执行所有request再倒序执行所有response chain.doFilter(request, response); System.out.println("过滤器2 后置处理"); } }
-
模拟实现过滤器链FilterChain
public class FilterChain { private List<Filter> filters = new ArrayList<Filter>(); private int index = 0; // 链式调用 public FilterChain addFilter(Filter filter) { this.filters.add(filter); return this; } public void doFilter(Request request, Response response) { if (index == filters.size()) { return; } Filter filter = filters.get(index); index++; filter.doFilter(request, response, this); } }
-
测试类
public class Client { public static void main(String[] args) { Request req = null; Response res = null ; FilterChain filterChain = new FilterChain(); filterChain.addFilter(new FirstFilter()).addFilter(new SecondFilter()); filterChain.doFilter(req,res); } }
六、 观察者模式
1.概述
定义:
又被称为发布-订阅(Publish/Subscribe)模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
2.结构
在观察者模式中有如下角色:
-
Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
-
ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
-
Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
-
ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
3.案例实现
【例】天气推送系统
类图如下:
使用前的代码如下:
public class WeatherData {
double temperature;
double humidity;
public WeatherData(double temperature, double humidity) {
this.temperature = temperature;
this.humidity = humidity;
}
public void getWeatherInfo() {
System.out.println("当前温度:" + temperature + ",当前湿度:" + humidity);
}
public void change(double temperature, double humidity) {
this.temperature = temperature;
this.humidity = humidity;
}
}
public class Baidu {
private WeatherData weatherData;
public Baidu(WeatherData weatherData) {
this.weatherData = weatherData;
}
public void getWeatherInfo() {
System.out.print("百度网站温馨提示===>");
weatherData.getWeatherInfo();
}
}
class Sina {
private WeatherData weatherData;
public Sina(WeatherData weatherData) {
this.weatherData = weatherData;
}
public void getWeatherInfo() {
System.out.print("新浪网站温馨提示===>");
weatherData.getWeatherInfo();
}
}
public class Client {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData(30,20);
Baidu baidu = new Baidu(weatherData);
Sina sina = new Sina(weatherData);
baidu.getWeatherInfo();
sina.getWeatherInfo();
weatherData.change(10,10);
baidu.getWeatherInfo();
sina.getWeatherInfo();
}
}
由第三方(百度、新浪)主动获取最新天气信息,这种方案需要每个第三方主动定时获取最新天气数据,涉及多个第三方;
使用后的代码如下:
public interface Subject {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
class WeatherData implements Subject{
double temperature;
double humidity;
List<Observer> Observers = new ArrayList<>();
public WeatherData(double temperature, double humidity) {
this.temperature = temperature;
this.humidity = humidity;
}
public void update(double temperature, double humidity) {
this.temperature = temperature;
this.humidity = humidity;
// 气象局数据一改变,马上通知接入的第三方/观察者
notifyObservers();
}
@Override
public void addObserver(Observer observer) {
Observers.add(observer);
observer.update(this.temperature,this.humidity);
}
@Override
public void removeObserver(Observer observer) {
Observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : Observers) {
observer.update(this.temperature,this.humidity);
}
}
}
public interface Observer {
void display();
void update(double temperature, double humidity);
}
class Baidu implements Observer{
double temperature;
double humidity;
@Override
public void display() {
System.out.println("百度温馨提示:当前温度:" + temperature + ",当前湿度:" + humidity);
}
@Override
public void update(double temperature, double humidity) {
this.temperature = temperature;
this.humidity = humidity;
this.display();
}
}
class Sina implements Observer{
double temperature;
double humidity;
@Override
public void display() {
System.out.println("新浪温馨提示:当前温度:" + temperature + ",当前湿度:" + humidity);
}
@Override
public void update(double temperature, double humidity) {
this.temperature = temperature;
this.humidity = humidity;
this.display();
}
}
public class Client {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData(30, 20);
Baidu baidu = new Baidu();
Sina sina = new Sina();
weatherData.addObserver(baidu);
weatherData.addObserver(sina);
weatherData.update(10, 10);
weatherData.removeObserver(baidu);
weatherData.update(12, 12);
}
}
由气象局主动通知第三方,天气数据发生了改变;并且,第三方的接入可以控制(增加、删除、通知);
4.优缺点
1,优点:
-
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
-
被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】
2,缺点:
-
如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
-
如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃
5.使用场景
-
对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
-
当一个抽象模型有两个方面,其中一个方面依赖于另一方面时。
6.JDK中提供的实现
在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
1,Observable类
Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
-
void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。
-
void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。
-
void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。
2,Observer 接口
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作。
【例】警察抓小偷
警察抓小偷也可以使用观察者模式来实现,警察是观察者,小偷是被观察者。代码如下:
小偷是一个被观察者,所以需要继承Observable类
public class Thief extends Observable {
private String name;
public Thief(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void steal() {
System.out.println("小偷:我偷东西了,有没有人来抓我!!!");
super.setChanged(); //changed = true
super.notifyObservers();
}
}
警察是一个观察者,所以需要让其实现Observer接口
public class Policemen implements Observer {
private String name;
public Policemen(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void update(Observable o, Object arg) {
System.out.println("警察:" + ((Thief) o).getName() + ",我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!!!");
}
}
客户端代码
public class Client {
public static void main(String[] args) {
//创建小偷对象
Thief t = new Thief("隔壁老王");
//创建警察对象
Policemen p = new Policemen("小李");
//让警察盯着小偷
t.addObserver(p);
//小偷偷东西
t.steal();
}
}
七、策略模式
1.概述
先看下面的图片,我们去旅游选择出行模式有很多种,可以骑自行车、可以坐汽车、可以坐火车、可以坐飞机。
作为一个程序猿,开发需要选择一款开发工具,当然可以进行代码开发的工具有很多,可以选择Idea进行开发,也可以使用eclipse进行开发,也可以使用其他的一些开发工具。
定义:
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
2.结构
策略模式的主要角色如下:
-
抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
-
具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
-
环境(Context)类:持有一个策略类的引用,最终给客户端调用。
3.案例实现
【例】学院共有专业需求
要求求出两个学校之间有多少的共有专业,类图如下:
使用前的代码如下:
public class Major {
private String name;
public Major(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
Major major = (Major) obj;
return this.name.equals(major.name);
}
}
public class College {
String name;
public College(String name) {
this.name = name;
}
}
class CollegeA extends College{
List<Major> list = new ArrayList<>();
CollegeA(String name){
super(name);
this.list.add(new Major("JAVA"));
this.list.add(new Major("PHP"));
this.list.add(new Major("JavaScript"));
this.list.add(new Major("C语言"));
this.list.add(new Major("android"));
}
}
class CollegeB extends College{
List<Major> list = new ArrayList<>();
CollegeB(String name){
super(name);
this.list.add(new Major("iOS"));
this.list.add(new Major("PHP"));
this.list.add(new Major("JavaScript"));
this.list.add(new Major("C语言"));
this.list.add(new Major("嵌入式"));
}
}
public class StrategyA {
public List<Major> intersect(List<Major> a,List<Major> b){
List<Major> list = new ArrayList();
for (Major major : a) {
if(b.contains(major)){
list.add(major);
}
}
return list;
}
}
class StrategyB {
public List<Major> intersect(List<Major> a,List<Major> b){
// a.retainAll(b);
b.retainAll(a);
return b;
}
}
public class Client {
public static void main(String[] args) {
StrategyA strategyA = new StrategyA();
CollegeA a = new CollegeA("北京大学");
CollegeB b = new CollegeB("清华大学");
List<Major> intersect = strategyA.intersect(a.list, b.list);
System.out.println(a.name + "与" + b.name + "都有的专业");
for (Major major : intersect) {
System.out.println(major.getName());
}
StrategyB strategyB = new StrategyB();
List<Major> intersect2 = strategyB.intersect(a.list, b.list);
System.out.println(a.name + "与" + b.name + "都有的专业");
for (Major major : intersect2) {
System.out.println(major.getName());
}
}
}
使用后的代码:
public class Major {
private String name;
public Major(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
Major major = (Major) obj;
return this.name.equals(major.name);
}
}
public class College {
String name;
public College(String name) {
this.name = name;
}
}
class CollegeA extends College {
List<Major> list = new ArrayList<>();
CollegeA(String name){
super(name);
this.list.add(new Major("JAVA"));
this.list.add(new Major("PHP"));
this.list.add(new Major("JavaScript"));
this.list.add(new Major("C语言"));
this.list.add(new Major("android"));
}
}
class CollegeB extends College {
List<Major> list = new ArrayList<>();
CollegeB(String name){
super(name);
this.list.add(new Major("iOS"));
this.list.add(new Major("PHP"));
this.list.add(new Major("JavaScript"));
this.list.add(new Major("C语言"));
this.list.add(new Major("嵌入式"));
}
}
public interface Strategy {
List<Major> intersect(List<Major> a, List<Major> b);
}
public class StrategyA implements Strategy{
public List<Major> intersect(List<Major> a,List<Major> b){
List<Major> list = new ArrayList();
for (Major major : a) {
if(b.contains(major)){
list.add(major);
}
}
return list;
}
}
public class Context {
public List<Major> intersect(List<Major> a, List<Major> b,Strategy strategy){
return strategy.intersect(a,b);
}
}
public class Client {
public static void main(String[] args) {
CollegeA a = new CollegeA("北京大学");
CollegeB b = new CollegeB("清华大学");
Context context = new Context();
List<Major> intersect = context.intersect(a.list, b.list, new StrategyA());
System.out.println(a.name + "与" + b.name + "都有的专业");
for (Major major : intersect) {
System.out.println(major.getName());
}
// 可以随意定制策略
List<Major> intersect2 = context.intersect(a.list, b.list, new Strategy() {
@Override
public List<Major> intersect(List<Major> a, List<Major> b) {
a.retainAll(b);
return a;
}
});
System.out.println(a.name + "与" + b.name + "都有的专业========");
for (Major major : intersect2) {
System.out.println(major.getName());
}
}
}
4.优缺点
1,优点:
-
策略类之间可以自由切换
由于策略类都实现同一个接口,所以使它们之间可以自由切换。
-
易于扩展
增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“
-
避免使用多重条件选择语句(if else),充分体现面向对象设计思想。
2,缺点:
-
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
-
策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
5.使用场景
-
一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
-
一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
-
系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
-
系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
-
多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
6.JDK源码解析
Comparator
中的策略模式。在Arrays类中有一个 sort()
方法,如下:
public class Arrays{
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a);
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
}
Arrays就是一个环境角色类,这个sort方法可以传一个新策略让Arrays根据这个策略来进行排序。就比如下面的测试类。
public class demo {
public static void main(String[] args) {
Integer[] data = {12, 2, 3, 2, 4, 5, 1};
// 实现降序排序
Arrays.sort(data, new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
System.out.println(Arrays.toString(data)); //[12, 5, 4, 3, 2, 2, 1]
}
}
这里我们在调用Arrays的sort方法时,第二个参数传递的是Comparator接口的子实现类对象。所以Comparator充当的是抽象策略角色,而具体的子实现类充当的是具体策略角色。环境角色类(Arrays)应该持有抽象策略的引用来调用。那么,Arrays类的sort方法到底有没有使用Comparator子实现类中的 compare()
方法吗?让我们继续查看TimSort类的 sort()
方法,代码如下:
class TimSort<T> {
static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
T[] work, int workBase, int workLen) {
assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;
int nRemaining = hi - lo;
if (nRemaining < 2)
return; // Arrays of size 0 and 1 are always sorted
// If array is small, do a "mini-TimSort" with no merges
if (nRemaining < MIN_MERGE) {
int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
binarySort(a, lo, hi, lo + initRunLen, c);
return;
}
...
}
private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,Comparator<? super T> c) {
assert lo < hi;
int runHi = lo + 1;
if (runHi == hi)
return 1;
// Find end of run, and reverse range if descending
if (c.compare(a[runHi++], a[lo]) < 0) { // Descending
while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
runHi++;
reverseRange(a, lo, runHi);
} else { // Ascending
while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
runHi++;
}
return runHi - lo;
}
}
上面的代码中最终会跑到 countRunAndMakeAscending()
这个方法中。我们可以看见,只用了compare方法,所以在调用Arrays.sort方法只传具体compare重写方法的类对象就行,这也是Comparator接口中必须要子类实现的一个方法。