设计模式-行为型模式(上)

行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。

行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。

1.观察者模式

观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者 一些产品的设计思路,都有这种模式的影子.

现在我们常说的基于事件驱动的架构,其实也是观察者模式的一种最佳实践。当我们观察某一个对象时,对象传递出的每一个行为都被看成是一个事件,观察者通过处理每一个事件来完成自身的操作处理。

生活中也有许多观察者模式的应用,比如 汽车与红绿灯的关系,'红灯停,绿灯行',在这个过程中交通信号灯是汽车的观察目标,而汽车是观察者.

观察者模式(observer pattern)的原始定义是:

定义对象之间的一对多依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。

解释一下上面的定义: 观察者模式它是用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应的作出反应.

在观察者模式中发生改变的对象称为观察目标,而被通知的对象称为观察,一个观察目标可以应对多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展.
观察者模式的别名有发布-订阅(Publish/Subscribe)模式,模型-视图(Model-View)模式、源-监听(Source-Listener) 模式等

1.1观察者模式原理

观察者模式结构中通常包括: 观察目标和观察者两个继承层次结构.

1.1.2在观察者模式中有如下角色:

Subject:抽象主题(抽象被观察者):

抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
ConcreteSubject:具体主题(具体被观察者):

该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
Observer:抽象观察者:

观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
ConcrereObserver:具体观察者:

实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要与具体目标保持一致.

 1.2观察者模式实现

观察者

/*** 抽象观察者* @author spikeCong* @date 2022/10/11**/
public interface Observer {//update方法: 为不同观察者的更新(响应)行为定义相同的接口,不同的观察者对该方法有不同的实现public void update();
}/*** 具体观察者* @author spikeCong* @date 2022/10/11**/
public class ConcreteObserverOne implements Observer {@Overridepublic void update() {//获取消息通知,执行业务代码System.out.println("ConcreteObserverOne 得到通知!");}
}/*** 具体观察者* @author spikeCong* @date 2022/10/11**/
public class ConcreteObserverTwo implements Observer {@Overridepublic void update() {//获取消息通知,执行业务代码System.out.println("ConcreteObserverTwo 得到通知!");}
}

被观察者

/*** 抽象目标类* @author spikeCong* @date 2022/10/11**/
public interface Subject {void attach(Observer observer);void detach(Observer observer);void notifyObservers();
}/*** 具体目标类* @author spikeCong* @date 2022/10/11**/
public class ConcreteSubject implements Subject {//定义集合,存储所有观察者对象private ArrayList<Observer> observers = new ArrayList<>();//注册方法,向观察者集合中增加一个观察者@Overridepublic void attach(Observer observer) {observers.add(observer);}//注销方法,用于从观察者集合中删除一个观察者@Overridepublic void detach(Observer observer) {observers.remove(observer);}//通知方法@Overridepublic void notifyObservers() {//遍历观察者集合,调用每一个观察者的响应方法for (Observer obs : observers) {obs.update();}}
}

测试类

public class Client {public static void main(String[] args) {//创建目标类(被观察者)ConcreteSubject subject = new ConcreteSubject();//注册观察者类,可以注册多个subject.attach(new ConcreteObserverOne());subject.attach(new ConcreteObserverTwo());//具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。subject.notifyObservers();}
}

1.3观察者模式应用实例

接下来我们使用观察模式,来实现一个买房摇号的程序.摇号结束,需要通过短信告知用户摇号结果,还需要想MQ中保存用户本次摇号的信息.

1.3.1未使用设计模式

/*** 模拟买房摇号服务* @author spikeCong* @date 2022/10/11**/
public class DrawHouseService {//摇号抽签public String lots(String uId){if(uId.hashCode() % 2 == 0){return "恭喜ID为: " + uId + " 的用户,在本次摇号中中签! !";}else{return "很遗憾,ID为: " + uId + "的用户,您本次未中签! !";}}
}public class LotteryResult {private String uId; // 用户idprivate String msg; // 摇号信息private Date dataTime; // 业务时间//get&set.....
}/*** 开奖服务接口* @author spikeCong* @date 2022/10/11**/
public interface LotteryService {//摇号相关业务public LotteryResult lottery(String uId);
}/*** 开奖服务* @author spikeCong* @date 2022/10/11**/
public class LotteryServiceImpl implements LotteryService {//注入摇号服务private DrawHouseService houseService = new DrawHouseService();@Overridepublic LotteryResult lottery(String uId) {//摇号String result = houseService.lots(uId);//发短信System.out.println("发送短信通知用户ID为: " + uId + ",您的摇号结果如下: " + result);//发送MQ消息System.out.println("记录用户摇号结果(MQ), 用户ID:" +  uId + ",摇号结果:" + result);return new LotteryResult(uId,result,new Date());}
}@Test
public void test1(){LotteryService ls = new LotteryServiceImpl();String result  = ls.lottery("1234567887654322");System.out.println(result);
}

1.3.2使用观察者模式进行优化

上面的摇号业务中,摇号、发短信、发MQ消息是一个顺序调用的过程,但是除了摇号这个核心功能以外, 发短信与记录信息到MQ的操作都不是主链路的功能,需要单独抽取出来,这样才能保证在后面的开发过程中保证代码的可扩展性和可维护性

事件监听

/*** 事件监听接口* @author spikeCong* @date 2022/10/11**/
public interface EventListener {void doEvent(LotteryResult result);
}/*** 短信发送事件* @author spikeCong* @date 2022/10/11**/
public class MessageEventListener implements EventListener {@Overridepublic void doEvent(LotteryResult result) {System.out.println("发送短信通知用户ID为: " + result.getuId() +",您的摇号结果如下: " + result.getMsg());}
}/*** MQ消息发送事件* @author spikeCong* @date 2022/10/11**/
public class MQEventListener implements EventListener {@Overridepublic void doEvent(LotteryResult result) {System.out.println("记录用户摇号结果(MQ), 用户ID:" +  result.getuId() +",摇号结果:" + result.getMsg());}
}

事件处理

/*** 事件处理类* @author spikeCong* @date 2022/10/11**/
public class EventManager {public enum EventType{MQ,Message}//监听器集合Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>();public EventManager(Enum<EventType>... operations) {for (Enum<EventType> operation : operations) {this.listeners.put(operation,new ArrayList<>());}}/*** 订阅* @param eventType 事件类型* @param listener  监听*/public void subscribe(Enum<EventType> eventType, EventListener listener){List<EventListener> users = listeners.get(eventType);users.add(listener);}/*** 取消订阅* @param eventType 事件类型* @param listener  监听*/public void unsubscribe(Enum<EventType> eventType,EventListener listener){List<EventListener> users = listeners.get(eventType);users.remove(listener);}/*** 通知* @param eventType 事件类型* @param result    结果*/public void notify(Enum<EventType> eventType, LotteryResult result){List<EventListener> users = listeners.get(eventType);for (EventListener listener : users) {listener.doEvent(result);}}
}

摇号业务处理

/*** 开奖服务接口* @author spikeCong* @date 2022/10/11**/
public abstract class LotteryService{private EventManager eventManager;public LotteryService(){//设置事件类型eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);//订阅eventManager.subscribe(EventManager.EventType.Message,new MessageEventListener());eventManager.subscribe(EventManager.EventType.MQ,new MQEventListener());}public LotteryResult lotteryAndMsg(String uId){LotteryResult result = lottery(uId);//发送通知eventManager.notify(EventManager.EventType.Message,result);eventManager.notify(EventManager.EventType.MQ,result);return result;}public abstract LotteryResult lottery(String uId);
}/*** 开奖服务* @author spikeCong* @date 2022/10/11**/
public class LotteryServiceImpl extends LotteryService {//注入摇号服务private DrawHouseService houseService = new DrawHouseService();@Overridepublic LotteryResult lottery(String uId) {//摇号String result = houseService.lots(uId);return new LotteryResult(uId,result,new Date());}
}

测试

@Test
public void test2(){LotteryService ls = new LotteryServiceImpl();LotteryResult result  = ls.lotteryAndMsg("1234567887654322");System.out.println(result);
}

1.4 观察者模式总结

1.4.1观察者模式的优点

降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】

1.4.2观察者模式的缺点

如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

1.4.3 观察者模式常见的使用场景

当一个对象状态的改变需要改变其他对象时。比如,商品库存数量发生变化时,需要通知商品详情页、购物车等系统改变数量。
一个对象发生改变时只想要发送通知,而不需要知道接收者是谁。比如,订阅微信公众号的文章,发送者通过公众号发送,订阅者并不知道哪些用户订阅了公众号。
需要创建一种链式触发机制时。比如,在系统中创建一个触发链,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象……这样通过观察者模式能够很好地实现。
微博或微信朋友圈发送的场景。这是观察者模式的典型应用场景,一个人发微博或朋友圈,只要是关联的朋友都会收到通知;一旦取消关注,此人以后将不会收到相关通知。
需要建立基于事件触发的场景。比如,基于 Java UI 的编程,所有键盘和鼠标事件都由它的侦听器对象和指定函数处理。当用户单击鼠标时,订阅鼠标单击事件的函数将被调用,并将所有上下文数据作为方法参数传递给它。

1.4.4JDK 中对观察者模式的支持

JDK中提供了Observable类以及Observer接口,它们构成了JDK对观察者模式的支持.

java.util.Observer接口: 该接口中声明了一个方法,它充当抽象观察者,其中声明了一个update方法.

void update(Observable o, Object arg);

java.util.Observable 类: 充当观察目标类(被观察类) , 在该类中定义了一个Vector集合来存储观察者对象.下面是它最重要的 3 个方法。 

 void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。

 void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。
 void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。

用户可以直接使用Observer接口和Observable类作为观察者模式的抽象层,再自定义具体观察者类和具体观察目标类,使用JDK中提供的这两个类可以更加方便的实现观察者模式.

2.模板方法模式

模板方法模式(template method pattern)原始定义是:在操作中定义算法的框架,将一些步骤推迟到子类中。模板方法让子类在不改变算法结构的情况下重新定义算法的某些步骤。

模板方法中的算法可以理解为广义上的业务逻辑,并不是特指某一个实际的算法.定义中所说的算法的框架就是模板, 包含算法框架的方法就是模板方法.

例如: 我们去医院看病一般要经过以下4个流程:挂号、取号、排队、医生问诊等,其中挂号、 取号 、排队对每个病人是一样的,可以在父类中实现,但是具体医生如何根据病情开药每个人都是不一样的,所以开药这个操作可以延迟到子类中实现。

模板方法模式是一种基于继承的代码复用技术,它是一种类行为模式. 模板方法模式其结构中只存在父类与子类之间的继承关系.

模板方法的作用主要是提高程序的复用性和扩展性:

复用指的是,所有的子类可以复用父类中提供的模板方法代码
扩展指的是,框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能.

2.1模板方法模式原理

模板方法模式的定位很清楚,就是为了解决算法框架这类特定的问题,同时明确表示需要使用继承的结构。

2.2模板方法(Template Method)模式包含以下主要角色:

抽象父类:

定义一个算法所包含的所有步骤,并提供一些通用的方法逻辑。
具体子类:

继承自抽象父类,根据需要重写父类提供的算法步骤中的某些步骤。

抽象类(Abstract Class):

负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。

模板方法:

定义了算法的骨架,按某种顺序调用其包含的基本方法。
基本方法:

是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:

抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。
具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
钩子方法(Hook Method) :

在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。

一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型

2.3模板方法模式实现

代码实现

/*** 抽象父类* @author spikeCong* @date 2022/10/12**/
public abstract class AbstractClassTemplate {void step1(String key){System.out.println("在模板类中 -> 执行步骤1");if(step2(key)){step3();}else{step4();}step5();}boolean step2(String key){System.out.println("在模板类中 -> 执行步骤2");if("x".equals(key)){return true;}return false;}abstract void step3();abstract void step4();void step5(){System.out.println("在模板类中 -> 执行步骤5");}void run(String key){step1(key);}}public class ConcreteClassA extends AbstractClassTemplate{@Overridevoid step3() {System.out.println("在子类A中 -> 执行步骤 3");}@Overridevoid step4() {System.out.println("在子类A中 -> 执行步骤 4");}
}public class ConcreteClassB extends AbstractClassTemplate {@Overridevoid step3() {System.out.println("在子类B中 -> 执行步骤 3");}@Overridevoid step4() {System.out.println("在子类B中 -> 执行步骤 4");}
}public class Test01 {public static void main(String[] args) {AbstractClassTemplate concreteClassA = new ConcreteClassA();concreteClassA.run("");System.out.println("===========");AbstractClassTemplate concreteClassB = new ConcreteClassB();concreteClassB.run("x");}
}// 输出结果
在模板类中 -> 执行步骤1
在模板类中 -> 执行步骤2
在子类A中 -> 执行步骤 4
在模板类中 -> 执行步骤5
===========
在模板类中 -> 执行步骤1
在模板类中 -> 执行步骤2
在子类B中 -> 执行步骤 3
在模板类中 -> 执行步骤5

2.4模板方法模式应用实例

P2P公司的借款系统中有一个利息计算模块,利息的计算流程是这样的:

1. 用户登录系统,登录时需要输入账号密码,如果登录失败(比如用户密码错误),系统需要给出提示
2. 如果用户登录成功,则根据用户的借款的类型不同,使用不同的利息计算方式进行计算
3. 系统需要显示利息.

/*** 账户抽象类* @author spikeCong* @date 2022/10/12**/
public abstract class Account {//step1 具体方法-验证用户信息是否正确public boolean validate(String account,String password){System.out.println("账号: " + account + ",密码: " + password);if(account.equalsIgnoreCase("tom") &&password.equalsIgnoreCase("123456")){return true;}else{return false;}}//step2 抽象方法-计算利息public abstract void calculate();//step3 具体方法-显示利息public void display(){System.out.println("显示利息!");}//模板方法public void handle(String account,String password){if(!validate(account,password)){System.out.println("账户或密码错误!!");return;}calculate();display();}
}/*** 借款一个月* @author spikeCong* @date 2022/10/12**/
public class LoanOneMonth extends Account{@Overridepublic void calculate() {System.out.println("借款周期30天,利率为10%!");}
}/*** 借款七天* @author spikeCong* @date 2022/10/12**/
public class LoanSevenDays extends Account{@Overridepublic void calculate() {System.out.println("借款周期7天,无利息!仅收取贷款金额1%的服务费!");}@Overridepublic void display() {System.out.println("七日内借款无利息!");}}public class Client {public static void main(String[] args) {Account a1 = new LoanSevenDays();a1.handle("tom","12345");System.out.println("==========================");Account a2 = new LoanOneMonth();a2.handle("tom","123456");}
}

2.5模板方法模式总结

2.5.1优点:

在父类中形式化的定义一个算法,而由它的子类来实现细节处理,在子类实现详细的处理代码时,并不会改变父类算法中步骤的执行顺序.
模板方法可以实现一种反向的控制结构,通过子类覆盖父类的钩子方法,来决定某一个特定步骤是否需要执行
在模板方法模式中可以通过子类来覆盖父类的基本方法,不同的子类可以提供基本方法的不同实现,更换和增加新的子类很方便,符合单一职责原则和开闭原则.

2.5.2缺点:

对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

2.5.3模板方法模式的使用场景一般有:

多个类有相同的方法并且逻辑可以共用时;
将通用的算法或固定流程设计为模板,在每一个具体的子类中再继续优化算法步骤或流程步骤时;
重构超长代码时,发现某一个经常使用的公有方法。

3.策略模式

策略模式(strategy pattern)的原始定义是:定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。策略模式让算法可以独立于使用它的客户端而变化。

其实我们在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等。

在软件开发中,经常会遇到这种情况,开发一个功能可以通过多个算法去实现,我们可以将所有的算法集中在一个类中,在这个类中提供多个方法,每个方法对应一个算法, 或者我们也可以将这些算法都封装在一个统一的方法中,使用if...else...等条件判断语句进行选择.但是这两种方式都存在硬编码的问题,后期需要增加算法就需要修改源代码,这会导致代码的维护变得困难.

比如网购,你可以选择工商银行、农业银行、建设银行等等,但是它们提供的算法都是一致的,就是帮你付款。

在软件开发中也会遇到相似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。

3.1策略模式原理

在策略模式中可以定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法,在这里每一个封装算法的类都可以被称为一种策略,为了保证这些策略在使用时具有一致性,一般会提供一个抽象的策略类来做算法的声明.而每种算法对应一个具体的策略类

策略模式的主要角色如下:

抽象策略(Strategy)类:

这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
具体策略(Concrete Strategy)类:

实现了抽象策略定义的接口,提供具体的算法实现或行为。
环境或上下文(Context)类:

是使用算法的角色,  持有一个策略类的引用,最终给客户端调用。

3.2策略模式实现

策略模式的本质是通过Context类来作为中心控制单元,对不同的策略进行调度分配。

/*** 抽象策略类* @author spikeCong* @date 2022/10/13**/
public interface Strategy {void algorithm();
}public class ConcreteStrategyA implements Strategy {@Overridepublic void algorithm() {System.out.println("执行策略A");}
}public class ConcreteStrategyB implements Strategy {@Overridepublic void algorithm() {System.out.println("执行策略B");}
}/*** 环境类* @author spikeCong* @date 2022/10/13**/
public class Context {//维持一个对抽象策略类的引用private Strategy strategy;public Context(Strategy strategy) {this.strategy = strategy;}//调用策略类中的算法public void algorithm(){strategy.algorithm();}
}public class Client {public static void main(String[] args) {Strategy strategyA  = new ConcreteStrategyA();Context context = new Context(strategyA); //可以在运行时指定类型,通过配置文件+反射机制实现context.algorithm();}
}

 3.3策略模式应用实例

面试问题: 如何用设计模式消除代码中的if-else

物流行业中,通常会涉及到EDI报文(XML格式文件)传输和回执接收,每发送一份EDI报文,后续都会收到与之关联的回执(标识该数据在第三方系统中的流转状态)。

这里列举几种回执类型:MT1101、MT2101、MT4101、MT8104,系统在收到不同的回执报文后,会执行对应的业务逻辑处理。我们就业回执处理为演示案例

3.3.1不使用设计模式

回执类

/*** 回执信息* @author spikeCong* @date 2022/10/13**/
public class Receipt {private String message; //回执信息private String type; //回执类型(MT1101、MT2101、MT4101、MT8104)public Receipt() {}public Receipt(String message, String type) {this.message = message;this.type = type;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public String getType() {return type;}public void setType(String type) {this.type = type;}
}

回执生成器

public class ReceiptBuilder {public static List<Receipt> genReceiptList(){//模拟回执信息List<Receipt> receiptList = new ArrayList<>();receiptList.add(new Receipt("MT1101回执","MT1011"));receiptList.add(new Receipt("MT2101回执","MT2101"));receiptList.add(new Receipt("MT4101回执","MT4101"));receiptList.add(new Receipt("MT8104回执","MT8104"));//......return receiptList;}}

客户端

public class Client {public static void main(String[] args) {List<Receipt> receiptList = ReceiptBuilder.genReceiptList();//循环判断for (Receipt receipt : receiptList) {if("MT1011".equals(receipt.getType())){System.out.println("接收到MT1011回执!");System.out.println("解析回执内容");System.out.println("执行业务逻辑A"+"\n");}else if("MT2101".equals(receipt.getType())){System.out.println("接收到MT2101回执!");System.out.println("解析回执内容");System.out.println("执行业务逻辑B"+"\n");}else if("MT4101".equals(receipt.getType())) {System.out.println("接收到MT4101回执!");System.out.println("解析回执内容");System.out.println("执行业务逻辑C"+"\n");}else if("MT8104".equals(receipt.getType())) {System.out.println("接收到MT8104回执!");System.out.println("解析回执内容");System.out.println("执行业务逻辑D");}//......}}
}

3.1.2使用策略模式进行优化

通过策略模式, 将所有的if-else分支的业务逻辑抽取为各种策略类,让客户端去依赖策略接口,保证具体策略类的改变不影响客户端.

策略接口

/*** 回执处理策略接口* @author spikeCong* @date 2022/10/13**/
public interface ReceiptHandleStrategy {void handleReceipt(Receipt receipt);
}

具体策略类 

public class Mt1011ReceiptHandleStrategy implements ReceiptHandleStrategy {@Overridepublic void handleReceipt(Receipt receipt) {System.out.println("解析报文MT1011: " + receipt.getMessage());}
}public class Mt2101ReceiptHandleStrategy implements ReceiptHandleStrategy {@Overridepublic void handleReceipt(Receipt receipt) {System.out.println("解析报文MT2101: " + receipt.getMessage());}
}......

策略上下文类(策略接口的持有者)

/*** 上下文类,持有策略接口* @author spikeCong* @date 2022/10/13**/
public class ReceiptStrategyContext {private ReceiptHandleStrategy receiptHandleStrategy;public void setReceiptHandleStrategy(ReceiptHandleStrategy receiptHandleStrategy) {this.receiptHandleStrategy = receiptHandleStrategy;}//调用策略类中的方法public void handleReceipt(Receipt receipt){if(receipt != null){receiptHandleStrategy.handleReceipt(receipt);}}
}

策略工厂

public class ReceiptHandleStrategyFactory {public ReceiptHandleStrategyFactory() {}//使用Map集合存储策略信息,彻底消除if...elseprivate static Map<String,ReceiptHandleStrategy> strategyMap;//初始化具体策略,保存到map集合public static void init(){strategyMap = new HashMap<>();strategyMap.put("MT1011",new Mt1011ReceiptHandleStrategy());strategyMap.put("MT2101",new Mt2101ReceiptHandleStrategy());}//根据回执类型获取对应策略类对象public static ReceiptHandleStrategy getReceiptHandleStrategy(String receiptType){return strategyMap.get(receiptType);}
}

客户端

public class Client {public static void main(String[] args) {//模拟回执List<Receipt> receiptList = ReceiptBuilder.genReceiptList();//策略上下文ReceiptStrategyContext context = new ReceiptStrategyContext();//策略模式将策略的 定义、创建、使用这三部分进行了解耦for (Receipt receipt : receiptList) {//获取置策略ReceiptHandleStrategyFactory.init();ReceiptHandleStrategy strategy = ReceiptHandleStrategyFactory.getReceiptHandleStrategy(receipt.getType());//设置策略context.setReceiptHandleStrategy(strategy);//执行策略context.handleReceipt(receipt);}}
}

经过上面的改造,我们已经消除了if-else的结构,每当新来了一种回执,只需要添加新的回执处理策略,并修改ReceiptHandleStrategyFactory中的Map集合。如果要使得程序符合开闭原则,则需要调整ReceiptHandleStrategyFactory中处理策略的获取方式,通过反射的方式,获取指定包下的所有IReceiptHandleStrategy实现类,然后放到字典Map中去.

3.4策略模式总结

3.4.1策略模式优点:

策略类之间可以自由切换

由于策略类都实现同一个接口,所以使它们之间可以自由切换。

易于扩展

增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“
避免使用多重条件选择语句(if else),充分体现面向对象设计思想。

3.4.2策略模式缺点:

客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。

3.4.3策略模式使用场景

一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。

策略模式最大的作用在于分离使用算法的逻辑和算法自身实现的逻辑,这样就意味着当我们想要优化算法自身的实现逻辑时就变得非常便捷,一方面可以采用最新的算法实现逻辑,另一方面可以直接弃用旧算法而采用新算法。使用策略模式能够很方便地进行替换。
 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。

在实际开发中,有许多算法可以实现某一功能,如查找、排序等,通过 if-else 等条件判断语句来进行选择非常方便。但是这就会带来一个问题:当在这个算法类中封装了大量查找算法时,该类的代码就会变得非常复杂,维护也会突然就变得非常困难。虽然策略模式看上去比较笨重,但实际上在每一次新增策略时都通过新增类来进行隔离,短期虽然不如直接写 if-else 来得效率高,但长期来看,维护单一的简单类耗费的时间其实远远低于维护一个超大的复杂类。
系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。

如果我们不希望客户知道复杂的、与算法相关的数据结构,在具体策略类中封装算法与相关数据结构,可以提高算法的保密性与安全性.

4.职责链模式

职责链模式(chain of responsibility pattern) 定义: 避免将一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求.将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止.

在职责链模式中,多个处理器(也就是刚刚定义中说的“接收对象”)依次处理同一个请 求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再 传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职 责,所以叫作职责链模式。

4.1职责链模式原理

职责链模式结构

职责链模式主要包含以下角色:

抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接(链上的每个处理者都有一个成员变量来保存对于下一处理者的引用,比如上图中的successor) 。
具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

4.2职责链模式实现

责任链模式的实现非常简单,每一个具体的处理类都会保存在它之后的下一个处理类。当处理完成后,就会调用设置好的下一个处理类,直到最后一个处理类不再设置下一个处理类,这时处理链条全部完成。

public class RequestData {private String data;public RequestData(String data) {this.data = data;}public String getData() {return data;}public void setData(String data) {this.data = data;}
}/*** 抽象处理者类* @author spikeCong* @date 2022/10/14**/
public abstract class Handler {protected Handler successor = null;public void setSuccessor(Handler successor){this.successor = successor;}public abstract void handle(RequestData requestData);
}public class HandlerA extends Handler {@Overridepublic void handle(RequestData requestData) {System.out.println("HandlerA 执行代码逻辑! 处理: " + requestData.getData());requestData.setData(requestData.getData().replace("A",""));if(successor != null){successor.handle(requestData);}else{System.out.println("执行中止!");}}
}public class HandlerB extends Handler {@Overridepublic void handle(RequestData requestData) {System.out.println("HandlerB 执行代码逻辑! 处理: " + requestData.getData());requestData.setData(requestData.getData().replace("B",""));if(successor != null){successor.handle(requestData);}else{System.out.println("执行中止!");}}
}public class HandlerC extends Handler {@Overridepublic void handle(RequestData requestData) {System.out.println("HandlerC 执行代码逻辑! 处理: " + requestData.getData());requestData.setData(requestData.getData());if(successor != null){successor.handle(requestData);}else{System.out.println("执行中止!");}}
}public class Client {public static void main(String[] args) {Handler h1 = new HandlerA();Handler h2 = new HandlerB();Handler h3 = new HandlerC();h1.setSuccessor(h2);h2.setSuccessor(h3);RequestData requestData = new RequestData("请求数据ABCDE");h1.handle(requestData);}}

 4.3职责链模式应用实例

接下来我们模拟有一个双11期间,业务系统审批的流程,临近双十一公司会有陆续有一些新的需求上线,为了保证线上系统的稳定,我们对上线的审批流畅做了严格的控制.审批的过程会有不同级别的负责人加入进行审批(平常系统上线只需三级负责人审批即可,双十一前后需要二级或一级审核人参与审批),接下来我们就使用职责链模式来设计一下此功能.

4.3.1不使用设计模式 

/*** 审核信息* @author spikeCong* @date 2022/10/14**/
public class AuthInfo {private String code;private String info ="";public AuthInfo(String code, String... infos) {this.code = code;for (String str : infos) {info = this.info.concat(str +" ");}}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getInfo() {return info;}public void setInfo(String info) {this.info = info;}@Overridepublic String toString() {return "AuthInfo{" +"code='" + code + '\'' +", info='" + info + '\'' +'}';}
}/*** 模拟审核服务* @author spikeCong* @date 2022/10/14**/
public class AuthService {//审批信息 审批人Id+申请单Idprivate static Map<String,Date> authMap = new HashMap<String, Date>();/*** 审核流程* @param uId    审核人id* @param orderId  审核单id*/public static void auth(String uId, String orderId){System.out.println("进入审批流程,审批人ID: " + uId);authMap.put(uId.concat(orderId),new Date());}//查询审核结果public static Date queryAuthInfo(String uId, String orderId){return authMap.get(uId.concat(orderId)); //key=审核人id+审核单子id}
}public class AuthController {//审核接口public AuthInfo doAuth(String name, String orderId, Date authDate) throws ParseException {//三级审批Date date = null;//查询是否存在审核信息,查询条件: 审核人ID+订单ID,返回Map集合中的Datedate = AuthService.queryAuthInfo("1000013", orderId);//如果为空,封装AuthInfo信息(待审核)返回if(date == null){return new AuthInfo("0001","单号: "+orderId,"状态: 等待三级审批负责人进行审批");}//二级审批SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 时间格式化//二级审核人主要审核双十一之前, 11-01 ~ 11-10号的请求,所以要对传入的审核时间进行判断//审核时间 大于 2022-11-01 并且  小于 2022-11-10,Date1.after(Date2),当Date1大于Date2时,返回TRUE,Date1.before(Date2),当Date1小于Date2时,返回TRUEif(authDate.after(f.parse("2022-11-01 00:00:00")) && authDate.before(f.parse("2022-11-10 00:00:00"))){//条件成立,查询二级审核的审核信息date = AuthService.queryAuthInfo("1000012",orderId);//如果为空,还是待二级审核人审核状态if(date == null){return new AuthInfo("0001","单号: "+orderId,"状态: 等待二级审批负责人进行审批");}}//一级审批//审核范围是在11-11日 ~ 11-31日if(authDate.after(f.parse("2022-11-11 00:00:00")) && authDate.before(f.parse("2022-11-31 00:00:00"))){date = AuthService.queryAuthInfo("1000011",orderId);if(date == null){return new AuthInfo("0001","单号: "+orderId,"状态: 等待一级审批负责人进行审批");}}return new AuthInfo("0001","单号: "+orderId,"申请人:"+ name +", 状态: 审批完成!");}
}public class Client {public static void main(String[] args) throws ParseException {AuthController controller = new AuthController();SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date date = sdf.parse("2022-11-12 00:00:00");//设置申请流程//三级审核//1.调用doAuth方法,模拟发送申请人相关信息AuthInfo info1 = controller.doAuth("研发小周", "100001000010000", date);System.out.println("当前审核状态:  " + info1.getInfo());/*** 2.模拟进行审核操作, 虚拟审核人ID: 1000013* 调用auth() 方法进行审核操作, 就是向Map中添加一个 审核人ID和申请单ID*/AuthService.auth("1000013", "100001000010000");System.out.println("三级负责人审批完成,审批人: 王工");System.out.println("===========================================================================");//二级审核//1.调用doAuth方法,模拟发送申请人相关信息AuthInfo info2 = controller.doAuth("研发小周", "100001000010000", date);System.out.println("当前审核状态:  " + info2.getInfo());/*** 2.模拟进行审核操作, 虚拟审核人ID: 1000012* 调用auth() 方法进行审核操作, 就是向Map中添加一个 审核人ID和申请单ID*/AuthService.auth("1000012", "100001000010000");System.out.println("二级负责人审批完成,审批人: 张经理");System.out.println("===========================================================================");//一级审核//1.调用doAuth方法,模拟发送申请人相关信息AuthInfo info3 = controller.doAuth("研发小周", "100001000010000", date);System.out.println("当前审核状态:  " + info3.getInfo());/*** 2.模拟进行审核操作, 虚拟审核人ID: 1000012* 调用auth() 方法进行审核操作, 就是向Map中添加一个 审核人ID和申请单ID*/AuthService.auth("1000011", "100001000010000");System.out.println("一级负责人审批完成,审批人: 罗总");}
}

4.3.2职责链模式重构代码

下图是为当前业务设计的责任链结构,统一抽象类AuthLink 下 有三个子类,将三个子类的执行通过编排,模拟出一条链路,这个链路就是业务中的责任链.

/*** 抽象审核链类*/
public abstract class AuthLink {protected Logger logger = LoggerFactory.getLogger(AuthLink.class);protected SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");protected String levelUserId;      //审核人IDprotected String levelUserName;   //审核人姓名protected AuthLink next;          //持有下一个处理类的引用public AuthLink(String levelUserId, String levelUserName) {this.levelUserId = levelUserId;this.levelUserName = levelUserName;}//获取下一个处理类public AuthLink getNext() {return next;}//责任链中添加处理类public AuthLink appendNext(AuthLink next) {this.next = next;return this;}//抽象审核方法public abstract AuthInfo doAuth(String uId, String orderId, Date authDate);
}/** 一级负责人*/
public class Level1AuthLink extends AuthLink {private Date beginDate = f.parse("2020-11-11 00:00:00");private Date endDate = f.parse("2020-11-31 23:59:59");public Level1AuthLink(String levelUserId, String levelUserName) throws ParseException {super(levelUserId, levelUserName);}@Overridepublic AuthInfo doAuth(String uId, String orderId, Date authDate) {Date date = AuthService.queryAuthInfo(levelUserId, orderId);if (null == date) {return new AuthInfo("0001", "单号:", orderId, " 状态:待一级审批负责人 ", levelUserName);}AuthLink next = super.getNext();if (null == next) {return new AuthInfo("0000", "单号:", orderId, " 状态:一级审批完成", " 时间:", f.format(date), " 审批人:", levelUserName);}if (authDate.before(beginDate) || authDate.after(endDate)) {return new AuthInfo("0000", "单号:", orderId, " 状态:一级审批完成", " 时间:", f.format(date), " 审批人:", levelUserName);}return next.doAuth(uId, orderId, authDate);}
}/*** 二级负责人*/
public class Level2AuthLink extends AuthLink {private Date beginDate = f.parse("2020-11-11 00:00:00");private Date endDate = f.parse("2020-11-31 23:59:59");public Level2AuthLink(String levelUserId, String levelUserName) throws ParseException {super(levelUserId, levelUserName);}public AuthInfo doAuth(String uId, String orderId, Date authDate) {Date date = AuthService.queryAuthInfo(levelUserId, orderId);if (null == date) {return new AuthInfo("0001", "单号:", orderId, " 状态:待二级审批负责人 ", levelUserName);}AuthLink next = super.getNext();if (null == next) {return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成", " 时间:", f.format(date), " 审批人:", levelUserName);}if (authDate.before(beginDate) || authDate.after(endDate) ) {return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成", " 时间:", f.format(date), " 审批人:", levelUserName);}return next.doAuth(uId, orderId, authDate);}}/*** 三级负责人*/
public class Level3AuthLink extends AuthLink {public Level3AuthLink(String levelUserId, String levelUserName) {super(levelUserId, levelUserName);}public AuthInfo doAuth(String uId, String orderId, Date authDate) {Date date = AuthService.queryAuthInfo(levelUserId, orderId);if (null == date) {return new AuthInfo("0001", "单号:", orderId, " 状态:待三级审批负责人 ", levelUserName);}AuthLink next = super.getNext();if (null == next) {return new AuthInfo("0000", "单号:", orderId, " 状态:三级审批完成", " 时间:", f.format(date), " 审批人:", levelUserName);}return next.doAuth(uId, orderId, authDate);}}

测试

public class Client {private Logger logger = LoggerFactory.getLogger(ApiTest.class);@Testpublic void test_AuthLink() throws ParseException {AuthLink authLink = new Level3AuthLink("1000013", "王工").appendNext(new Level2AuthLink("1000012", "张经理").appendNext(new Level1AuthLink("1000011", "段总")));SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date currentDate = f.parse("2020-11-18 23:49:46");logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("研发牛马", "1000998004813441", currentDate)));// 模拟三级负责人审批AuthService.auth("1000013", "1000998004813441");logger.info("测试结果:{}", "模拟三级负责人审批,王工");logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("研发牛马", "1000998004813441", currentDate)));// 模拟二级负责人审批AuthService.auth("1000012", "1000998004813441");logger.info("测试结果:{}", "模拟二级负责人审批,张经理");logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("研发牛马", "1000998004813441", currentDate)));// 模拟一级负责人审批AuthService.auth("1000011", "1000998004813441");logger.info("测试结果:{}", "模拟一级负责人审批,段总");logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("研发牛马", "1000998004813441", currentDate)));}
}

从上面的代码结果看,我们的责任链已经生效,按照责任链的结构一层一层审批.当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。并且每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

4.4职责链模式总结

4.4.1职责链模式的优点:

降低了对象之间的耦合度

该模式降低了请求发送者和接收者的耦合度。
增强了系统的可扩展性

可以根据需要增加新的请求处理类,满足开闭原则。
增强了给对象指派职责的灵活性

当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。
责任链简化了对象之间的连接

一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
责任分担

  每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

4.4.2职责链模式的缺点:

不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

4.4.3使用场景分析

责任链模式常见的使用场景有以下几种情况。

在运行时需要动态使用多个关联对象来处理同一次请求时。比如,请假流程、员工入职流程、编译打包发布上线流程等。
不想让使用者知道具体的处理逻辑时。比如,做权限校验的登录拦截器。
需要动态更换处理对象时。比如,工单处理系统、网关 API 过滤规则系统等。
职责链模式常被用在框架开发中,用来实现框架的过滤器、拦截器功能,让框架的使用者在不修改源码的情况下,添加新的过滤拦截功能.

5.状态模式

自然界很多事物都有多种状态,而且不同状态下会具有不同的行为,这些状态在特定条件下还会发生相互转换,比如水

在软件系统中,有些对象也像水一样具有多种状态,这些状态在某些情况下能够相互转换,而且对象在不同状态下也将具有不同的行为.

状态模式(state pattern)的定义:  允许一个对象在其内部状态改变时改变它的行为. 对象看起来似乎修改了它的类.

状态模式就是用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题. 状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中(用类来表示状态) ,使得对象状态可以灵活变化.

5.1状态模式原理

状态模式结构图:

5.2状态模式实现

代码示例

/*** 抽象状态接口* @author spikeCong* @date 2022/10/17**/
public interface State {//声明抽象方法,不同具体状态类可以有不同实现void handle(Context context);
}/*** 上下文类* @author spikeCong* @date 2022/10/17**/
public class Context {private State currentState; //维持一个对状态对象的引用public Context() {this.currentState = null;}public State getCurrentState() {return currentState;}public void setCurrentState(State currentState) {this.currentState = currentState;}@Overridepublic String toString() {return "Context{" +"currentState=" + currentState +'}';}
}public class ConcreteStateA implements State {@Overridepublic void handle(Context context) {System.out.println("进入状态模式A......");context.setCurrentState(this);}@Overridepublic String toString() {return "当前状态: ConcreteStateA";}
}public class ConcreteStateB implements State{@Overridepublic void handle(Context context) {System.out.println("进入状态模式B......");context.setCurrentState(this);}@Overridepublic String toString() {return "当前状态: ConcreteStateB";}
}public class Client {public static void main(String[] args) {Context context = new Context();State state1 = new ConcreteStateA();state1.handle(context);System.out.println(context.getCurrentState().toString());System.out.println("========================");State state2 = new ConcreteStateB();state2.handle(context);System.out.println(context.getCurrentState().toString());}
}

5.2状态模式应用实例

模拟交通信号灯的状态转换. 交通信号灯一般包括了红、黄、绿3种颜色状态,不同状态之间的切换逻辑为: 红灯只能切换为黄灯,黄灯可以切换为绿灯或红灯,绿灯只能切换为黄灯.

5.2.1不使用设计模式 

/*** 交通灯类*    红灯(禁行) ,黄灯(警示),绿灯(通行) 三种状态.* @author spikeCong* @date 2022/10/17**/
public class TrafficLight {//初始状态红灯private String state = "红";//切换为绿灯(通行)状态public void switchToGreen(){if("绿".equals(state)){//当前是绿灯System.out.println("当前为绿灯状态,无需切换!");}else if("红".equals(state)){System.out.println("红灯不能切换为绿灯!");}else if("黄".equals(state)){state = "绿";System.out.println("绿灯亮起...时长: 60秒");}}//切换为黄灯(警示)状态public void switchToYellow(){if("黄".equals(state)){//当前是黄灯System.out.println("当前为黄灯状态,无需切换!");}else if("红".equals(state) || "绿".equals(state)){state = "黄";System.out.println("黄灯亮起...时长:10秒");}}//切换为黄灯(警示)状态public void switchToRed(){if("红".equals(state)){//当前是绿灯System.out.println("当前为红灯状态,无需切换!");}else if("绿".equals(state)){System.out.println("绿灯不能切换为红灯!");}else if("黄".equals(state)){state = "红";System.out.println("红灯亮起...时长: 90秒");}}
}

问题: 状态切换的操作全部在一个类中,如果有很多的交通灯进行联动,这个程序的逻辑就会变得非常复杂,难以维护.

5.2.2使用状态模式

将交通灯的切换逻辑组织起来,把跟状态有关的内容从交通灯类里抽离出来,使用类来表示不同的状态

/*** 交通灯类*    红灯(禁行) ,黄灯(警示),绿灯(通行) 三种状态.* @author spikeCong* @date 2022/10/17**/
public class TrafficLight {//初始状态红灯State state = new Red();public void setState(State state) {this.state = state;}//切换为绿灯状态public void switchToGreen(){state.switchToGreen(this);}//切换为黄灯状态public void switchToYellow(){state.switchToYellow(this);}//切换为红灯状态public void switchToRed(){state.switchToRed(this);}
}/*** 交通灯状态接口* @author spikeCong* @date 2022/10/17**/
public interface State {void switchToGreen(TrafficLight trafficLight); //切换为绿灯void switchToYellow(TrafficLight trafficLight); //切换为黄灯void switchToRed(TrafficLight trafficLight); //切换为红灯
}/*** 红灯状态类* @author spikeCong* @date 2022/10/17**/
public class Red implements State {@Overridepublic void switchToGreen(TrafficLight trafficLight) {System.out.println("红灯不能切换为绿灯!");}@Overridepublic void switchToYellow(TrafficLight trafficLight) {System.out.println("黄灯亮起...时长:10秒!");}@Overridepublic void switchToRed(TrafficLight trafficLight) {System.out.println("已是红灯状态无须再切换!");}
}/*** 绿灯状态类* @author spikeCong* @date 2022/10/17**/
public class Green implements State {@Overridepublic void switchToGreen(TrafficLight trafficLight) {System.out.println("已是绿灯无须切换!");}@Overridepublic void switchToYellow(TrafficLight trafficLight) {System.out.println("黄灯亮起...时长:10秒!");}@Overridepublic void switchToRed(TrafficLight trafficLight) {System.out.println("绿灯不能切换为红灯!");}
}/*** 黄灯状态类* @author spikeCong* @date 2022/10/17**/
public class Yellow implements State {@Overridepublic void switchToGreen(TrafficLight trafficLight) {System.out.println("绿灯亮起...时长:60秒!");}@Overridepublic void switchToYellow(TrafficLight trafficLight) {System.out.println("已是黄灯无须切换!");}@Overridepublic void switchToRed(TrafficLight trafficLight) {System.out.println("红灯亮起...时长:90秒!");}
}public class Client {public static void main(String[] args) {TrafficLight trafficLight = new TrafficLight();trafficLight.switchToYellow();trafficLight.switchToGreen();trafficLight.switchToRed();}
}

通过代码重构,将"状态" 接口化、模块化,最终将它们从臃肿的交通类中抽了出来, 消除了原来TrafficLight类中的if...else,代码看起来干净而优雅.

5.3状态模式总结

5.3.1状态模式的优点:

将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。

5.3.2状态模式的缺点:

状态模式的使用必然会增加系统类和对象的个数。
状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
状态模式对"开闭原则"的支持并不太好 (添加新的状态类需要修改那些负责状态转换的源代码)。

5.3.3状态模式常见的使用场景:

对象根据自身状态的变化来进行不同行为的操作时, 比如,购物订单状态。
对象需要根据自身变量的当前值改变行为,不期望使用大量 if-else 语句时, 比如,商品库存状态。
对于某些确定的状态和行为,不想使用重复代码时, 比如,某一个会员当天的购物浏览记录。

6.迭代器模式

迭代器模式是我们学习一个设计时很少用到的、但编码实现时却经常使用到的行为型设计模式。在绝大多数编程语言中,迭代器已经成为一个基础的类库,直接用来遍历集合对象。在平时开发中,我们更多的是直接使用它,很少会从零去实现一个迭代器。

迭代器模式(Iterator pattern)又叫游标(Cursor)模式,它的原始定义是:迭代器提供一种对容器对象中的各个元素进行访问的方法,而又不需要暴露该对象的内部细节。

在软件系统中,容器对象拥有两个职责: 一是存储数据,而是遍历数据.从依赖性上看,前者是聚合对象的基本职责.而后者是可变化的,又是可分离的.因此可以将遍历数据的行为从容器中抽取出来,封装到迭代器对象中,由迭代器来提供遍历数据的行为,这将简化聚合对象的设计,更加符合单一职责原则 

6.1迭代器模式原理

6.1.1迭代器模式主要包含以下角色:

抽象集合(Aggregate)角色:

用于存储和管理元素对象, 定义存储、添加、删除集合元素的功能,并且声明了一个createIterator()方法用于创建迭代器对象。
具体集合(ConcreteAggregate)角色:

实现抽象集合类,返回一个具体迭代器的实例。
抽象迭代器(Iterator)角色:

定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。
  hasNext()函数用于判断集合中是否还有下一个元素
  next() 函数用于将游标后移一位元素
  currentItem() 函数,用来返回当前游标指向的元素
具体迭代器(Concretelterator)角色:

实现抽象迭代器接口中所定义的方法,完成对集合对象的遍历,同时记录遍历的当前位置。

 6.2迭代器模式实现

/*** 迭代器接口* @author spikeCong* @date 2022/10/18**/
public interface Iterator<E> {//判断集合中是否有下一个元素boolean hasNext();//将游标后移一位元素void next();//返回当前游标指定的元素E currentItem();
}/*** 具体迭代器* @author spikeCong* @date 2022/10/18**/
public class ConcreteIterator<E> implements Iterator<E>{private int cursor; //游标private ArrayList<E> arrayList; //容器public ConcreteIterator(ArrayList<E> arrayList) {this.cursor = 0;this.arrayList = arrayList;}@Overridepublic boolean hasNext() {return cursor != arrayList.size();}@Overridepublic void next() {cursor++;}@Overridepublic E currentItem() {if(cursor >= arrayList.size()){throw new NoSuchElementException();}return arrayList.get(cursor);}
}public class Test01 {public static void main(String[] args) {ArrayList<String> names = new ArrayList<>();names.add("lisi");names.add("zhangsan");names.add("wangwu");Iterator<String> iterator = new ConcreteIterator(names);while(iterator.hasNext()){System.out.println(iterator.currentItem());iterator.next();}/*** 使用ArrayList集合中的iterator()方法获取迭代器* 将创建迭代器的方法放入集合容器中,这样做的好处是对客户端封装了迭代器的实现细节.*/java.util.Iterator<String> iterator1 = names.iterator();while(iterator1.hasNext()){System.out.println(iterator1.next());iterator.next();}}
}

6.3迭代器模式应用实例

/*** 抽象迭代器 IteratorIterator* @author spikeCong* @date 2022/10/18**/
public interface IteratorIterator<E> {void reset();   //重置为第一个元素E next();   //获取下一个元素E currentItem();    //检索当前元素boolean hasNext();  //判断是否还有下一个元素存在
}/*** 抽象集合 ListList* @author spikeCong* @date 2022/10/18**/
public interface ListList<E> {//获取迭代器对象的抽象方法(面向接口编程)IteratorIterator<E> Iterator();
}/*** 主题类* @author spikeCong* @date 2022/10/18**/
public class Topic {private String name;public Topic(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}/*** 具体迭代器* @author spikeCong* @date 2022/10/18**/
public class TopicIterator implements IteratorIterator<Topic> {//Topic数组private Topic[] topics;//记录存储位置private int position;public TopicIterator(Topic[] topics) {this.topics = topics;position = 0;}@Overridepublic void reset() {position = 0;}@Overridepublic Topic next() {return topics[position++];}@Overridepublic Topic currentItem() {return topics[position];}@Overridepublic boolean hasNext() {if(position >= topics.length){return false;}return true;}
}/*** 具体集合类* @author spikeCong* @date 2022/10/18**/
public class TopicList implements ListList<Topic> {private Topic[] topics;public TopicList(Topic[] topics) {this.topics = topics;}@Overridepublic IteratorIterator<Topic> Iterator() {return new TopicIterator(topics);}
}public class Client {public static void main(String[] args) {Topic[] topics = new Topic[4];topics[0] = new Topic("topic1");topics[1] = new Topic("topic2");topics[2] = new Topic("topic3");topics[3] = new Topic("topic4");TopicList topicList = new TopicList(topics);IteratorIterator<Topic> iterator = topicList.Iterator();while(iterator.hasNext()){Topic t = iterator.next();System.out.println(t.getName());}}
}

6.4迭代器模式总结

6.4.1迭代器的优点:

迭代器模式支持以不同方式遍历一个集合对象,在同一个集合对象上可以定义多种遍历方式. 在迭代器模式中只需要用一个不同的迭代器来替换原有的迭代器,即可改变遍历算法,也可以自己定义迭代器的子类以支持新的遍历方式.
迭代器简化了集合类。由于引入了迭代器,在原有的集合对象中不需要再自行提供数据遍历等方法,这样可以简化集合类的设计。
在迭代器模式中,由于引入了抽象层,增加新的集合类和迭代器类都很方便,无须修改原有代码,满足 "基于接口编程而非实现" 和 "开闭原则" 的要求。

6.4.2迭代器的缺点:

由于迭代器模式将存储数据和遍历数据的职责分离,增加了类的个数,这在一定程度上增加了系统的复杂性。
抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展.

6.4.3使用场景

减少程序中重复的遍历代码
对于放入一个集合容器中的多个对象来说,访问必然涉及遍历算法。如果我们不将遍历算法封装到容器里(比如,List、Set、Map 等),那么就需要使用容器的人自行去实现遍历算法,这样容易造成很多重复的循环和条件判断语句出现,不利于代码的复用和扩展,同时还会暴露不同容器的内部结构。而使用迭代器模式是将遍历算法作为容器对象自身的一种“属性方法”来使用,能够有效地避免写很多重复的代码,同时又不会暴露内部结构。
 当需要为遍历不同的集合结构提供一个统一的接口时或者当访问一个集合对象的内容而无须暴露其内部细节的表示时。
迭代器模式把对不同集合类的访问逻辑抽象出来,这样在不用暴露集合内部结构的情况下,可以隐藏不同集合遍历需要使用的算法,同时还能够对外提供更为简便的访问算法接口。
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/668020.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker 搭建mysql 集群(二)

PXC方案 很明显 PXC方案在任何一个节点写入的数据都会同步到其他节点&#xff0c;数据双向同步的&#xff08;在任何节点上都可以同时读写&#xff09; 创建MySQL PXC集群 1 安装PXC镜像 docker pull percona/percona-xtradb-cluster:5.7.21 2 为PXC镜像改名 docker tag pe…

如何选择旅游路线,使得假期旅游路费最少?

旅行是许多人的热爱&#xff0c;但是在规划一个完美的假期时&#xff0c;找到最经济的路线常常是一个挑战。这里就需要引入一个著名的优化问题——旅行商问题。本文将介绍TSP的基础知识&#xff0c;并使用MTZ消除子环方法优化一个简单的TSP问题的示例。 旅行商问题简介 TSP&a…

用友U8 Cloud ReportDetailDataQuery SQL注入漏洞复现(QVD-2023-47860)

0x01 产品简介 用友U8 Cloud 提供企业级云ERP整体解决方案,全面支持多组织业务协同,实现企业互联网资源连接。 U8 Cloud 亦是亚太地区成长型企业最广泛采用的云解决方案。 0x02 漏洞概述 用友U8 cloud ReportDetailDataQuery 接口处存在SQL注入漏洞,攻击者未经授权可以访…

自然语言nlp学习五

6-10 文本生成--介绍_哔哩哔哩_bilibili 在自然语言处理&#xff08;NLP, Natural Language Processing&#xff09;领域&#xff0c;“sequence”通常是指一个有序的数据集合&#xff0c;它由一系列元素按照特定顺序排列而成。这些元素可以是单词、字符、句子或其他文本单位。…

NLP_语言模型的雏形N-Gram

文章目录 N-Gram 模型1.将给定的文本分割成连续的N个词的组合(N-Gram)2.统计每个N-Gram在文本中出现的次数&#xff0c;也就是词频3.为了得到一个词在给定上下文中出现的概率&#xff0c;我们可以利用条件概率公式计算。具体来讲&#xff0c;就是计算给定前N-1个词时&#xff0…

【ROS机器人系统】实验1 熟悉ROS操作系统、熟悉ROS通信架构

文章目录 实验1 熟悉ROS操作系统、熟悉ROS通信架构1、实验目的2、实验设备3、实验内容3.1 安装ROS3.2 ROS常用指令以及小海龟示例roscore命令rosrun命令rosnode 命令roscd 命令rostopic 命令 动手实现1&#xff1a;rosservice 命令roslaunch 命令 动手实现2&#xff1a;动手实现…

免费代理IP的弊端有哪些?使用代理IP前要这样哪些事项?

随着互联网的普及&#xff0c;越来越多的人开始需要使用代理IP来保护自己的隐私或突破网络限制。然而&#xff0c;免费代理IP并非完美的解决方案&#xff0c;它们也存在一些弊端。在本文中&#xff0c;我们将探讨免费代理IP的弊端以及使用代理IP前需要注意的事项。 免费代理IP的…

如何使用第三方API采集电商数据呢?

电商商家最常唠叨的就是店铺运营难做。每日多平台店铺数据统计汇总繁琐耗时&#xff0c;人工效率偏低&#xff0c;且工作内容有限。 特别是眼下“618&#xff0c;双十一&#xff0c;双十二&#xff0c;年底大促”将至&#xff0c;如何提高运营的效率和质量、保证产品及服务的良…

算法学习——华为机考题库9(HJ56 - HJ63)

算法学习——华为机考题库9&#xff08;HJ56 - HJ63&#xff09; HJ56 完全数计算 描述 完全数&#xff08;Perfect number&#xff09;&#xff0c;又称完美数或完备数&#xff0c;是一些特殊的自然数。 它所有的真因子&#xff08;即除了自身以外的约数&#xff09;的和&…

C# CAD界面-自定义工具栏(二)

运行环境 vs2022 c# cad2016 调试成功 一、引用 acdbmgd.dllacmgd.dllaccoremgd.dllAutodesk.AutoCAD.Interop.Common.dllAutodesk.AutoCAD.Interop.dll using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.T…

【pwn】pwnable_start --只有read和write函数的getshell

首先查一下程序的保护情况 保护全关&#xff01;&#xff01;&#xff01; 然后看ida逻辑 ida的结果很简洁&#xff0c;只有一段汇编代码&#xff0c;我们再来看看nc情况 现在我们来分析一下汇编代码 mov ecx, esp ; addr .text:08048089 B2 14 …

Fink CDC数据同步(六)数据入湖Hudi

数据入湖Hudi Apache Hudi(简称&#xff1a;Hudi)使得您能在hadoop兼容的存储之上存储大量数据&#xff0c;同时它还提供两种原语&#xff0c;使得除了经典的批处理之外&#xff0c;还可以在数据湖上进行流处理。这两种原语分别是&#xff1a; Update/Delete记录&#xff1a;H…

LLaVA:GPT-4V(ision) 的新开源替代品

LLaVA&#xff1a;GPT-4V(ision) 的新开源替代品。 LLaVA &#xff08;https://llava-vl.github.io/&#xff0c;是 Large Language 和Visual A ssistant的缩写&#xff09;。它是一种很有前景的开源生成式 AI 模型&#xff0c;它复制了 OpenAI GPT-4 在与图像对话方面的一些功…

arping交叉编译

arping命令依赖libpcap和libnet&#xff0c;需要先交叉编译这两个库。 1.交叉编译libpcap 下载libpcap源文件&#xff0c;从github上克隆: git clone https://github.com/the-tcpdump-group/libpcap.git source交叉编译环境 # environment-setup是本机的交叉编译环境, 里面…

LabVIEW风力发电机在线监测

LabVIEW风力发电机在线监测 随着可再生能源的发展&#xff0c;风力发电成为越来越重要的能源形式。设计了一个基于控制器局域网&#xff08;CAN&#xff09;总线和LabVIEW的风力发电机在线监测系统&#xff0c;实现风力发电机的实时监控和故障诊断&#xff0c;以提高风力发电的…

windows安装Visual Studio Code,配置C/C++运行环境(亲测可行)

一.下载 Visual Studio Code https://code.visualstudio.com/ 二.安装 选择想要安装的位置: 后面的点击下一步即可。 三.下载编译器MinGW vscode只是写代码的工具&#xff0c;使用编译器才能编译写的C/C程序&#xff0c;将它转为可执行文件。 MinGW下载链接&#xff1a;…

Stable Diffusion 模型下载:国风3 GuoFeng3

文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十推荐提示词下载地址模型介绍 欢迎使用GuoFeng3模型 - 这是一个中国华丽古风风格模型,也可以说是一个古风游戏角色模型,具有2.5D的质感。 条目内

CDH6.3.2 多 Spark 版本共存

一 部署Spark客户端 1.1 部署spark3客户端 tar -zxvf spark-3.3.1-bin-3.0.0-cdh6.3.2.tgz -C /opt/cloudera/parcels/CDH/lib cd /opt/cloudera/parcels/CDH/lib mv spark-3.3.1-bin-3.0.0-cdh6.3.2/ spark3将 CDH 集群的 spark-env.sh 复制到 /opt/cloudera/parcels/CDH/li…

C语言函数递归例子2青蛙跳台阶问题

接下来我们来看一下第二个例子青蛙跳台阶 青蛙跳台阶问题 这个问题经常在各类面试中看到。一只青蛙一次可以跳上1级台阶&#xff0c;也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。是实践函数递归的典型问题 分析问题 我们先假设有n个台阶&#xff0c;如果n1&am…

如何构建多种系统架构支持的 Docker 镜像

如何构建多种系统架构支持的 Docker 镜像 1.概述2.解决方案3.使用manifest案例 1.概述 我们知道使用镜像创建一个容器&#xff0c;该镜像必须与 Docker 宿主机系统架构一致&#xff0c;例如 Linux x86_64 架构的系统中只能使用 Linux x86_64 的镜像创建容器 例如我们在 Linux…