(Java)设计模式:行为型
前言
- 此篇博文内容续接的是 UML建模语言、设计原则、创建型设计模式 的内容,有兴趣的可以点前面的链接去看一下
3.2、行为型
这类设计模式是专门用于:对象间的高效沟通和职责委派
* 3.2.1、责任链模式
定义:责任链模式又名职责链模式,指的是:对某个请求的所有处理构成一条链,如果链上的某一处理者可以处理,则处理后返回。如果不能处理则将请求传递给链上的下一个处理者
废话文学:所谓责任链模式就是为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止
场景理解:开发中的捕获异常,只有遇到对应的某一个 或 某一类异常时,才会有对应的处理机制;还有Servlet编程中的Filter过滤器;以及SpringMVC执行原理(请求经过DispatcherServlet,然后经其转到HandletMapping,然后通过HandlerExcuttionChain这条执行链将结果返回给DispatcherServlet…..,)
图示理解:使用的是Servlet的Filter举例
生活中的例子:跳槽离职,要找好多人审批、以及击鼓传花…..
使用场景:在处理消息的时候要过滤很多道时就可以使用责任链模式
责任链模式的角色
- 抽象处理者:一个请求接口,里面包含抽象的处理请求的方法 和 后继链接
- 具体处理者:抽象处理者的子类,实现了处理请求方法,判断请求是否能够处理,能则处理返回结果,否则将请求传给它的后继者
- 客户端:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程
责任链模式的类图
3.2.1.1、简单逻辑
1、抽象处理者
package com.zixieqing.handler;
/**
* <p>@description : 该类功能 抽象处理者:做用户名判断
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public abstract class AbstractHakimHandler {
private AbstractHakimHandler next;
public AbstractHakimHandler getNext() {
return next;
}
public void setNext(AbstractHakimHandler next) {
this.next = next;
}
public abstract String hakim(String name);
}
2、具体处理者
-
package com.zixieqing.handler; import com.zixieqing.UsernameEnum; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <p>@description : 该类功能 第一个处理者 * </p> * <p>@package : com.zixieqing</p> * <p>@author : ZiXieqing</p> * <p>@version : V1.0.0</p> */ public class OneHakimHandler extends AbstractHakimHandler{ private Logger logger = LoggerFactory.getLogger(OneHakimHandler.class); @Override public String hakim(String name) { logger.info("进入OneHakimHandler处理器"); // 如果当前处理者能处理该请求,则进行处理,返回结果 if (UsernameEnum.ZIXIEQING.toString().equals(name.trim().toUpperCase())) { return "欢迎:" + name + " 进入系统"; } // 如果当前处理者不能处理改请求,则丢给后继者 if (null != getNext()) return getNext().hakim(name); return "没有处理者能够处理该请求"; } }
-
package com.zixieqing.handler; import com.zixieqing.UsernameEnum; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <p>@description : 该类功能 第二个处理者 * </p> * <p>@package : com.zixieqing.handler</p> * <p>@author : ZiXieqing</p> * <p>@version : V1.0.0</p> */ public class TwoHakimHandler extends AbstractHakimHandler{ private Logger logger = LoggerFactory.getLogger(OneHakimHandler.class); @Override public String hakim(String name) { logger.info("进入TwoHakimHandler处理器"); if (UsernameEnum.ZIMINGXUAN.toString().equals(name.trim().toUpperCase())) { logger.info("正在进行数据查询.........."); logger.info("查询到用户:{}", name); return "欢迎:" + name + " 进入系统"; } if (null != getNext()) return getNext().hakim(name); return "没有处理者能够处理该请求"; } }
-
上述涉及到的枚举类
-
package com.zixieqing; /** * <p>@description : 该类功能 用户名集合 * </p> * <p>@package : com.zixieqing</p> * <p>@author : ZiXieqing</p> * <p>@version : V1.0.0</p> */ public enum UsernameEnum { /** * 紫邪情 */ ZIXIEQING, /** * 紫明轩 */ ZIMINGXUAN, ; }
-
3、客户端
package com.zixieqing;
import com.zixieqing.handler.OneHakimHandler;
import com.zixieqing.handler.TwoHakimHandler;
/**
* <p>@description : 该类功能 客户端:这部分的代码可以封装起来,然后提供一个公共方法给外部调用即可
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class APITest{
public static void main(String[] args) {
// 创建处理链 并 将请求丢给链头的处理者
OneHakimHandler handler1 = new OneHakimHandler();
handler1.setNext(new TwoHakimHandler());
System.out.println(handler1.hakim("LISI"));
System.out.println("============华丽的分隔符=============");
System.out.println(handler1.hakim("ZIXIEQING"));
System.out.println("============华丽的分隔符=============");
System.out.println(handler1.hakim("zimingxuan"));
}
}
责任链模式的不足
- 不能保证每个请求一定能被处理,如:上面的“LISI”,原因:因为请求没有明确的接收者,所以一个请求可能一直传到处理链的末端都还未被处理
- 责任链是否合理需要靠客户端来进行创建,所以增加了客户端的复杂性,可能出现客户端中责任链的错误设置而导致系统出错
- 如果客户端中的责任链过长,那么就需要很多个处理对象的参与,所以系统性能会受到影响
* 3.2.2、命令模式
定义:指的是请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令
命令模式是将操作请求(行为请求者)和逻辑实现(行为实现者)进行了分离,从而降低耦合、方便扩展
适用场景: 只要认为是命令的地方都可以使用,如:CMD、GUI界面中的按钮…………
命令模式类图
- 场景理解:
- 电视机换台:1、想要换台(调用者:发起请求);2、按下遥控器的换台按钮(将请求包装成了命令对象);3、电视机进行换台(接收者:执行请求相应的操作)
- 去饭店吃饭:1、你要吃饭(调用者:你要菜单上的某菜,整个菜单就是所有可以调用的命令);2、跟服务员讲你要菜单上的什么菜(命令对象:服务员在本子上记下菜名,即:将请求转成了命令对象);3、服务员将写有菜名的纸交给厨师(接收者:厨师通过命令对象接收到调用者的请求,开始对应的操作:炒菜)
3.2.2.1、简单逻辑
使用上面去饭店吃饭为例,对这个场景进行拆解,可以得到如下的逻辑图
- 服务员:调用者
- 菜单:命令
- 厨师:接收者
- 逻辑顺序是:调用者(invoker)——> 命令(command) ——> 接收者(receiver);而我写代码的习惯顺序是逆向的
1、先决条件
- 菜组成
package com.zixieqing;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.util.List;
/**
* <p>@description : 该类功能 菜
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
@Data
@ToString
@Accessors(chain = true)
public class Food {
/**
* 菜名
*/
private String name;
/**
* 油:菜油、猪油、地沟油(^_^)......
*/
private String oil;
/**
* 辣椒类型:有线辣椒、小米椒、青椒、朝天椒..........
*/
private String chili;
/**
* 配料:食盐、味精、八角、糖、酱油、耗油、醋、料酒、姜、葱、蒜.........
*/
private List<String> excipients;
/**
* 容器:碗、锅、盘子
*/
private String container;
}
- 菜单枚举类
package com.zixieqing;
/**
* <p>@description : 该类功能 菜单枚举类:就是去点菜时需要看有哪些菜的菜单
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public enum MenuEnum {
/**
* 鱼香肉丝
*/
YU_XIANG_ROU_SI("鱼香肉丝", "18"),
/**
* 鸳鸯锅
*/
YUAN_YANG_GUO("鸳鸯锅", "50"),
;
private String foodName;
private String price;
MenuEnum(String foodName, String price) {
this.foodName = foodName;
this.price = price;
}
public String getFoodName() {
return foodName;
}
public void setFoodName(String foodName) {
this.foodName = foodName;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
}
2、接收者:厨师
package com.zixieqing.command;
import com.zixieqing.Food;
import java.util.List;
/**
* <p>@description : 该类功能 厨师
* </p>
* <p>@package : com.zixieqing.command</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface IChef {
Food cook(List<String> foodList);
}
- 张师:专做火锅系列
package com.zixieqing.command.impl;
import com.zixieqing.Food;
import com.zixieqing.MenuEnum;
import com.zixieqing.command.IChef;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 该类功能 张师:假如他是专门做火锅系列的
* </p>
* <p>@package : com.zixieqing.command.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ZhangChefImpl implements IChef {
private Logger logger = LoggerFactory.getLogger(ZhangChefImpl.class);
@Override
public Food cook(List<String> foodList) {
for (String name : foodList) {
if (MenuEnum.YUAN_YANG_GUO.getFoodName().equals(name)) {
logger.info("张师正在准备做:{}", name);
List<String> excipients = new ArrayList<>();
excipients.add("食盐半勺");
excipients.add("味精少量");
excipients.add("八角微量");
excipients.add("糖少许");
excipients.add("酱油半勺");
excipients.add("耗油微量");
excipients.add("葱姜蒜适量");
Food food = new Food();
food.setName(name)
.setOil("菜油")
.setChili("朝天椒")
.setExcipients(excipients)
.setContainer("鸳鸯锅");
logger.info("张师做好了:{},成品为:{}", name, food);
return food;
}
}
return null;
}
}
- 李师:专做炒菜系列
package com.zixieqing.command.impl;
import com.zixieqing.Food;
import com.zixieqing.MenuEnum;
import com.zixieqing.command.IChef;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 该类功能 李师:假如他是专门做炒菜系列的
* </p>
* <p>@package : com.zixieqing.command.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class LiChefImpl implements IChef {
private Logger logger = LoggerFactory.getLogger(LiChefImpl.class);
@Override
public Food cook(List<String> foodList) {
for (String name : foodList) {
if (MenuEnum.YU_XIANG_ROU_SI.getFoodName().equals(name)) {
logger.info("李师正在准备做:{}", name);
List<String> excipients = new ArrayList<>();
excipients.add("食盐少量");
excipients.add("味精少许");
excipients.add("葱蒜少许");
Food food = new Food();
food.setName(name)
.setOil("猪油")
.setChili("青椒")
.setExcipients(excipients)
.setContainer("盘子");
logger.info("李师做好了:{},成品为:{}", name, food);
return food;
}
}
return null;
}
}
3、命令:菜单
package com.zixieqing.command;
import java.util.List;
/**
* <p>@description : 该类功能 菜单:就是服务员在纸上写的那些菜名,即:命令对象
* </p>
* <p>@package : com.zixieqing.command</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface IMenu {
void make(List<String> foodList);
}
- 火锅类型菜单
package com.zixieqing.command.impl;
import com.zixieqing.command.IChef;
import com.zixieqing.command.IMenu;
import java.util.List;
/**
* <p>@description : 该类功能 火锅类型的菜单
* </p>
* <p>@package : com.zixieqing.command.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class HuoGuoMenuImpl implements IMenu {
private IChef chef;
public HuoGuoMenuImpl(IChef chef) {
this.chef = chef;
}
@Override
public void make(List<String> foodList) {
chef.cook(foodList);
}
}
- 炒菜类型菜单
package com.zixieqing.command.impl;
import com.zixieqing.command.IChef;
import com.zixieqing.command.IMenu;
import java.util.List;
/**
* <p>@description : 该类功能 炒菜类型的菜单
* </p>
* <p>@package : com.zixieqing.command.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ChaoCaiMenuImpl implements IMenu {
private IChef chef;
public ChaoCaiMenuImpl(IChef chef) {
this.chef = chef;
}
@Override
public void make(List<String> foodList) {
chef.cook(foodList);
}
}
4、调用者:服务员
package com.zixieqing.command;
import com.zixieqing.MenuEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 该类功能 服务员
* </p>
* <p>@package : com.zixieqing.command</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Waiter {
private Logger logger = LoggerFactory.getLogger(Waiter.class);
/**
* 服务员本子上记的菜名
*/
private List<String> foodList = new ArrayList<>();
/**
* 本店菜单
*/
private MenuEnum[] values = MenuEnum.values();
private IMenu menu;
public Waiter(IMenu menu) {
this.menu = menu;
}
/**
* <p>@description : 该方法功能 点单
* </p>
* <p>@methodName : order</p>
* <p>@author: ZiXieqing</p>
* <p>@version: V1.0.0</p>
* @param foodName 菜名
*/
public void order(String foodName) {
for (MenuEnum value : values) {
// 判断客人所说的菜名是否在本店的菜单中
if (foodName.trim().equals(value.getFoodName()))
this.foodList.add(foodName);
}
}
/**
* <p>@description : 该方法功能 下单
* </p>
* <p>@methodName : placeOrder</p>
* <p>@author: ZiXieqing</p>
* <p>@version: V1.0.0</p>
*
*/
public void placeOrder() {
menu.make(foodList);
foodList.clear();
}
}
5、测试
package com.zixieqing;
import com.zixieqing.command.Waiter;
import com.zixieqing.command.impl.ChaoCaiMenuImpl;
import com.zixieqing.command.impl.HuoGuoMenuImpl;
import com.zixieqing.command.impl.LiChefImpl;
import com.zixieqing.command.impl.ZhangChefImpl;
/**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class APITest {
public static void main(String[] args) {
Waiter waiter = new Waiter(new HuoGuoMenuImpl(new ZhangChefImpl()));
// 点单
waiter.order("鸳鸯锅");
// 下单
waiter.placeOrder();
System.out.println("==========华丽的分隔符==========");
Waiter newWaiter = new Waiter(new ChaoCaiMenuImpl(new LiChefImpl()));
// 点单
newWaiter.order("鱼香肉丝");
// 下单
newWaiter.placeOrder();
}
}
- 结果
11:43:54.317 [main] INFO c.z.command.impl.ZhangChefImpl - 张师正在准备做:鸳鸯锅
11:43:54.320 [main] INFO c.z.command.impl.ZhangChefImpl - 张师做好了:鸳鸯锅,成品为:Food(name=鸳鸯锅, oil=菜油, chili=朝天椒, excipients=[食盐半勺, 味精少量, 八角微量, 糖少许, 酱油半勺, 耗油微量, 葱姜蒜适量], container=鸳鸯锅)
==========华丽的分隔符==========
11:43:54.320 [main] INFO c.zixieqing.command.impl.LiChefImpl - 李师正在准备做:鱼香肉丝
11:43:54.320 [main] INFO c.zixieqing.command.impl.LiChefImpl - 李师做好了:鱼香肉丝,成品为:Food(name=鱼香肉丝, oil=猪油, chili=青椒, excipients=[食盐少量, 味精少许, 葱蒜少许], container=盘子)
命令模式的优点:
- 将发出命令的责任和执行命令的责任分隔开(请求者 / 调用者 和 执行者 / 接收者没有必然联系),请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的,所以降低了请求发出者和请求执行者之间的耦合,当然也方便扩展(Command命令采用的是接口+实现类 或是 抽象类+实现类)
命令模式的缺点:
- 和简单工厂模式差不多,要是命令Command很多的话,那么实现类就会变多,造成类臃肿,比较繁琐
* 3.2.3、迭代器模式
定义:迭代、迭代,就是遍历呗,所以指的就是:提供一种顺序访问集合容器 / 复杂对象中各个元素的方法,又无须暴露集合容器的内部表示
本质:就是将一个集合容器的遍历方法抽取出来,然后单独弄到一个迭代器对象中,从而:做到让我们以相同的方式,可以遍历不同数据结构的集合容器(这也是这个模式的应用场景)
典型例子:List、Set系列的集合,他们都继承了
Iterable
接口,都有Iterator
类型的iterator(
)方法,而hasNext()
和next()
方法就在Iterator
接口中
迭代器模式的角色
- 抽象迭代器(Iterator):负责定义访问和遍历元素的接口,通常包含
hasNext()
、next()
等方法 - 具体迭代器(Concretelterator):实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置
- 抽象容器(Aggregate):负责定义创建具体迭代器的接口、以及所谓的对集合容器 / 复杂对象的增删改这些方法
- 具体容器(ConcreteAggregate):抽象容器的子类,负责创建具体迭代器 / 返回迭代器实例
迭代器模式的逻辑
- 从上面那个list的构成其实也知道了
3.2.3.1、简单逻辑
- 注:下面这个逻辑没多大意思,就意思意思而已,只是为了理解逻辑,因为搞的就是套娃
1、抽象迭代器
package com.zixieqing.o1simple;
/**
* <p>@description : 该类功能 抽象迭代器
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface Iterator<E> {
boolean hasNext();
E next();
}
2、具体迭代器
package com.zixieqing.o1simple.impl;
import com.zixieqing.o1simple.Iterator;
import java.util.List;
/**
* <p>@description : 该类功能 具体迭代器
* </p>
* <p>@package : com.zixieqing.o1simple.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class IteratorImpl<E> implements Iterator<E> {
/**
* 下一个元素的索引
*/
private int cursor;
/**
* 最后一个元素的索引,如果没有就返回-1
*/
private int lastRet = -1;
private List<E> list;
public IteratorImpl(List<E> list) {
this.list = list;
}
@Override
public boolean hasNext() {
return this.cursor < list.size();
}
@Override
public E next() {
return this.list.get(cursor++);
}
}
3、抽象容器
package com.zixieqing.o1simple;
/**
* <p>@description : 该类功能 抽象容器
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface ICollection<E> {
Iterator<E> iterator();
boolean add(E element);
}
4、具体容器
package com.zixieqing.o1simple;
import com.zixieqing.o1simple.impl.IteratorImpl;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 该类功能 具体容器
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class NewList<E> implements ICollection<E>{
private List<E> list = new ArrayList<>();
@Override
public Iterator<E> iterator() {
return new IteratorImpl<>(list);
}
@Override
public boolean add(E element) {
return list.add(element);
}
}
5、测试
package com.zixieqing;
import com.zixieqing.o1simple.Iterator;
import com.zixieqing.o1simple.NewList;
/**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) {
NewList<Integer> newList = new NewList<>();
newList.add(1);
newList.add(2);
newList.add(3);
newList.add(4);
newList.add(5);
Iterator<Integer> iterator = newList.iterator();
while (iterator.hasNext())
System.out.println(iterator.next());
}
}
迭代器模式的优点:
- 在不需要暴露聚合对象的内部结构的情况下,可以让我们以相同的方式遍历不同数据结构(链表、数组、树…….)的聚合对象 / 集合容器的各个元素
迭代器模式的缺点:
- 它的缺点和工厂方法模式的缺点是类似的,因为迭代器模式是将容器存储数据和遍历数据进行了分离,所以就会造成:新增聚合对象就可能需要新增迭代器类(主要为具体迭代器)
使用场景:
- 想要遍历不同的聚合结构,这时提供一个统一的接口(迭代器)
- 想要为聚合对象提供多种遍历方式
* 3.2.4、中介者模式
定义:我拿你的钱,办你的事儿;所谓的中介者就是一个和事佬
处理的问题:对象之间存在的依赖关系(多个类相互耦合,形成了网状结构),即:一个类的方法中用到了另一个的对象(可以是把A类对象当做B类方法的参数;可以是B类方法中new了A类的实例;也可以是B类中的属性引用了A类对象[但:没有new,即:聚合],然后在B类方法中new出实例。如果有很多个类之间存在依赖关系,那么就会造成只要动一个类,则很多类都要进行整改,这就是所谓的牵一发而动全身,典型例子就是如下的关系:
中介者模式的角色
- 抽象中介者(Mediator):定义统一的接口,用于各同事角色之间的通信
- 具体中介者(ConcreteMediator):通过协调各同事对象实现协作行为,因此它必须依赖于各个同事角色
- 同事类(Colleague):每一个同事角色都知道中介者角色,而且与其他的同事角色通信的时候,一定要通过中介者角色协作。每个同事类的行为分为两种:
- 一种是同事本身的行为,比如改变对象本身的状态,处理自己的行为等,这种行为叫做自发行为,与其他的同事类或中介者没有任何的依赖
- 第二种是必须依赖中介者才能完成的行为,叫做依赖方法
- 当然:根据需要也可以把同事类进行抽取,进而变成:抽象同事类+具体同事类
3.2.4.1、简单逻辑
情景:男女分手+中间调和者,类图逻辑如下:
1、抽象中介者 / 抽象协调者
package com.zixieqing.o1simple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 该类功能 抽象协调者
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public abstract class Coordinator {
private Logger logger = LoggerFactory.getLogger(Coordinator.class);
protected Man man;
protected Woman woman;
public Coordinator() {
man = new Man(this);
woman = new Woman(this);
}
/**
* 中介者 / 协调者的核心方法:处理同事角色关系
*
* @param type 事件类型 1、传话;2、退还物品
* @param thing 发生对应类型时要做的事
*/
public abstract void handler(int type, String thing);
/**
* 传话
*
* @param context 传话内容
*/
public abstract void message(String context);
/**
* 退还物品
*
* @param thing 要退还的东西
*/
public abstract void handover(String thing);
}
2、具体中介者
package com.zixieqing.o1simple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 该类功能 具体协调者
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ConsreteConnrdinator extends Coordinator {
private Logger logger = LoggerFactory.getLogger(ConsreteConnrdinator.class);
/**
* 代表吵架
*/
private static final int QUARREL = 1;
/**
* 代表退还物品
*/
private static final int HANDOVER = 2;
/**
* MAG_FLAG 消息标识 1、给女传话;2、给男传话
* HANDOVER_FLAG 退还物品标识 1、给女退还物品;2、给男退还物品
*/
public static int MSG_FLAG = 1, HANDOVER_FLAG = 1;
/**
* 中介者 / 协调者的核心方法:处理同事角色关系
* @param type 事件类型 1、传话;2、退还物品
* @param thing 发生对应类型时要做的事
*/
@Override
public void handler(int type, String thing) {
switch (type) {
case QUARREL:
this.message(thing);
break;
case HANDOVER:
this.handover(thing);
break;
default:
logger.info("贫道爱莫能助,只能摆烂..........");
}
}
/**
* 传话
* @param context 传话内容
*/
@Override
public void message(String context) {
if (1==MSG_FLAG)
super.woman.talkToFriend(context);
if (2==MSG_FLAG)
super.man.love(context);
}
/**
* 退还物品
*
* @param thing 要退还的东西
*/
@Override
public void handover(String thing) {
if (1==HANDOVER_FLAG)
super.woman.talkToFriend(thing);
if (2==HANDOVER_FLAG)
super.man.love(thing);
}
}
3、同事类:男人
package com.zixieqing.o1simple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Scanner;
/**
* <p>@description : 该类功能 男人:同事类A
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Man {
private Logger logger = LoggerFactory.getLogger(Man.class);
/**
* 聚合协调者
*/
private Coordinator coordinator;
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
/**
* 敏感词
*/
private String filter = "不听,不听,分手";
public Man(Coordinator coordinator) {
this.coordinator = coordinator;
}
/**
* 吃
* @param foodName 食物名
*/
public void eat(String foodName) {
logger.info("这个男人今天吃了:{}", foodName);
}
/**
* 喝
* @param drinkName 饮料名
*/
public void drink(String drinkName) {
logger.info("这个男人刚刚喝了:{}", drinkName);
}
/**
* 做饭
* @param foodName 菜名
*/
public void cook(String foodName) {
logger.info("这个男人正在做:{}", foodName);
}
/**
* 谈恋爱
* @param thing 做的是恋爱中的什么事
*/
public void love(String thing) {
logger.info("女:{}", thing);
if (thing.trim().equals(filter)) {
ConsreteConnrdinator.MSG_FLAG = 1;
coordinator.handler(1,"你这娘们儿简直不可理喻,分就分");
}
ConsreteConnrdinator.MSG_FLAG = 1;
Scanner input = new Scanner(System.in);
coordinator.handler(1,input.next());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
4、同事类:女人
package com.zixieqing.o1simple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Scanner;
/**
* <p>@description : 该类功能 女人:同事类B
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Woman {
private Logger logger = LoggerFactory.getLogger(Woman.class);
private Coordinator coordinator;
private String name;
private int age;
/**
* 火药桶
*/
private String gunpowder = "你这娘们儿简直不可理喻,分就分";
public Woman(Coordinator coordinator) {
this.coordinator = coordinator;
}
/**
* 逛街购物
*/
public void shopping() {
logger.info("这个仙女今天去哪里逛街,又买了xxxxxxxxxxx");
}
/**
* 耍朋友
*/
public void talkToFriend(String context) {
logger.info("男:{}",context);
// 如果触碰火药桶,那就退还物品
if (context.trim().equals(gunpowder)) {
logger.info("分就分,把他的破东西拿回去..........");
ConsreteConnrdinator.HANDOVER_FLAG = 2;
ConsreteConnrdinator.MSG_FLAG = 2;
Scanner input = new Scanner(System.in);
String goods = input.next();
// 退还物品
coordinator.handler(2,goods);
}
// 否则就是继续交流
ConsreteConnrdinator.MSG_FLAG = 2;
Scanner input = new Scanner(System.in);
coordinator.handler(1,input.next());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
5、测试
package com.zixieqing;
import com.zixieqing.o1simple.ConsreteConnrdinator;
import com.zixieqing.o1simple.Coordinator;
import com.zixieqing.o1simple.Man;
import com.zixieqing.o1simple.Woman;
/**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) {
Coordinator connrdinator = new ConsreteConnrdinator();
Woman woman = new Woman(connrdinator);
// 同事类的独有方法
woman.shopping();
Man man = new Man(connrdinator);
// 同事类独有方法
man.eat("闭门羹");
man.drink("寂寞");
man.cook("空气");
// 提供给外部,进行协调的方法
woman.talkToFriend("");
// 提供给外部,进行协调的方法
man.love("");
}
}
自娱自乐的聊天室就来了
当然:中介者被称为协调者,那也得协调协调,做点事情嘛,不能只当个传话筒,这个就根据开发场景自由发挥了
同时:上面的同事类是可以抽离成一个接口,变成抽象同事类+实现类
分析一丢丢中介者模式:
- 1、每个同事类对象只与中介者产生依赖关系,同事类彼此之间不产生依赖
- 2、具体协调者中加入了用private修饰的用于处理各同事类关系的方法,从而让一个对象依赖多个对象的情况移到这个中介者中来进行处理了
- 3、同事类中都加入了中介者,从而让各个同事类都具有中介者的特性,这样做之后:各个同事类就可以只负责自己的行为(独有方法),而不需要自己负责的 / 需要协调的就丢给中介者进行处理(这样做也取消了各个同事类之间产生依赖或关联关系)
- 同事类只负责自己的行为,不需要负责的就丢给中介者处理,这样好处就是:各个同事类之间可以不用知道彼此的存在,它只需要和中介者打交道即可
- 同事类只负责自己的行为,需要协调的就丢给中介者进行处理,这样可能会造成这个模式滥用的情况(慎用),因为这样做一不注意就可能导致:同事类之间知道彼此的存在了,即:各同事类之间一不注意就产生关联或依赖关系,那这模式架构就当白做了(别被我上面弄的男女耍朋友给搞混了,谁跟你说他们知道彼此存在的,我没说,万一这里面有故事呢)
当然上面这些都可以说是中介者模式的优点,相应地就有缺点
- 原本是各个同事类之间产生依赖或关联关系,现在是变成同事类和中介者之间产生依赖关系,这样就造成:同事类越多,那么中介者就越肿大(需要聚合的同事类就变多,核心方法[事件方法]中需要处理的逻辑就越复杂),这个缺点也是决定到底要不要用中介者模式的核心,否则就成为乱整,要用这个模式就一定要考虑是否会让中介者变得肿大的问题
3.2.5、备忘录模式
定义:备忘录模式又称为快照模式,备忘、备忘嘛,指的就是在不破坏封装性的前提下,获取到一个对象的内部状态,并在对象之外记录或保存这个状态,在有需要的时候可将该对象恢复到原先保存的状态。使用备忘录模式可以对操作对象提供回滚操作,但对资源消耗过大,每操作一次都需要记录操作前数据
人话:就是为了防丢失、撤销、恢复等事情的(即:需要记录一个对象的内部状态,目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先的状态,有 “后悔药” 可吃,这也是这个模式的适用场景)
注意上面的官方话定义:
获取对象内部状态(内部还有什么状态,就属性值呗),并在对象之外保存这个状态(即:在要保存的对象中会有一个save方法用来把其当前属性值保存到另一个对象[即:备忘录对象]中
在有需要时可将对象恢复到原先保存的状态:也就是说在要保存对象中还会有一个提供和撤销 / 回滚类似的方法,从而让对象回到上一步的状态
备忘录模式的角色
-
备忘录(Memento):负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人
-
发起人(Originator):需要备忘的对象,记录当前时刻的内部状态信息,提供将当前时刻的内部状态状态信息保存到备忘录中,并将备忘录对象返回 以及 从备忘录中记录的状态信息恢复此对象状态的方法
-
负责人(Caretaker):用于管理备忘录,提供保存与获取备忘录对象的功能(getter、setter),但其不能直接对备忘录中的内容进行操作(要操作备忘录中的内容只能找备忘录本身才可以)
备忘录模式逻辑关系草图
3.4.5.1、简单逻辑
1、备忘录:存储发起人的内部状态信息,并在需要时能将数据交出去
package com.zixieqing.o1simple;
/**
* <p>@description : 该类功能 备忘录:存储发起人的内部状态信息,并在需要时能够将存储的数据交出去
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Memento {
private Object state;
public Memento(Object state) {
this.state = state;
}
public Object getState() {
return state;
}
}
2、负责人:管理备忘录,提供获取、保存备忘录对象的方法,但不可对备忘录中的内容进行操作
package com.zixieqing.o1simple;
/**
* <p>@description : 该类功能 负责人:管理备忘录,提供获取、保存备忘录对象的方法,但不可以对备忘录中的内容进行操作
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
3、发起人:要进行备忘的对象,记录当前时刻的内部属性信息到备忘录中 并返回备忘录对象,同时在需要时可从备忘录中恢复当前对象的内部信息
package com.zixieqing.o1simple;
/**
* <p>@description : 该类功能 发起人:需要进行备忘的对象,记录当前时刻的内部属性信息到备忘录中,并返回备忘录对象
* 并在需要时可从备忘录中记录的状态信息恢复数据
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Orginal {
private Object field;
/**
* 保存当前时刻的状态信息到备忘录中,并返回备忘录对象
*/
public Memento saveToMemento() {
return new Memento(this.field);
}
/**
* 从备忘录中恢复当前对象的内部状态信息
* @param memento 备忘录
*/
public void rollbackFromMemento(Memento memento) {
this.field = memento.getState();
}
@Override
public String toString() {
return "Orginal{" +
"field=" + field +
'}';
}
public Object getField() {
return field;
}
public void setField(Object field) {
this.field = field;
}
}
4、测试
package com.zixieqing;
import com.zixieqing.o1simple.Memento;
import com.zixieqing.o1simple.Orginal;
/**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) {
// 要进行保存的对象
Orginal orginal = new Orginal();
orginal.setField("稀里哗啦保存了一堆的东西");
// 将对象的当前时刻的内部信息保存到备忘录中
Memento memento = orginal.saveToMemento();
System.out.println(orginal);
// 模拟
orginal.setField("噼里啪啦又是一顿操作,保存了数据");
System.out.println(orginal);
System.out.println("=============芜湖~断电咯================");
System.out.println("。。。。。。。。。。。。。。。。。。。。");
System.out.println("=============耶吼~电力恢复啦=============");
// 从记录的备忘录中恢复对象内部状态
orginal.rollbackFromMemento(memento);
System.out.println("=============对象恢复之后的状态===========");
System.out.println(orginal);
}
}
备忘录模式的缺点:
- 如果要保存的对象的内部状态信息(属性)很多的话,那内存资源消耗是很大的,所以有时可以使用单例、命令模式这些来进行数据保存,当然更可以通过第三方中间件保存
* 3.2.6、观察者模式
定义:指的是多个观察者同时监听一个主题对象,每当主题对象发生变化时,都会通知监听 / 依赖它的所有观察者,从而让观察者自动更新自身相关的数据。因此主题对象和观察者之间就是一对多的依赖关系(这定义就是它的适用场景)
核心:将观察者和被观察者进行了解耦,并通过类似于消息发送的机制让两者进行联动,从而做到被观察者发生变化时,监听 / 依赖于它的所有观察者都会得到通知并做出响应(更新自身数据)
观察者模式的角色(可以当做发布订阅模式来理解,但:这二者不能完全等价,发布订阅模式在观察者和被观察者之间再套了一层[套的这一层就是发布订阅中的事件机制 Event Channel],自行面向百度编程):
- 抽象主题(Subject):被观察的对象,是接口或抽象类。也就是发布者,包含了:增加、删除、通知观察者对象的方法
- 具体主题(ConcreteSubject):具体被观察者,是抽象主题的子类。当其内部状态发生改变时会通知所有依赖与它的观察者
- 抽象观察者(Observer):观察者模型,也就是订阅者,定义了响应通知的更新方法
- 具体观察者(ConcreteObserver):是抽象观察者的子类,在收到主题对象状态发生改变的通知后,会立刻对其进行响应,并更新自身的相关数据
观察者模式逻辑草图
3.2.6.1、简单逻辑
场景:博主与粉丝,博主发文章,粉丝收到通知
1、先决条件:文章类
package com.zixieqing.o1simple;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* <p>@description : 该类功能 文章
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
@Data
@ToString
@Accessors(chain = true)
public class Article {
/**
* 文章标题
*/
private String title;
/**
* 文章内容
*/
private String context;
/**
* 发布人编号
*/
private Long publisherId;
/**
* 发布时间
*/
private Date createTime;
/**
* 文章所属标签
*/
private String articleLabel;
}
2、博主接口:抽象主题 即发布者,包含增加、删除、通知观察者对象等方法
package com.zixieqing.o1simple;
/**
* <p>@description : 该类功能 博主接口:抽象主题 包含增加、删除、通知观察者对象的方法
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface IBlogger {
/**
* 将粉丝添加到通知列表中,能够让粉丝接受到通知
* @param fan 粉丝
* @return true / false
*/
boolean add(IFan fan);
/**
* 将粉丝拉入黑名单,不让其接收到通知
* @param fan 粉丝
* @return true / false
*/
boolean remove(IFan fan);
/**
* 通知观察者
* @param article 消息体
*/
void notice(Article article);
}
- 具体博主:具体主题
package com.zixieqing.o1simple.impl;
import com.zixieqing.o1simple.Article;
import com.zixieqing.o1simple.IBlogger;
import com.zixieqing.o1simple.IFan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 该类功能 一个叫紫邪情的厚脸皮博主
* </p>
* <p>@package : com.zixieqing.o1simple.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Zixieqing implements IBlogger {
private Logger logger = LoggerFactory.getLogger(Zixieqing.class);
/**
* 粉丝列表:一对多
*/
private List<IFan> fanList = new ArrayList<>();
@Override
public boolean add(IFan fan) {
if (!fanList.contains(fan))
return fanList.add(fan);
return false;
}
@Override
public boolean remove(IFan fan) {
if (!add(fan))
return fanList.remove(fan);
return false;
}
@Override
public void notice(Article article) {
for (IFan fan : fanList) {
fan.response(article);
}
}
}
- 注:还是补上吧,上面通知的方法,若是再加上”满足什么条件就触发通知方法”,把这句话再进行抽取一下搞到另外合适的地方去,就变成了触发机制,即:被观察者满足什么条件触发通知,从而让所依赖的所有观察者都收到通知,并更新自身相关数据。这里抽取出来的触发机制就是事件通道Event channel,抽取出来之后,这个观察者模式就可以变成发布-订阅模式了
3、粉丝接口:即订阅者 得到通知之后进行响应并改变自身和主题相关的数据
package com.zixieqing.o1simple;
/**
* <p>@description : 该类功能 粉丝接口:抽象观察者 得到通知之后,更新自身相关数据 / 做自己要做的事情
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public abstract class IFan {
public abstract boolean response(Article article);
}
- 具体粉丝Q
package com.zixieqing.o1simple.impl;
import com.zixieqing.o1simple.Article;
import com.zixieqing.o1simple.IFan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 该类功能 一个叫Q的粉丝
* </p>
* <p>@package : com.zixieqing.o1simple.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class QFan extends IFan {
private Logger logger = LoggerFactory.getLogger(QFan.class);
/**
* 粉丝感兴趣的博文列表
*/
private static List<String> interestList = new ArrayList<>();
static {
interestList.add("设计模式");
interestList.add("爱码有道");
interestList.add("Java");
}
@Override
public boolean response(Article article) {
for (String interest : interestList) {
if (article.getTitle().equals(interest)) {
logger.info("粉丝:{},接收了通知:{},并开始去做一系列和自身数据相关的事情",
this.getClass().getSimpleName(),
article);
return true;
}
}
return false;
}
}
- 具体粉丝X
package com.zixieqing.o1simple.impl;
import com.zixieqing.o1simple.Article;
import com.zixieqing.o1simple.IFan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 该类功能 一个叫X的粉丝
* </p>
* <p>@package : com.zixieqing.o1simple.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class XFan extends IFan {
private Logger logger = LoggerFactory.getLogger(XFan.class);
/**
* 粉丝感兴趣的博文列表
*/
private static List<String> interestList = new ArrayList<>();
static {
interestList.add("消息中间件");
interestList.add("爱码有道");
interestList.add("分布式事务");
interestList.add("分布式事务");
}
@Override
public boolean response(Article article) {
for (String interest : interestList) {
if (article.getTitle().equals(interest)) {
logger.info("粉丝:{},接收了通知:{},并开始去做一系列和自身数据相关的事情",
this.getClass().getSimpleName(),
article);
return true;
}
}
return false;
}
}
4、测试
package com.zixieqing;
import com.zixieqing.o1simple.Article;
import com.zixieqing.o1simple.IBlogger;
import com.zixieqing.o1simple.impl.QFan;
import com.zixieqing.o1simple.impl.XFan;
import com.zixieqing.o1simple.impl.Zixieqing;
import java.util.Date;
/**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) {
IBlogger blogger = new Zixieqing();
// 让指定粉丝能收到通知
QFan qFan = new QFan();
XFan xFan = new XFan();
blogger.add(qFan);
blogger.add(xFan);
// 博主开始写文章
Article article = new Article();
String context = "设计莫斯一共有23种,分为创建型、行为型、结构型,这三类其实就是不同层别的架构," +
"如:创建型就是专门用于new实例的架构;即:对象,而行为型体现在行为上,也就是所谓的方法," +
"变来变去其实本质就是对类中方法的架构" +
"学完全部设计模式之后,感受就会起来了,然后只需要知晓每个设计模式解决的场景是什么," +
"最后开发中遇到对应的设计模式场景就套用,然后思路就出来,也熟练了";
article.setTitle("设计模式")
.setPublisherId(System.nanoTime())
.setContext(context)
.setArticleLabel("设计")
.setCreateTime(new Date());
// 将文章推给粉丝
blogger.notice(article);
System.out.println("=============华丽的分隔符===============");
// blogger.remove(xFan);
article.setTitle("爱码有道")
.setPublisherId(System.nanoTime())
.setContext("正式开发时还是遵循开发规范比较好")
.setArticleLabel("代码整洁")
.setCreateTime(new Date());
// 将文章推给粉丝
blogger.notice(article);
}
}
3.2.6.2、分析观察者模式
观察者模式的优点
- 被观察者(Subject主题) 和 观察者(Observer)是松耦合的,包含关系嘛,同时也是面向接口编程的(即:符合依赖倒置原则)
- 将表示层(观察者) 和 数据逻辑层(被观察者)进行了分离,只要被观察者的自身状态发生改变就可以让观察者相应的数据也发生改变,即:制定了一套触发机制,从而做到数据逻辑层的数据变化可以相应到多个表示层上
- 使用了一对多的机制,被观察者的状态发生改变,只有满足同一套触发机制的观察者才能接收到通知,这样的机制可以做很多事,比如:兴趣分发、事件注册等机制
观察者模式的缺点
- 因为观察者是被放在被观察者的一个容器中,那众多的观察者在这个容器中就产生了类似于链化的情况,而只要是容器那就逃不开遍历的事(像链表一样,或者说线性关系更恰当),这就有缺点了,若是观察者很多的话,那通知所有的观察者就需要耗费时间了
- 基于前面的线性关系的情况,还有造成一个问题:只要一个观察者没处理好,导致出现卡死的情况,那么后续的观察者也别通知了(换言之:后续观察者可能出现接收不到通知的情况),相当于后续观察者死翘翘了
* 3.2.7、状态模式
定义:一个对象在其内部状态改变时改变它的行为
解决的问题:解决复杂对象的状态转换以及不同状态下行为的封装问题,一句话:对象在不同状态下有不同行为时就可以使用状态模式
核心:将一个对象的状态从该对象中分离出来,封装到专门的状态类中(抽象状态+实现类),从而使得对象状态可以灵活变化
本质:在代码中对对象属性进行的大量
if-else
判断进行抽离(如代码:if(p.getxxxx().equals("yyyy")){}
,状态模式解决的差不多就是这种代码),在对象内部提供外部调用的方法,从而让其自身根据状态的不同去走对应的逻辑(状态实现类中的逻辑),如:一个界面登录是一种样子,未登录又是另一种样子,状态不同行为不同。这也是这个模式的适用场景,即:代码中包含大量与对象状态有关的条件语句时(if-else
和switch
)就可以使用状态模式进行改造
状态模式的角色
- 上下文(Context):状态所属者,维护具体状态类的实例,这个实例存储其当前状态
- 抽象状态(State):封装与Context的一个特定状态相关的行为操作(这里面可以定义多个方法的,这是和命令模式的一个小区别)
- 具体状态(ConcreteState):每一个具体的状态类实现一个与Context的一个状态相关的行为,在需要时也可以进行状态切换
状态模式的逻辑草图
3.2.7.1、简单逻辑
场景:去ATM中取款
1、抽象状态:ATM状态对应的行为封装
package com.zixieqing;
/**
* <p>@description : 该类功能 抽象状态:ATM状态对应的行为
* 封装与Context状态所属者的状态 / 属性相关的行为
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface ATMState {
/**
* 插卡
*/
void insertCard();
/**
* 提交密码
*/
void submitPwd();
/**
* 取款
*/
void getCash();
/**
* 查询余额
*/
void queryBalance();
/**
* 退卡
*/
void checkOut();
}
2、具体状态
- 准备状态
package com.zixieqing.impl;
import com.zixieqing.ATMContext;
import com.zixieqing.ATMState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 该类功能 具体状态:准备状态
* 具体状态:1、实现Context的一个状态的相关行为逻辑
* 2、在需要时进行状态切换
* 状态切换可以是本状态中的不同行为切换,也可以是切换到不同具体状态者
* </p>
* <p>@package : com.zixieqing.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ReadyState implements ATMState {
private Logger logger = LoggerFactory.getLogger(ReadyState.class);
/**
* 依赖:保留状态所属者Context的引用,作用:方便对其进行操作
* 接口ATMState 是对状态进行了抽象
* 实现类(ReadyState或其他实现类)是具体的状态
* 这些具体状态者(实现类)都只是状态本身而已,而状态所属者是Context,最后状态是要回到所属者身边的
* 如果这个具体状态者中没有 对 状态所属者Context进行相应操作,那:具体状态者 和 状态所属者就是割裂 / 分开的
*/
private ATMContext atmContext;
public ReadyState(ATMContext atmContext) {
this.atmContext = atmContext;
}
@Override
public void insertCard() {
logger.info("完成插卡");
}
@Override
public void submitPwd() {
logger.info("正在进行密码校验............");
if ("123456".equals(atmContext.getPwd())) {
logger.info("密码校验成功");
}else {
logger.info("密码不正确");
// 状态切换:退卡 / 重新进入准备状态..........
checkOut();
}
}
@Override
public void getCash() {
// 如果ATM中没钱了,那就不能再提供服务
if (atmContext.getATMBalance() == 0) {
logger.info("无法使用服务,请去另外机子进行业务办理");
// 弹卡
checkOut();
/*
状态切换
注意这里:这里没有用new,而是用的atmContext的setter,这也是前面说保留atmContext引用的好处之一
好处:降低耦合(迪米特原则:最少知道原则),获取弹性
*/
atmContext.setCurrState(atmContext.getNoServiceState());
}else {
if (atmContext.getMoney() <= atmContext.getATMBalance() && atmContext.getMoney() <= atmContext.getBalance()) {
// 出钞、减少用户的账户余额
logger.info("出钞¥:{}",atmContext.getMoney());
atmContext.setBalance(atmContext.getBalance() - atmContext.getMoney());
// 减少ATM中的钞票金额
atmContext.setATMBalance(atmContext.getATMBalance() - atmContext.getMoney());
// 打印发票、回到准备状态...........
// 弹卡
checkOut();
}else {
logger.info("余额不足");
checkOut();
}
}
}
@Override
public void queryBalance() {
logger.info("账户余额为:{}", atmContext.getBalance());
}
@Override
public void checkOut() {
logger.info("退卡成功");
}
}
- 无法提供服务状态
package com.zixieqing.impl;
import com.zixieqing.ATMContext;
import com.zixieqing.ATMState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 该类功能 具体状态:停止服务
* </p>
* <p>@package : com.zixieqing.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class NoServiceState implements ATMState {
private ATMContext atmContext;
public NoServiceState(ATMContext atmContext) {
this.atmContext = atmContext;
}
private Logger logger = LoggerFactory.getLogger(NoServiceState.class);
@Override
public void insertCard() {
logger.info("服务停止,请去其他取款机进行业务办理");
}
@Override
public void submitPwd() {
logger.info("服务停止,请去其他取款机进行业务办理");
}
@Override
public void getCash() {
logger.info("服务停止,请去其他取款机进行业务办理");
}
@Override
public void queryBalance() {
logger.info("服务停止,请去其他取款机进行业务办理");
}
@Override
public void checkOut() {
logger.info("服务停止,请去其他取款机进行业务办理");
}
}
3、核心:Context上下文对象
package com.zixieqing;
import com.zixieqing.impl.NoServiceState;
import com.zixieqing.impl.ReadyState;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
/**
* <p>@description : 该类功能 Context上下文对象(这个Context是核心)
* 状态所属者:1、维护具体状态者实例;
* 2、也可以有它自己的特有状态;
* 3、将状态改变时其对应的行为交给对应的状态对象,即:状态改变时让其走对应的逻辑(供外部调用的)
* 上面2、3根据情况决定可有可无
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
@Data
@ToString
@Accessors(chain = true)
public class ATMContext {
// 假设此Context对象有如下的独有状态(实质是:老衲待会儿测试需要,一次性写在这里了)
/**
* 密码
*/
private String pwd;
/**
* 用户取款金额
*/
private int money;
/**
* 用户的账户余额(假设为int类型)
*/
private int balance;
/**
* ATM机中的钞票余额
*/
private int ATMBalance;
// 下面这些就是这个Context所维护的具体状态者实例
/**
* 当前状态
*/
private ATMState currState;
/**
* 准备状态
*/
private ATMState readyState;
/**
* 无服务状态
*/
private ATMState noServiceState;
/**
* 初始化所有状态
* @param pwd 密码
* @param money 用户取款金额
* @param balance 用户的账户余额
* @param ATMBalance ATM机中的钞票余额
*/
public ATMContext(String pwd, int money, int balance, int ATMBalance) throws Exception {
this.pwd = pwd;
this.money = money;
this.balance = balance;
this.ATMBalance = ATMBalance;
this.readyState = new ReadyState(this);
this.noServiceState = new NoServiceState(this);
if (this.getATMBalance() > 0) {
this.currState = readyState;
} else if (this.getATMBalance() < 0) {
this.currState = noServiceState;
}else {
throw new Exception();
}
}
// 下面这些就是这个Context在其状态改变时将具体行为委托给具体状态对象(可以直接供外部调用)
/**
* 转换到插卡行为
*/
public void insertCard() {
this.currState.insertCard();
}
/**
* 提交密码
*/
public void submitPwd() {
this.currState.submitPwd();
}
/**
* 取款
*/
public void getCash() {
this.currState.getCash();
}
/**
* 查询余额
*/
public void queryBalance() {
this.currState.queryBalance();
}
public void checkOut() {
this.currState.checkOut();
}
}
4、测试
package com.zixieqing;
/**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) throws Exception {
ATMContext atmContext = new ATMContext("123456", 300, 200000, 100000);
System.out.println("===============初始状态===================");
System.out.println(atmContext);
atmContext.insertCard();
atmContext.submitPwd();
atmContext.getCash();
atmContext.queryBalance();
atmContext.checkOut();
System.out.println("===============结束状态===================");
System.out.println(atmContext);
}
}
- 结果
===============初始状态===================
ATMContext(pwd=123456, money=300, balance=200000, ATMBalance=100000, currState=com.zixieqing.impl.ReadyState@5d6f64b1, readyState=com.zixieqing.impl.ReadyState@5d6f64b1, noServiceState=com.zixieqing.impl.NoServiceState@32a1bec0)
17:50:01.410 [main] INFO com.zixieqing.impl.ReadyState - 完成插卡
17:50:01.413 [main] INFO com.zixieqing.impl.ReadyState - 正在进行密码校验............
17:50:01.413 [main] INFO com.zixieqing.impl.ReadyState - 密码校验成功
17:50:01.413 [main] INFO com.zixieqing.impl.ReadyState - 出钞¥:300
17:50:01.413 [main] INFO com.zixieqing.impl.ReadyState - 退卡成功
17:50:01.413 [main] INFO com.zixieqing.impl.ReadyState - 账户余额为:199700
17:50:01.413 [main] INFO com.zixieqing.impl.ReadyState - 退卡成功
===============结束状态===================
ATMContext(pwd=123456, money=300, balance=199700, ATMBalance=99700, currState=com.zixieqing.impl.ReadyState@5d6f64b1, readyState=com.zixieqing.impl.ReadyState@5d6f64b1, noServiceState=com.zixieqing.impl.NoServiceState@32a1bec0)
3.2.7.2、分析状态模式
状态模式的优点
- 消除了使用对象属性的条件分支语句(
if-else
或switch
),通过Context状态所属者中的状态行为托管,将属性 / 状态的逻辑处理迁移到了State子类来进行,降低了相互间的依赖。使代码结构清晰的同时也易扩展和易维护 - 显式化进行状态转换:为不同的状态引入独立的对象,使得状态的转换变得更加明确。而且状态对象可以保证上下文不会发生内部状态不一致的状况,因为上下文中只有一个变量来记录状态对象,只要为这一个变量赋值就可以了
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为
状态模式的缺点
- 因为此模式就是让对象随着其状态改变而改变行为,从而让其本身看起来像改变了一样,这其实就违背了“开闭原则”,因为若是添加新的具体状态类,那就需要在Context中维护此具体状态实例(修改负责状态转换的源代码),否则就无法切换到新增状态
- 状态模式的结构和实现逻辑一般都是很复杂的,如果一不注意搞不好就会导致程序结构和代码混乱
使用状态模式的建议:
1、具体状态类一般不要超过5个左右,不然维护起来也是很麻烦的,因为每一个具体状态类中不同行为逻辑本身就很复杂,而且不同行为之间会进行切换,甚至会切换到另外的状态去,例子如下(这还不是复杂的,一般真要用这个模式时复杂程度不算低):
3.2.7.3、状态模式 VS 命令模式
这两个模式的思路,或者说结构吧,是有点像的,但还是有区别的,还有一个策略模式,但这个还没玩呢,后续比较时再加入
状态模式和命令模式最大的区别是下面这些
- 首先第一点:状态模式中在抽象状态中是可以定义很多方法的,而命令模式的抽象命令中一般都是一个方法
- 其次第二点:要区分开二者的定义
- 状态模式:Context状态所属者自己内部维护着状态,会随着公共方法(前面示例中哪些供外部调用的,如:下图的)的调用而切换状态,外界不需要知道状态的真实变化情况(变化情况实质在具体状态类中,会根据情况进行状态切换)
- 命令模式:根据请求封装相应的命令,请求发起者不需要知道真正的命令是什么、该怎么处理(如:电视换台,只需要按下相应按钮,这按钮对应的底层命令是什么、会怎么进行处理,按下按钮的人不需要知道),只需要去调用抽象命令中的
execute()
接口就行(当然:也不是一定要叫这个接口名
- 状态模式:Context状态所属者自己内部维护着状态,会随着公共方法(前面示例中哪些供外部调用的,如:下图的)的调用而切换状态,外界不需要知道状态的真实变化情况(变化情况实质在具体状态类中,会根据情况进行状态切换)
* 3.2.8、策略模式
定义:又名政策模式(Policy),指的是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,使得它们可以互换(实现同一个接口:里氏替换原则),可以让算法随着调用者的变化而变化
解决的问题 / 适用场景:在有多种算法相似(同类可替代的行为逻辑算法)的情况下,去掉使用条件分支语句(
if-else
或switch
)所带来的复杂性和难以维护场景理解:不同类型的交易方式(微信、支付宝、银行卡);生成唯一主键策略(UUID、DB自增、DB自增+Redis、Leaf算法),这些就可以使用策略模式对不同的行为进行封装,供外部调用
策略模式的角色
-
上下文 / 环境类(Context):负责查询要做什么。这里面也可以封装着具体策略对象需要的数据,具体策略对象通过回调上下文的方法来获取这些数据,这玩意儿可以在必要时当做参数(即:关联)传给具体策略对象
-
抽象策略(Strategy):算法的抽象 / 算法的规范。接口或抽象类都可以
-
具体策略(ConcreteStrategy):具体的某一个算法 / 行为实现。若是有必要可以从上下文中回调它里面的方法来获取所需要的数据
策略模式的逻辑草图(可以对照命令模式来看,二者结构很像的)
3.2.8.1、简单逻辑
场景:就用上面说的不同类型交易方式(微信、支付宝)
1、上下文Context:在必要时存储具体策略需要的数据;负责调用者要做什么
package com.zixieqing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 该类功能 上下文 / 环境类
* 1、可以在必要时存储具体策略对象需要的数据
* 2、负责调用者要做什么
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class PayContext {
private Logger logger = LoggerFactory.getLogger(PayContext.class);
private PayStrategy payStrategy;
/**
* 让调用者自己决定用哪种支付策略
* @param payStrategy 支付方式
*/
public PayContext(PayStrategy payStrategy) {
this.payStrategy = payStrategy;
}
/**
* 负责要做什么:支付
*/
public void pay() {
this.payStrategy.pay();
}
}
2、抽象策略:对同组不同算法进行抽象,制定规范
package com.zixieqing;
/**
* <p>@description : 该类功能 抽象策略:对同组不同算法进行抽象
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface PayStrategy {
void pay();
}
- 微信支付
package com.zixieqing.impl;
import com.zixieqing.PayStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 该类功能 具体策略:微信支付方式
* </p>
* <p>@package : com.zixieqing.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class WeChatPayImpl implements PayStrategy {
private Logger logger = LoggerFactory.getLogger(WeChatPayImpl.class);
@Override
public void pay() {
logger.info("本微信系统:收到商户平台中传来的 创建订单请求");
logger.info("本微信系统:正在创建 预付单");
logger.info("本微信系统:预付单生成成功:返回商户平台预付单标识");
logger.info("这中间就是商户平台生成带签名的支付信息、用户发起支付请求、商户平台找微信客户端调起微信支付");
logger.info("本微信系统:收到微信客户端(即:微信APP)发起的支付请求,开始验证支付授权权限");
logger.info("本微信系统:给微信客户端返回支付授权");
logger.info("用户:确认支付、输入密码,即提交授权给本微信支付系统");
logger.info("本微信系统:正在验证支付授权");
logger.info("本微信系统:异步通知商户平台:支付结果");
logger.info("商户平台:保存支付通知,并返回本微信支付系统成功接收、处理与否");
logger.info("本微信系统:给微信客户端返回支付结果,并发微信消息提醒(即:微信中的那个微信支付的消息)");
logger.info("最后就是另外的一堆逻辑处理情况");
}
}
- 支付宝支付
package com.zixieqing.impl;
import com.zixieqing.PayStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 该类功能 具体策略:支付宝支付
* </p>
* <p>@package : com.zixieqing.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class AliPayImpl implements PayStrategy {
private Logger logger = LoggerFactory.getLogger(AliPayImpl.class);
@Override
public void pay() {
logger.info("支付宝支付和微信支付逻辑差不多的,假设这里就是一堆的支付宝支付逻辑,意思意思");
}
}
3、测试
package com.zixieqing;
import com.zixieqing.impl.AliPayImpl;
import com.zixieqing.impl.WeChatPayImpl;
/**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) {
PayContext payContext = new PayContext(new WeChatPayImpl());
payContext.pay();
System.out.println("==================华丽的分隔符====================");
PayContext payContext1 = new PayContext(new AliPayImpl());
payContext1.pay();
}
}
- 结果
16:56:43.248 [main] INFO com.zixieqing.impl.WeChatPayImpl - 本微信系统:收到商户平台中传来的 创建订单请求
16:56:43.250 [main] INFO com.zixieqing.impl.WeChatPayImpl - 本微信系统:正在创建 预付单
16:56:43.250 [main] INFO com.zixieqing.impl.WeChatPayImpl - 本微信系统:预付单生成成功:返回商户平台预付单标识
16:56:43.250 [main] INFO com.zixieqing.impl.WeChatPayImpl - 这中间就是商户平台生成带签名的支付信息、用户发起支付请求、商户平台找微信客户端调起微信支付
16:56:43.250 [main] INFO com.zixieqing.impl.WeChatPayImpl - 本微信系统:收到微信客户端(即:微信APP)发起的支付请求,开始验证支付授权权限
16:56:43.250 [main] INFO com.zixieqing.impl.WeChatPayImpl - 本微信系统:给微信客户端返回支付授权
16:56:43.250 [main] INFO com.zixieqing.impl.WeChatPayImpl - 用户:确认支付、输入密码,即提交授权给本微信支付系统
16:56:43.251 [main] INFO com.zixieqing.impl.WeChatPayImpl - 本微信系统:正在验证支付授权
16:56:43.251 [main] INFO com.zixieqing.impl.WeChatPayImpl - 本微信系统:异步通知商户平台:支付结果
16:56:43.251 [main] INFO com.zixieqing.impl.WeChatPayImpl - 商户平台:保存支付通知,并返回本微信支付系统成功接收、处理与否
16:56:43.251 [main] INFO com.zixieqing.impl.WeChatPayImpl - 本微信系统:给微信客户端返回支付结果,并发微信消息提醒(即:微信中的那个微信支付的消息)
16:56:43.251 [main] INFO com.zixieqing.impl.WeChatPayImpl - 最后就是另外的一堆逻辑处理情况
==================华丽的分隔符====================
16:56:43.251 [main] INFO com.zixieqing.impl.AliPayImpl - 支付宝支付和微信支付逻辑差不多的,假设这里就是一堆的支付宝支付逻辑,意思意思
3.2.8.2、分析策略模式
策略模式的优点
- 首先就是它的适用场景就是优点:避免代码中使用多重条件分支语句来判定采用哪种策略方式,从而变为采用new的方式,即:面向对象
-
符合开闭原则,当要新增策略时,只需要去实现接口,然后编写相应的策略逻辑即可
-
由于实现同一接口,同时在Context上下文中拥有接口的引用,所以方便客户端对不同策略进行自由切换
策略模式的缺点
- 可能会造成过多的具体策略类,即:在需要的代码中可以需要new好多个具体策略类(可以通过后续玩的亨元模式来减少一定的对象数量)
- 因为具体采用哪种策略时客户端来决定,所以:就会导致客户端需要知道所有的具体策略类,并且知道这些具体策略类之间有什么不同
3.2.8.3、策略模式 VS 状态模式 VS 命令模式
状态模式和命令模式已在前面玩状态模式时进行了对比
3.2.8.3.1、策略模式 VS 状态模式
这二者可以说是”亲兄弟”,二者在结构上很像,但是二者有区别
- 最主要的区别在定义上,也就是适用场景
- 1、策略模式讲究的是同组不同算法,从而让这些相似的算法之间根据客户端需要可以自由切换,具体算法之间没有交互。即:策略模式讲的是客户端控制对象使用什么策略。策略可自由切换是因为Context类中有Strategy策略类的引用(即:策略类之间依赖注入到Context类中)
- 2、状态模式讲究的是对象内部状态时行为也跟着改变,同时这些行为、状态之间可能有交互(即:状态切换)。即:状态模式讲的是自动改变状态(具体状态类中会根据相关逻辑进行状态切换)。可进行自动改变状态是因为具体状态类中有状态所属者Context的引用,从而在状态子类中已根据情况进行状态切换
- 1、策略模式讲究的是同组不同算法,从而让这些相似的算法之间根据客户端需要可以自由切换,具体算法之间没有交互。即:策略模式讲的是客户端控制对象使用什么策略。策略可自由切换是因为Context类中有Strategy策略类的引用(即:策略类之间依赖注入到Context类中)
- 其次区别于做的事情上
- 策略模式做的是同一件事情。如:前面的例子微信支付、支付宝支付,这些都是针对于支付方式这一件事
- 状态模式在不同状态下会做不同事情。如:前面的例子ATM在准备状态、无服务状态对于同一方法(插卡、提交密码、取款……)做的都是不同的事情,方法之间不具有可替代性
- 然后就是对于Context这个核心的上下文对象
- 策略模式在具体策略类中并不持有Context的引用,只供Context本身使用
- 状态模式在具体状态类中会持有Context的引用,目的就是对Context类进行操作,从而让状态最后回归到Context这个所属者身上(即:方便进行状态切换)
3.2.8.3.2、策略模式 VS 命令模式
这二者在结构上”有点像”,命令模式可以抽象地认为是一种策略模式(命令模式多了一个”接收者”) 且 命令处理的问题也更复杂,二者区别如下:
-
首先从定义上来看:
-
策略模式:是同组不同算法。它认为”算法”是一个已经不可拆分的业务(即:一件事,如:支付),只是这个业务有不同方式的实现而已,目的是为了让每个”具体算法”独立,并彼此之间可替代,让客户端来抉择使用哪一种”算法”。所以:此模式的关注重心在于”算法”的可替代问题
-
命令模式:它是将动作进行解耦(即:将请求包裹在命令对象中传给调用对象,由调用对象来找能处理该命令的对象进行处理),换言之:就是将一个动作分为了执行行为(命令对象)、执行对象(接收者角色),让这二者彼此独立而不相互影响。所以:此模式的关注重心在于动作的解耦问题
-
-
负责点不同(策略模式的抽象策略+具体策略、命令的接收者[也可拆为抽象接收者+具体接收者])
- 策略模式:具体策略是一个完整的逻辑,对客户端负责,所以具体策略要做即做完,同时一旦修改就意味着对”算法”的整体调整
- 命令模式:只对命令负责,至于请求是什么跟它关系不大(如:前面示例的厨师,它只需要负责做菜即可,至于用户请求是什么关其叼事);接收者可以不是一个完整的逻辑,事情没做好不直接影响客户端(影响的是命令对象),所以修改它不需要客户端操心(如:某个厨师可以不做某个菜、菜怎么做不需要客户单教他)
-
适用场景不同
- 策略模式:适用于”算法”要求变换的场景
- 命令模式:适用于解耦两个有紧耦合关系的对象场合 或 多命令多撤销的场景
* 3.2.9、模板模式
定义:模板模式也可以叫模板方法模式,指的是:在一个方法中定义一个算法的骨架(即:算法的结构),而一些其他非公共的步骤推迟到子类中去实现
一句话:封装公有部分,扩展非公有
场景理解:建房子。工地上会把地基、走线、水管…..通用的东西搭建好(毛坯房),然后根据情况加壁柜、加栅栏….这些有差异的就是其他接手了这个毛坯房的装修者来弄
适用场景:有多个子类共有的方法,并且逻辑相同;当然对于重要的、复杂的方法,也可以使用模板模式
注意点:因为是在父类中定义”算法”骨架,并实现一些公有操作,而子类是不可以改变这个”算法”骨架的,因此:模板方法一般加上
final
关键字,即:不可更改
模板方法模式的角色:
- 抽象模板(Template):定义”算法”模板、公有的具体操作、抽象操作
- 具体模板(ConcreteTemplate):抽象模板的子类,实现抽象操作(即:非公有部分的逻辑)
模板方法模式的逻辑草图:
3.2.9.1、简单逻辑
场景:搞豆浆,分为选原料、添加原料、浸泡、打碎,添加原料是非公有的(可为红豆、绿豆、黑豆、赤小豆………)
1、抽象模板
package com.zixieqing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 该类功能 抽象模板
* 1、定义模板方法 制定要做的这件事的算法结构
* 2、封装公有部分 实现其逻辑
* 3、抽象非公有部分 等着子类来实现逻辑
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public abstract class AbstractSoyMilk {
private Logger logger = LoggerFactory.getLogger(AbstractSoyMilk.class);
/**
* 模板方法:制定"算法"结构
*/
protected final void make() {
// 1、选择原料
choose();
// 2、添加调料
add();
// 3、浸泡
soak();
// 4、打碎
smashed();
}
/**
* 添加调料:非公有逻辑交由子类来实现
*/
protected abstract void add();
/**
* 选择原料:封装公有部分
*/
protected void choose() {
logger.info("{} 正在准备添加原料 ",this.getClass().getSimpleName());
logger.info("正在选择原料");
}
/**
* 浸泡:封装公有部分
*/
protected void soak() {
logger.info("{} 正在准备添加原料 ",this.getClass().getSimpleName());
logger.info("正在进行浸泡!");
}
/**
* 打碎:封装公有部分
*/
protected void smashed() {
logger.info("{} 正在准备添加原料 ",this.getClass().getSimpleName());
logger.info("正在进行打碎");
}
}
2、具体模板
- 红豆
package com.zixieqing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 该类功能 具体模板:对非公有部分进行实现
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class RedBeans extends AbstractSoyMilk{
private Logger logger = LoggerFactory.getLogger(RedBeans.class);
@Override
protected void add() {
logger.info("{} 正在准备添加原料 ",this.getClass().getSimpleName());
logger.info("价值3个W的红豆已添加");
}
}
- 绿豆
package com.zixieqing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 该类功能 具体模板:对非公有部分进行实现
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class GreenBeans extends AbstractSoyMilk{
private Logger logger = LoggerFactory.getLogger(GreenBeans.class);
@Override
protected void add() {
logger.info("{} 正在准备添加原料 ",this.getClass().getSimpleName());
logger.info("已经添加成功适合你颜色的绿豆");
}
}
3、测试
package com.zixieqing;
/**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) {
// 红豆型豆浆
AbstractSoyMilk redBeans = new RedBeans();
redBeans.make();
System.out.println("=====================华丽的分隔符==================");
// 绿豆型豆浆
AbstractSoyMilk greenBeans = new GreenBeans();
greenBeans.make();
}
}
- 结果
15:05:06.896 [main] INFO com.zixieqing.AbstractSoyMilk - RedBeans 正在准备添加原料
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - 正在选择原料
15:05:06.899 [main] INFO com.zixieqing.RedBeans - RedBeans 正在准备添加原料
15:05:06.899 [main] INFO com.zixieqing.RedBeans - 价值3个W的红豆已添加
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - RedBeans 正在准备添加原料
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - 正在进行浸泡!
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - RedBeans 正在准备添加原料
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - 正在进行打碎
=====================华丽的分隔符==================
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - GreenBeans 正在准备添加原料
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - 正在选择原料
15:05:06.899 [main] INFO com.zixieqing.GreenBeans - GreenBeans 正在准备添加原料
15:05:06.899 [main] INFO com.zixieqing.GreenBeans - 已经添加成功适合你颜色的绿豆
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - GreenBeans 正在准备添加原料
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - 正在进行浸泡!
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - GreenBeans 正在准备添加原料
15:05:06.899 [main] INFO com.zixieqing.AbstractSoyMilk - 正在进行打碎
3.2.9.2、分析模板方法模式
模板方法模式的优点
- 提取公共代码,便于维护;封装了不变部分,扩展可变部分
- 算法的结构由父类控制,子类只实现部分逻辑。 即扩大了代码的复用,父类中定义了模板方法、公有部分逻辑,子类在需要时可以直接复用
模板方法模式的缺点
- 对于可变部分,每一个不同的实现方式需要不同的类来实现(如:前面示例的红豆、绿豆),这样类的个数变多时,系统就变得复杂
3.2.10、访问者模式
定义·:使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。 元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作
解决的问题: 稳定的数据结构和易变的操作耦合问题(方式:在被访问的类里面加一个对外提供接待访问者的接口)
适用场景: 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作”玷污”这些对象的类(即:这些对象所在类的原有的数据结构),使用访问者模式将这些封装到类中。通俗易懂的话:就是一个东西不同的人看到的是不一样的结果也可以用,如:公司查账和税务局查账
访问者模式的角色
- 抽象元素(Element): 接收访问者访问的方法
- 具体元素(ConcreteElement): 抽象元素的子类,实现方法
- 抽象访问者(Visitor): 定义访问每个具体元素的方法
- 具体访问者(ConcreteVisitor): 抽象访问者的子类,对方法进行实现
- 对象结构(Subject Structure): 定义维护/添加元素、提供访问者能够访问所有元素的方法。注:这个是可以根据情况来变的
- 一是可以采用直接实现抽象接口,和抽象元素并为一个层次,然后里面做对象结构相应的事情即可
- 二是可以直接起一个对象结构的类,这里面一样的做对象结构相应的事情即可
3.2.10.1、访问者模式是怎么变出来的
1、首先知道原有数据结构指的是什么。 肯定用过如下的代码
@Data
public class Person {
private String name;
}
class Test{
public static void main(String[] args) {
Person person = new Person();
person.setName("紫邪情");
System.out.println(person.getName());
}
}
- 这整个
Person
就是数据结构,而name
就是所谓的元素,而person.setName("紫邪情")
就是对元素的操作,而访问者模式就是解决对这种数据结构中的对象(元素)进行很多不同且不相关的操作,从而避免这些操作”玷污”这些对象(元素,如上面的name·
)的类(如上面的Person
)的原有数据结构(即:Person
原来的结构)
2、抽象元素:接收访问者能够访问的方法
- 要访问的就是元素,也就是要访问
name
,因此:对类的所有要进行不同且不相关操作的属性 进行 抽象化
/**
* <p>@description : 该类功能 抽象元素:把原有对象的所有属性 进行 抽象化
* 1、接收访问者访问的方法
* </p>
* <p>@package : com.zixieqing.o1derive</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface People {
/**
* 接收访问者访问的方法 方法名不一定是这个,
* 根据对要进行不同且不相关操作的属性操作的抽象化命名即可
* @param visitor 访问者
*/
void accept(Visitor visitor);
}
- 把对原有数据结构中要进行不同且不相关操作的属性进行抽象化了
- 具体元素:原有数据结构(原有类,如:
Person
)中要进行不同且不相关操作的属性(如:name
……),这些每一个属性就是一个具体元素(子类),同时:这些子类都有访问者的引用,所以都能够让访问者访问了;相应地,具体元素中的构成就变成要对这个元素(属性)进行的那些不同且不相关的操作(如:简单的那种setter,但不是真搞这种无趣的操作)
package com.zixieqing.o1derive.impl;
import com.zixieqing.o1derive.People;
import com.zixieqing.o1derive.Visitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 该类功能 具体元素
* 1、让访问者能够对原有类中的属性进行操作
* </p>
* <p>@package : com.zixieqing.o1derive.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Name implements People {
private Logger logger = LoggerFactory.getLogger(Name.class);
@Override
public void accept(Visitor visitor) {
logger.info("{}这个类就是原来类中的属性,这个{}类中就可以进行这个{}属性的不同且不相关的操作",
this.getClass().getSimpleName(),
this.getClass().getSimpleName(),
this.getClass().getSimpleName().toLowerCase());
}
}
3、访问者:提供访问所有元素的方法 访问所有元素,就是访问原有类(如:Perosn
)的属性,而现在属性变成了一个单独的类(即:具体元素)
import com.zixieqing.o1derive.impl.Name;
/**
* <p>@description : 该类功能 抽象访问者:提供能够访问所有元素(原来类中属性)的方法
* </p>
* <p>@package : com.zixieqing.o1derive</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface Visitor {
/**
* 访问原有类的name属性 / 访问name元素
* 要是还有其他的就继续加 如:sex........
* @param name 要访问的元素
*/
void visit(Name name);
}
- 具体访问者就是去访问元素,而元素的引用也有了,那执行
package com.zixieqing.o1derive.impl;
import com.zixieqing.o1derive.Visitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 该类功能 具体访问者
* </p>
* <p>@package : com.zixieqing.o1derive.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ConcreteVisitor implements Visitor {
private Logger logger = LoggerFactory.getLogger(ConcreteVisitor.class);
@Override
public void visit(Name name) {
logger.info("{}访问者即将开始元素访问",this.getClass().getSimpleName());
// 需要访问者,当前这个就是一个具体的访问者,当然:也可以根据情况来弄
name.accept(this);
}
}
4、对象结构:维护元素、提供访问者访问所有元素的方法
- 原有类Person类中要进行不同且不相关操作的属性被抽象化为了:抽象元素(接收访问者访问)+具体操作(该元素进行的不同且不相关的操作)
- 访问每个具体元素的也有了:抽象访问者+具体访问者
- 那现在还得有一个东西将元素+访问者进行连接,即:对象结构(添加元素+让访问者能访问所有的元素)
package com.zixieqing.o1derive;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 该类功能 对象结构
* 1、添加元素 / 维护元素
* 2、让访问者能访问所有元素
* </p>
* <p>@package : com.zixieqing.o1derive</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class SubjectStructure {
private List<People> peoples = new ArrayList<>();
/**
* 添加元素
* @param people 要添加的元素
* @return true/false
*/
public boolean addElement(People people) {
return peoples.add(people);
}
/**
* 让访问者能够访问所有元素
* @param visitor 访问者
*/
public void getPeoples(Visitor visitor) {
for (People people : peoples) {
people.accept(visitor);
}
}
}
5、现在我们要添加元素、获取元素就找”对象结构”+访问者
package com.zixieqing;
import com.zixieqing.o1derive.SubjectStructure;
import com.zixieqing.o1derive.impl.ConcreteVisitor;
import com.zixieqing.o1derive.impl.Name;
/**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) {
SubjectStructure subjectStructure = new SubjectStructure();
// 添加要访问的元素
subjectStructure.addElement(new Name());
// 通过对象结构+具体访问者进行元素访问
subjectStructure.getPeoples(new ConcreteVisitor());
}
}
因此:现在这个模式的逻辑草图就出来了
3.2.10.2、简单逻辑
场景:同一问题不同角度观察,校长对于学生、老师的关注点(升学率);家长对学生老师的关注点(成绩)
1、抽象元素:接收访问者访问
package com.zixieqing.o2simple.user;
import com.zixieqing.o2simple.visitor.Visitor;
/**
* <p>@description : 该类功能 抽象元素:用户信息
* 1、接收访问者访问
* </p>
* <p>@package : com.zixieqing.o2simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public abstract class User {
/**
* 姓名
*/
public String name;
/**
* 身份;重点班、普通班 | 特级教师、普通教师、实习教师
*/
public String identity;
/**
* 班级
*/
public String clazz;
public User(String name, String identity, String clazz) {
this.name = name;
this.identity = identity;
this.clazz = clazz;
}
/**
* 核心访问方法
* @param visitor 访问者
*/
public abstract void accept(Visitor visitor);
}
- 具体元素:学生
package com.zixieqing.o2simple.user.impl;
import com.zixieqing.o2simple.user.User;
import com.zixieqing.o2simple.visitor.Visitor;
/**
* <p>@description : 该类功能 具体元素:学生
* </p>
* <p>@package : com.zixieqing.o2simple.user.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Student extends User {
public Student(String name, String identity, String clazz) {
super(name, identity, clazz);
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
/**
* 对这个元素进行的不同且不相关操作之一:排名
* @return int 排名
*/
public int ranking() {
return (int) (Math.random() * 100);
}
}
- 具体元素:老师
package com.zixieqing.o2simple.user.impl;
import com.zixieqing.o2simple.user.User;
import com.zixieqing.o2simple.visitor.Visitor;
import java.math.BigDecimal;
/**
* <p>@description : 该类功能 具体元素:老师
* </p>
* <p>@package : com.zixieqing.o2simple.user.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Teacher extends User {
public Teacher(String name, String identity, String clazz) {
super(name, identity, clazz);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
/**
* 对这个具体元素进行的不同且不相关的操作之一:升本率
* @return double 升学率
*/
public double entranceRatio() {
return BigDecimal.valueOf(Math.random() * 100).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
}
}
2、访问者:访问每个具体元素
package com.zixieqing.o2simple.visitor;
import com.zixieqing.o2simple.user.impl.*;
/**
* <p>@description : 该类功能 访问者:访问每个具体元素
* </p>
* <p>@package : com.zixieqing.o2simple.visitor</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public interface Visitor {
/**
* 访问学生信息
* @param student 具体元素:学生
*/
void visit(Student student);
/**
* 访问老师信息
* @param teacher 具体元素:老师
*/
void visit(Teacher teacher);
}
- 具体访问者:校长
package com.zixieqing.o2simple.visitor.impl;
import com.zixieqing.o2simple.visitor.Visitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zixieqing.o2simple.user.impl.*;
/**
* <p>@description : 该类功能 具体访问者:校长
* </p>
* <p>@package : com.zixieqing.o2simple.visitor.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Principal implements Visitor {
private Logger logger = LoggerFactory.getLogger(Principal.class);
public void visit(Student student) {
logger.info("学生信息 姓名:{} 班级:{}", student.name, student.clazz);
}
public void visit(Teacher teacher) {
logger.info("学生信息 姓名:{} 班级:{} 升学率:{}", teacher.name, teacher.clazz, teacher.entranceRatio());
}
}
- 具体访问者:家长
package com.zixieqing.o2simple.visitor.impl;
import com.zixieqing.o2simple.visitor.Visitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zixieqing.o2simple.user.impl.*;
/**
* <p>@description : 该类功能 具体访问者:家长
* </p>
* <p>@package : com.zixieqing.o2simple.visitor.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class Parent implements Visitor {
private Logger logger = LoggerFactory.getLogger(Parent.class);
public void visit(Student student) {
logger.info("学生信息 姓名:{} 班级:{} 排名:{}", student.name, student.clazz, student.ranking());
}
public void visit(Teacher teacher) {
logger.info("老师信息 姓名:{} 班级:{} 级别:{}", teacher.name, teacher.clazz, teacher.identity);
}
}
3、对象结构/数据看板
package com.zixieqing.o2simple;
import com.zixieqing.o2simple.user.User;
import com.zixieqing.o2simple.user.impl.Student;
import com.zixieqing.o2simple.user.impl.Teacher;
import com.zixieqing.o2simple.visitor.Visitor;
import java.util.ArrayList;
import java.util.List;
/**
* <p>@description : 该类功能 对象结构/数据看板
* 1、添加元素
* 2、访问所有元素
* </p>
* <p>@package : com.zixieqing.o2simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class DataView {
private List<User> userList = new ArrayList<User>();
public DataView() {
userList.add(new Student("谢飞机", "重点班", "一年一班"));
userList.add(new Student("windy", "重点班", "一年一班"));
userList.add(new Student("大毛", "普通班", "二年三班"));
userList.add(new Student("Shing", "普通班", "三年四班"));
userList.add(new Teacher("BK", "特级教师", "一年一班"));
userList.add(new Teacher("娜娜Goddess", "特级教师", "一年一班"));
userList.add(new Teacher("dangdang", "普通教师", "二年三班"));
userList.add(new Teacher("泽东", "实习教师", "三年四班"));
}
/**
* 获取所有元素
* @param visitor 访问者
*/
public void show(Visitor visitor) {
for (User user : userList) {
user.accept(visitor);
}
}
}
4、测试
package com.zixieqing;
import com.zixieqing.o2simple.DataView;
import com.zixieqing.o2simple.visitor.impl.Parent;
import com.zixieqing.o2simple.visitor.impl.Principal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/
public class ApiTest {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(ApiTest.class);
DataView dataView = new DataView();
logger.info("\r\n家长视角访问:");
// 家长
dataView.show(new Parent());
logger.info("\r\n校长视角访问:");
// 校长
dataView.show(new Principal());
}
}
- 结果
10:53:55.565 [main] INFO com.zixieqing.ApiTest -
家长视角访问:
10:53:55.568 [main] INFO c.z.o2simple.visitor.impl.Parent - 学生信息 姓名:谢飞机 班级:一年一班 排名:54
10:53:55.568 [main] INFO c.z.o2simple.visitor.impl.Parent - 学生信息 姓名:windy 班级:一年一班 排名:83
10:53:55.568 [main] INFO c.z.o2simple.visitor.impl.Parent - 学生信息 姓名:大毛 班级:二年三班 排名:84
10:53:55.568 [main] INFO c.z.o2simple.visitor.impl.Parent - 学生信息 姓名:Shing 班级:三年四班 排名:91
10:53:55.568 [main] INFO c.z.o2simple.visitor.impl.Parent - 老师信息 姓名:BK 班级:一年一班 级别:特级教师
10:53:55.568 [main] INFO c.z.o2simple.visitor.impl.Parent - 老师信息 姓名:娜娜Goddess 班级:一年一班 级别:特级教师
10:53:55.568 [main] INFO c.z.o2simple.visitor.impl.Parent - 老师信息 姓名:dangdang 班级:二年三班 级别:普通教师
10:53:55.568 [main] INFO c.z.o2simple.visitor.impl.Parent - 老师信息 姓名:泽东 班级:三年四班 级别:实习教师
10:53:55.568 [main] INFO com.zixieqing.ApiTest -
校长视角访问:
10:53:55.569 [main] INFO c.z.o2simple.visitor.impl.Principal - 学生信息 姓名:谢飞机 班级:一年一班
10:53:55.569 [main] INFO c.z.o2simple.visitor.impl.Principal - 学生信息 姓名:windy 班级:一年一班
10:53:55.569 [main] INFO c.z.o2simple.visitor.impl.Principal - 学生信息 姓名:大毛 班级:二年三班
10:53:55.569 [main] INFO c.z.o2simple.visitor.impl.Principal - 学生信息 姓名:Shing 班级:三年四班
10:53:55.571 [main] INFO c.z.o2simple.visitor.impl.Principal - 学生信息 姓名:BK 班级:一年一班 升学率:44.54
10:53:55.571 [main] INFO c.z.o2simple.visitor.impl.Principal - 学生信息 姓名:娜娜Goddess 班级:一年一班 升学率:45.49
10:53:55.571 [main] INFO c.z.o2simple.visitor.impl.Principal - 学生信息 姓名:dangdang 班级:二年三班 升学率:70.92
10:53:55.571 [main] INFO c.z.o2simple.visitor.impl.Principal - 学生信息 姓名:泽东 班级:三年四班 升学率:78.14
3.2.10.3、分析访问者模式
访问者模式的优点:
- 符合单一职责
- 拥有良好的扩展性,要扩展只需添加相应的元素+访问者即可
访问者模式的缺点:
- 因为元素类中有访问者的引用,即:元素的细节对访问者是公开的,这违反了迪米特法则(最少知道原则)
- 在访问者中用的是具体元素的引用,而不是抽象元素的引用,所以:这违反了里氏替换原则(即:多态)
访问者模式的重点:元素+访问者