状态模式
概述
状态模式:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。
模式结构
在状态模式结构图中包含如下几个角色:
- Context(环境类):环境类又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象。
- State(抽象状态类):它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。
- ConcreteState(具体状态类):它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。
案例
抽奖活动项目设计
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();
}
}
}
从上面的编码中,我们可以看出,完成该需求有很多的条件判断,非常不利于后续的维护;上面状态只有4个,代码已经比较复杂了;状态越多,代码嵌套就越复杂,维护成本就越高;
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("------------------");
}
}
一次没抽中
抽中一次
抽中两次
注意事项和细节
- 代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
方便维护。将容易产生问题的 if-else 语句删除了
符合“开闭原则”。容易增删状态
缺点:会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
总结
状态模式将一个对象在不同状态下的不同行为封装在一个个状态类中,通过设置不同的状态对象可以让环境对象拥有不同的行为,而状态转换的细节对于客户端而言是透明的,方便了客户端的使用。在实际开发中,状态模式具有较高的使用频率,在工作流和游戏开发中状态模式都得到了广泛的应用,例如公文状态的转换、游戏中角色的升级等。
主要优点
- 封装了状态的转换规则,在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
- 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可以让我们避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起。
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
主要缺点
- 状态模式的使用必然会增加系统中类和对象的个数,导致系统运行开销增大。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的难度。
- 状态模式对“开闭原则”的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。
适用场景
- 对象的行为依赖于它的状态(如某些属性值),状态的改变将导致行为的变化。
- 在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强。
责任链模式
概述
职责链模式(Chain of Responsibility Pattern):避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。职责链模式是一种对象行为型模式
模式结构
在职责链模式结构图中包含如下几个角色:
- Handler(抽象处理者):它定义了一个处理请求的接口,一般设计为抽象类,由于不同的具体处理者处理请求的方式不同,因此在其中定义了抽象请求处理方法。因为每一个处理者的下家还是一个处理者,因此在抽象处理者中定义了一个抽象处理者类型的对象(如结构图中的successor),作为其对下家的引用。通过该引用,处理者可以连成一条链。
- ConcreteHandler(具体处理者):它是抽象处理者的子类,可以处理用户请求,在具体处理者类中实现了抽象处理者中定义的抽象请求处理方法,在处理请求之前需要进行判断,看是否有相应的处理权限,如果可以处理请求就处理它,否则将请求转发给后继者;在具体处理者中可以访问链中下一个对象,以便请求的转发。
案例
需求: OA系统请假审批案例
学生请假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;
}
}
package com.javaxl.design.chain.before;
/**
* @author 小李飞刀
* @site www.javaxl.com
* @company
* @create 2020-02-25 19:31
*/
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);
}
}
注意事项:
将请求和处理分开,实现解耦,提高系统的灵活性 简化了对象,使对象不需要知道链的结构
职责链模式并不创建职责链,职责链的创建工作必须由系统的其他部分来完成,一般是在使用该职责链的客户端中创建职责链
性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在 Handler 中设置一个最大节点数量,在 setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
总结
职责链模式通过建立一条链来组织请求的处理者,请求将沿着链进行传递,请求发送者无须知道请求在何时、何处以及如何被处理,实现了请求发送者与处理者的解耦。在软件开发中,如果遇到有多个对象可以处理同一请求时可以应用职责链模式,例如在Web应用开发中创建一个过滤器(Filter)链来对请求数据进行过滤,在工作流系统中实现公文的分级审批等等,使用职责链模式可以较好地解决此类问题。
1.主要优点
- 职责链模式使得一个对象无须知道是其他哪一个对象处理其请求,对象仅需知道该请求会被处理即可,接收者和发送者都没有对方的明确信息,且链中的对象不需要知道链的结构,由客户端负责链的创建,降低了系统的耦合度。
- 请求处理对象仅需维持一个指向其后继者的引用,而不需要维持它对所有的候选处理者的引用,可简化对象的相互连接。
- 在给对象分派职责时,职责链可以给我们更多的灵活性,可以通过在运行时对该链进行动态的增加或修改来增加或改变处理一个请求的职责。
- 在系统中增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在客户端重新建链即可,从这一点来看是符合“开闭原则”的。
主要缺点
- 由于一个请求没有明确的接收者,那么就不能保证它一定会被处理,该请求可能一直到链的末端都得不到处理;一个请求也可能因职责链没有被正确配置而得不到处理。
- 对于比较长的职责链,请求的处理可能涉及到多个处理对象,系统性能将受到一定影响,而且在进行代码调试时不太方便。
- 如果建链不当,可能会造成循环调用,将导致系统陷入死循环。
适用场景
- 有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定,客户端只需将请求提交到链上,而无须关心请求的处理对象是谁以及它是如何处理的。
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
- 可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序。
观察者模式
概述
观察者模式(Observer Pattern):定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式是一种对象行为型模式。
在观察者模式结构图中包含如下几个角色:
- Subject(目标):目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notify()。目标类可以是接口,也可以是抽象类或具体类。
- ConcreteSubject(具体目标):具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有的话)。如果无须扩展目标类,则具体目标类可以省略。
- Observer(观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法update(),因此又称为抽象观察者。
- ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者Observer中定义的update()方法。通常在实现时,可以调用具体目标类的attach()方法将自己添加到目标类的集合中或通过detach()方法将自己从目标类的集合中删除。
案例
需求:气象站数据更新推送问题
使用前
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);
}
}
由气象局主动通知第三方,天气数据发生了改变;并且,第三方的接入可以控制(增加、删除、通知)
注意事项和细节:
集合的方式来管理用户(Observer),包括注册,移除和通知
总结
观察者模式是一种使用频率非常高的设计模式,无论是移动应用、Web应用或者桌面应用,观察者模式几乎无处不在,它为实现对象之间的联动提供了一套完整的解决方案,凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。观察者模式广泛应用于各种编程语言的GUI事件处理的实现,在基于事件的XML解析技术(如SAX2)以及Web事件处理中也都使用了观察者模式。
主要优点
- 观察者模式可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色。
- 观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。
- 观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。
- 观察者模式满足“开闭原则”的要求,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。
主要缺点
- 如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
适用场景
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致一个或多个其他对象也发生改变,而并不知道具体有多少对象将发生改变,也不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
策略模式
概述
策略模式(Strategy Pattern):定义一系列算法类,将每一个算法封装起来,并让它们可以相互替换,策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。策略模式是一种对象行为型模式
模式结构
在策略模式结构图中包含如下几个角色:
Context(环境类):环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。
Strategy(抽象策略类):它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。
ConcreteStrategy(具体策略类):它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。
案例
需求:学院共有专业需求
使用前
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());
}
}
}
注意事项和细节:
分析项目中变化部分与不变部分
体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为) 即可
策略模式将算法封装在独立的 Strategy 类中使得你可以独立于其 Context 改变它,使它易于切换、易于理解、易于扩展
- 在很多场景中,策略接口会作为内部接口体现
应用:
Arrays工具类的排序方法Comparator策略接口的使用
JDBC对Result结果集的处理
总结
策略模式用于算法的自由切换和扩展,它是应用较为广泛的设计模式之一。策略模式对应于解决某一问题的一个算法族,允许用户从该算法族中任选一个算法来解决某一问题,同时可以方便地更换算法或者增加新的算法。只要涉及到算法的封装、复用和切换都可以考虑使用策略模式。
主要优点
- 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
- 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族,恰当使用继承可以把公共的代码移到抽象策略类中,从而避免重复的代码。
- 策略模式提供了一种可以替换继承关系的办法。如果不使用策略模式,那么使用算法的环境类就可能会有一些子类,每一个子类提供一种不同的算法。但是,这样一来算法的使用就和算法本身混在一起,不符合“单一职责原则”,决定使用哪一种算法的逻辑和该算法本身混合在一起,从而不可能再独立演化;而且使用继承无法实现算法或行为在程序运行时的动态切换。
- 使用策略模式可以避免多重条件选择语句。多重条件选择语句不易维护,它把采取哪一种算法或行为的逻辑与算法或行为本身的实现逻辑混合在一起,将它们全部硬编码(Hard Coding)在一个庞大的多重条件选择语句中,比直接继承环境类的办法还要原始和落后。
- 策略模式提供了一种算法的复用机制,由于将算法单独提取出来封装在策略类中,因此不同的环境类可以方便地复用这些策略类。
主要缺点
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
- 策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类。
- 无法同时在客户端使用多个策略类,也就是说,在使用策略模式时,客户端每次只能使用一个策略类,不支持使用一个策略类完成部分功能后再使用另一个策略类来完成剩余功能的情况。
适用场景
- 一个系统需要动态地在几种算法中选择一种,那么可以将这些算法封装到一个个的具体算法类中,而这些具体算法类都是一个抽象算法类的子类。换言之,这些具体算法类均有统一的接口,根据“里氏代换原则”和面向对象的多态性,客户端可以选择使用任何一个具体算法类,并只需要维持一个数据类型是抽象算法类的对象。
- 一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句。
- 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法与相关的数据结构,可以提高算法的保密性与安全性。
至此,行为模式介绍完毕,由于作者水平有限难免有疏漏,欢迎留言纠错。