目录
- 1、软件设计原则
- 2、创建者模式
- 3、结构型模式
- 4、行为型模式
- 4.1、模板方法模式
- 4.2、策略模式
- 4.3、命令模式
- 4.4、责任链模式
- 4.5、状态模式
- 4.6、观察者模式
- 4.7、中介者模式
- 4.8、迭代器模式
- 4.9、访问者模式
- 4.10、备忘录模式
- 4.11、解释器模式
1、软件设计原则
1.1、开闭原则
总结: 对扩展开放,对修改关闭
解释: 在程序需要进行扩展的时候,尽量不要修改原有的代码,而是尽量使用接口或者抽象类来达到热插拔的效果。这是因为抽象可以带来很强的扩展性,以及很高的灵活性,对于容易变化的地方使用实现类来进行不同实现即可。
举例: 我原来用RabbitMQ做消息中间件,但是后来RabbitMQ出现问题了,我要把消息中间件换成Kafka。在使用消息中间件的时候我创建的是接口,然后写了一个RabbitMQ实现类,所以我不需要更改RabbitMQ实现类代码,而是重新写一个Kafak实现类,然后对原来接口中的方法进行实现即可
1.2、里氏代换原则
总结: 基类出现的地方可以被任何子类替换,即子类不要重写父类的方法
解释: 子类可以扩展父类的功能,但是尽量不要重写父类的方法,否则当继承层级比较深的时候,你自己都搞不懂当前类所用的父类方法到底是哪个,这将导致出错的概率很大
举例: A是B的父类,B是C的父类,其中A中有一个方法test(),在B中进行了重写,然后我还想在C中使用A类的test()方法,但是它使用的却是B类重写的test()方法,这样一来程序的清晰度大大降低
1.3、依赖倒转原则
总结: 依赖抽象而不是细节
解释: 依赖细节会导致难以扩展、代码冗余,而依赖抽象则不会出现这种情况,并且会使代码更加清晰
举例: 假设我需要一辆汽车,但是没有指定颜色和动力源,我们如果直接创建红色新能源汽车类、黑色燃油汽车类……,恐怕会产生很多类,因此我们可以创建一个汽车类,并且在汽车类中添加颜色类和动力源类对象,用户需要什么类型汽车,只需要指定颜色和动力源,就可以得到指定的汽车
1.4、接口隔离原则
总结: 最小接口原则,所实现接口中的方法尽量都要真正重写,而不是空方法
解释: 实现一个接口,但是接口中的方法我根本用不到,但是我依然需要重写该方法,即使是一个空的方法重写,这其实就是冗余代码,因此还可以将接口在分,保证所实现接口中的方法都是真正被重写的
举例: 假设我有一家门厂,可以生产防盗、防火、防水的门,用户A想要一个防火门,而用户B想要一个防盗防火防水门,因此防盗、防火、防水功能都是单独的接口,我们可以为用户进行定制化
1.5、迪米特法则
总结: 最少知道原则
解释: 如果两个类不需要直接通信,那么就不应该相互调用,可以通过第三方调用相关类来实现功能,减少类之间的耦合性
举例: 假设某明星开演唱会,那么明星只经纪人联系就行,经纪人去协调门票销售商卖票,其中明星只需要根据经纪人指示去唱歌就行,其他的事情和明星无关,所以对于明星和门票销售商就不应该互相调用,只需要让经纪人充当第三方进行协调就可以了
1.6、合成复用原则
总结: 首选组合或者聚合,其次才是继承
解释: 继承虽然可以减少冗余代码,但是也存在缺点,比如父类的任何改变都会影响子类,另外子类还可以重写父类方法,造成不安全的因素,但是组合或者聚合不同,我们不需要关心所用对象方法的实现细节,只需要调用其方法就可以了,只需它方法中是如何实现的,以及以后会不会变,这些我们都不需要关心,我们只是一个使用者
举例: 假设我需要一辆汽车,但是没有指定颜色和动力源,我们如果直接创建一个汽车类,然后红色新能源汽车类、黑色燃油汽车类……都继承汽车类,恐怕会产生很多类,因此我们可以在汽车类中组合或者聚合颜色类和动力源类,用户需要什么类型汽车,只需要指定颜色和动力源,就可以得到指定的汽车
2、创建者模式
说明:
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。
这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。
2.1、单例模式
2.1.1、实现方式
2.1.1.1、饿汉式
2.1.1.1.1、静态变量(线程安全)
public class Singleton {
/** 静态变量 **/
private static Singleton instance = new Singleton();
/**
* 私有化构造方法
**/
private Singleton() {}
/**
* 获取单例对象方法
*/
public static Singleton getInstance() {
return instance;
}
}
2.1.1.1.2、静态代码块(线程安全)
public class Singleton {
/** 静态变量 **/
private static Singleton instance;
/**
* 静态代码块
*/
static {
instance = new Singleton();
}
/**
* 私有化构造方法
**/
private Singleton() {}
/**
* 获取单例对象方法
*/
public static Singleton getInstance() {
return instance;
}
}
2.1.1.2、懒汉式
2.1.1.2.1、判空创建(线程不安全)
public class Singleton {
/** 静态变量 **/
private static Singleton instance;
/**
* 私有化构造方法
**/
private Singleton() {}
/**
* 获取单例对象方法
*/
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2.1.1.2.2、同步方法(线程安全)
public class Singleton {
/** 静态变量 **/
private static Singleton instance;
/**
* 私有化构造方法
**/
private Singleton() {}
/**
* 获取单例对象方法
*/
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2.1.1.2.3、双重校验锁(线程安全)
public class Singleton {
/** 防止指令重排的静态变量 **/
private static volatile Singleton instance;
/**
* 私有化构造方法
**/
private Singleton() {}
/**
* 获取单例对象方法
*/
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
2.1.1.2.4、静态内部类(线程安全)
public class Singleton {
/**
* 私有化构造方法
**/
private Singleton() {}
/**
* 静态内部类
*/
private static class SingletonHolder {
/** 静态变量 **/
private static Singleton instance = new Singleton();
}
/**
* 获取单例对象方法
*/
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
2.1.1.2.5、枚举(线程安全)
public enum Singleton {
INSTANCE;
}
说明: 通过枚举创建的单例对象不会被反射破坏
2.1.2、存在问题(单例模式被破坏)
2.1.2.1、序列化和反序列化
不太懂,以后碰到了在补上
2.1.2.2、反射
2.1.2.2.1、问题复现
public class Test {
public static void main(String[] args) throws Exception {
// 获取Class字节码对象
Class clazz = Singleton.class;
// 获取无参构造方法对象
Constructor constructor = clazz.getDeclaredConstructor();
// 取消访问检查
constructor.setAccessible(true);
// 创建对象
Singleton s1 = (Singleton) constructor.newInstance();
Singleton s2 = (Singleton) constructor.newInstance();
// 比较判断
System.out.println(s1 == s2); // 结果:false;代表:单例模式失效
}
}
2.1.2.2.2、解决问题
public class Singleton {
/** 防止指令重排的静态变量 **/
private static volatile Singleton instance;
/** 构造方法是否被调用过的标识 **/
private static boolean flag = false;
/**
* 私有化构造方法
**/
private Singleton() {
// 禁止两次调用构造方法
synchronized (Singleton.class) {
if (flag) {
throw new RuntimeException(new NoSuchMethodException());
}
flag = true;
}
}
/**
* 获取单例对象方法
*/
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
2.1.3、使用案例
2.1.3.1、Runtime源码(饿汉模式)
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
}
2.1.3.2、公司线程池(静态内部类)
public class ThreadPoolHolder {
/**
* 静态内部类
*/
private static class SingletonHolder {
// corePoolSize本身设置为0,整个线程池的生命周期就已经全部纳入keepAliveTime管理
// CallerRunsPolicy则是为了做任务提交的缓冲
private static volatile ThreadPoolExecutor INSTANCE = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), 5 * Runtime.getRuntime().availableProcessors(), 0,
TimeUnit.SECONDS, new SynchronousQueue<Runnable>(true),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
}
/**
* 私有构造器
*/
private ThreadPoolHolder() {}
/**
* 获取单例对象方法
*/
public static ThreadPoolExecutor getThreadPoolExecutor() {
return ThreadPoolHolder.SingletonHolder.INSTANCE;
}
}
2.2、简单工厂模式
2.2.1、通俗解释
现在有多个具体产品,但是只有一个工厂,通过在具体工厂方法中添加判断条件来决定采用哪个具体产品
2.2.2、涉及角色
- 具体工厂:创建具体产品
- 抽象产品:描述产品功能
- 具体产品:描述产品具体功能
2.2.3、优缺点
优点: 可以通过参数直接获取对象,把对象创建和对象业务逻辑方法实现分开,当需要增加新产品的时候,只需要创建一个新的产品,然后在具体工厂中创建产品的方法中添加判断条件来获取新产品即可,不用更改以往具体产品方法的代码,增强扩展性
缺点: 增加新产品的时候依然需要更改工厂类的代码(增加判断条件来获取新产品),违背了开闭原则
2.2.4、实现方式
代码:
// 具体工厂
public class SimpleCoffeeFactory {
public Coffee createCoffee(String type) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffee;
}
}
// 抽象产品
class Coffee {
public void make();
}
// 具体产品1
class AmericanoCoffee {
public void make();
}
// 具体产品2
class LatteCoffee {
public void make();
}
UML类图:
说明:咖啡店类通过咖啡工厂中的createCoffee()方法产生具体咖啡
2.2.5、使用案例
说明: 虽然实现方式中给我们提供了简单工厂模式的一种实现,但是我们可以在此基础上变形,比如实现方式中是传递的筛选条件,但是我们的筛选条件也是可以写在配置文件中,或者其他地方,只要在创建具体对象的时候能获取到,然后用以判断就可以了,因此对于设计模式不需要定的死死的
2.2.5.1、Server构建器
// 具体工厂
public class WebServerFactory {
public static WebServer create(Injector injector) {
// 从配置文件中获取mode
String mode = Config.getServerMode();
if ("bio".equals(mode)) {
return new BioWebServer(injector);
} else {
return new BioWebServer(injector);
}
}
}
// 抽象产品
public interface WebServer {
public MqServer getMqServer();
public void start();
}
// 具体产品
public class BioWebServer implements WebServer, Runnable {
……
private ServerSocket serverSocket;
private MqServer mqServer;
private boolean inited;
private Injector injector;
public BioWebServer(Injector injector) {
mqServer = MqServerFactory.create(injector);
this.injector = injector;
}
……
@Override
public MqServer getMqServer() {
return this.mqServer;
}
@Override
public void start() {
if (!inited) {
inited = true;
this.thread.start();
}
}
}
2.2.5.2、消息队列服务构建器
// 具体工厂
public class MqServerFactory {
public static MqServer create(Injector injector) {
String mode = Config.getMqMode();
if ("rabbitmq".equals(mode)) {
return new RabbitMqServer();
} else if ("kafka".equals(mode)) {
return injector.getInstance(KafkaMqServer.class);
} else {
return new RabbitMqServer();
}
}
}
// 抽象产品
public interface MqServer extends Singleton{
// 创建用户连接会话
MqSession createUserSession(String userId, WebSession session);
// 创建服务连接会话
MqSession createServerSession(String domain, WebSession session);
……
}
// 具体产品1(kafka)
public class KafkaMqServer implements MqServer{
public Group2UserDao group2UserDao;
@Override
public MqSession createUserSession(String userId, WebSession webSession) {
KafkaMqSession session = new KafkaMqSession(this,webSession);
session.login(userId);
return session;
}
……
}
// 具体产品2(rabbitmq)
public class RabbitMqServer implements MqServer {
……
public Channel createChannel(Connection conn) throws IOException {
Channel channel = conn.createChannel();
if(conn.getId()==null) {
conn.setId(UUIDUtil.create());
}
Integer count = connectionChannelCount.get(conn.getId());
if(count==null) {
count=0;
}
count++;
connectionChannelCount.put(conn.getId(), count);
return channel;
}
……
}
2.2.6、扩展
2.2.6.1、静态工厂模式
在工厂类方法上添加static关键字,那么这就是静态工厂模式,如下图:
public class SimpleCoffeeFactory {
// 添加static关键字
public static Coffee createCoffee(String type) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffe;
}
}
2.2.6.2、用配置文件解除简单工厂和具体对象的耦合
2.2.6.2.1、第一步:定义配置文件
为了演示方便,我们使用properties文件作为配置文件,名称为bean.properties
american=com.itheima.pattern.factory.config_factory.AmericanCoffee
latte=com.itheima.pattern.factory.config_factory.LatteCoffee
2.2.6.2.2、第二步:改进工厂类
public class CoffeeFactory {
private static Map<String,Coffee> map = new HashMap();
static {
Properties p = new Properties();
InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
p.load(is);
//遍历Properties集合对象
Set<Object> keys = p.keySet();
for (Object key : keys) {
//根据键获取值(全类名)
String className = p.getProperty((String) key);
//获取字节码对象
Class clazz = Class.forName(className);
Coffee obj = (Coffee) clazz.newInstance();
map.put((String)key,obj);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static Coffee createCoffee(String name) {
return map.get(name);
}
}
2.2.6.2.3、解释说明
静态成员变量用来存储创建的对象(键存储的是名称,值存储的是对应的对象),而读取配置文件以及创建对象写在静态代码块中,目的就是只需要执行一次。
以后在需要增加具体产品,只需要在配置文件中写明标识(key)和类全路径就可以了,我们就不需要更改具体工厂代码了,另外反射也是很nice的解耦方式,把配置信息放在配置文件中就更nice了,但是具体产品数量比较少的话不建议这样去做,毕竟配置文件不太直观
2.3、工厂方法模式(用于同类产品)
2.3.1、通俗解释
我所说的同类产品是一个类型的产品,比如惠普电脑、联想电脑、华硕电脑都是一个类型的,所以说工厂方法模式更像是一个产品销售中间商
简单工厂模式只有一个工厂,在新增具体产品的时候还需要修改具体工厂获取具体产品对象的方法,这不符合开闭原则,为了符合开闭原则,那就设置一个抽象工厂,然后同类产品对应一个具体工厂(比如各种咖啡都是同类产品,下面的举例只是说明概念而已,其实可以归到一个具体工厂中),这样就符合开闭原则了
2.3.2、概念
定义创建对象的接口(抽象工厂),让子类(具体工厂)决定采用哪个产品类对象(具体产品),工厂方法模式使一个产品类的实例化延迟到具体工厂
2.3.3、涉及角色
- 抽象工厂:接口,提供产品创建方法
- 具体工厂:类,创建具体产品
- 抽象产品:接口,描述产品功能
- 具体产品:类,描述产品具体功能
2.3.4、优缺点
优点: 在新增产品的时候,简单工厂模式需要更改具体工厂类中方法的代码(增加判断条件),而工厂方法模式只需要增加具体产品类和具体工厂类就可以了
缺点: 增加新产品的时候不仅需要增加产品本身,还需要增加一个具体工厂类
2.3.5、实现方式
// 抽象工厂
public interface CoffeeFactory {
Coffee createCoffee();
}
// 具体工厂1
public class LatteCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
}
// 具体工厂2
public class AmericanCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
}
UML类图:
2.3.6、使用案例
2.3.6.1、ArrayList
使用迭代器遍历ArrayList的代码大家肯定很熟悉,如下:
public class Test {
public static void main(String[] args) throws Exception {
Collection<String> collection = new ArrayList<>();
collection.add("令狐冲");
collection.add("风清扬");
collection.add("任我行");
//获取迭代器对象
Iterator<String> itr = collection.iterator();
//使用迭代器遍历
while(itr.hasNext()) {
String e = itr.next();
System.out.println(e);
}
}
}
其实Collection是抽象工厂,ArrayList是具体工厂,Iterator是抽象产品,而ArrayList内部类Itr是具体产品,类图如下:
2.4、抽象工厂模式(用于产品族)
2.4.1、通俗解释
一个产品族是不同产品的所有类型,更像是一个大型综合工厂,比如海尔、美的、九阳等
在抽象工厂模式中一个具体工厂只会生产同类产品,比如电视机厂只生成各种品牌的电视机,但是实际情况中却不是这样,比如海尔会生产电视机、冰箱、洗衣机……,因此在抽象工厂模式中抽象工厂定义的是一个产品族(一组不同类型的产品)的生产方法,下图中同一级别并不是指代型号,我认为指代品牌比较好,毕竟一家工厂生成的型号也是多种多样的,用下图来描述产品族:
如果拿衣服来描述产品族,如下:
2.4.2、概念
2.4.3、涉及角色
- 抽象工厂:接口,提供产品创建方法
- 具体工厂:类,创建具体产品
- 抽象产品:接口,描述产品功能
- 具体产品:类,描述产品具体功能
2.4.4、优缺点
优点: 在新增产品的时候,简单工厂模式需要更改具体工厂类中方法的代码(增加判断条件),而抽象工厂模式只需要增加抽象工厂类中的方法就可以了,当然实现抽象工厂的具体工厂类都需要重写抽象方法
缺点: 增加新产品的时候不仅需要增加产品本身,如果新产品和老产品在同一个产品族中,还需要更改抽象工厂模式中的方法,进而更改简单工厂的方法
2.4.5、实现方式
// 抽象工厂
public interface DessertFactory {
Coffee createCoffee();
Dessert createDessert();
}
// 具体工厂1(美式甜点工厂)
public class AmericanDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
public Dessert createDessert() {
return new MatchaMousse();
}
}
// 具体工厂2(意大利风味甜点工厂)
public class ItalyDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
public Dessert createDessert() {
return new Tiramisu();
}
}
2.5、原型模式
2.5.1、概述
复制已有对象,来创建一个新对象
原型模式的拷贝分为浅拷贝和深拷贝
浅拷贝: 创建一个新的对象,新对象中的引用数据类型(不包括基本数据类型包装类、String)依然使用原有对象的,原理是使用Object类中的native原生clone方法进行浅拷贝,画图表示如下:
深拷贝: 创建一个新的对象,新对象中的引用数据类型(不包括基本数据类型包装类、String)也是新创建的,原理是在对象的clone方法中将所有引用类型属性的浅拷贝对象赋值给对应引用数据,当然引用数据类型先有支持浅拷贝,画图表示如下:
2.5.2、涉及角色
- 抽象原型类:规定了具体原型对象必须实现的clone()方法
- 具体原型类:实现抽象原型类中的clone()方法
2.5.3、实现方式
2.5.3.1、浅拷贝
代码:
public class Test {
public static void main(String[] args) throws Exception {
Info info1 = new Info();
info1.setCountry("中国");
Info info2 = info1.clone();
info2.setCountry("美国");
System.out.println(info1.getCountry());
System.out.println(info2.getCountry());
}
}
/**
* 具体原型类
*/
class Info implements Cloneable {
/** 国家信息 **/
private String country;
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
@Override
protected Info clone() throws CloneNotSupportedException {
return (Info) super.clone();
}
}
结果:
中国
美国
2.5.3.2、深拷贝
代码:
public class Test {
public static void main(String[] args) throws Exception {
Info info1 = new Info();
Student student = new Student();
student.setName("李华");
student.setAge(18);
info1.setCountry("中国");
info1.setStudent(student);
Info info2 = info1.clone();
info2.setCountry("美国");
info2.getStudent().setName("Tom");
info2.getStudent().setAge(20);
System.out.println(info1.getStudent());
System.out.println(info2.getStudent());
}
}
/**
* 具体原型类
*/
class Info implements Cloneable {
/** 国家信息 **/
private String country;
/** 学生基本信息 **/
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
@Override
protected Info clone() throws CloneNotSupportedException {
Info info = (Info) super.clone();
// 1、对引用数据类型的属性进行浅拷贝
info.setStudent(this.student.clone());
return info;
}
}
/**
* 引用数据类型
* 2、引用数据类型本身支持浅拷贝
*/
class Student implements Cloneable {
/** 姓名 **/
private String name;
/** 年龄 **/
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
protected Student clone() throws CloneNotSupportedException {
return (Student) super.clone();
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
结果:
Student{name='李华', age=18}
Student{name='Tom', age=20}
2.5.3.3、对引用数据类型进行浅拷贝,导致引用数据类型属性值被改变的案例
代码:
public class Test {
public static void main(String[] args) throws Exception {
Info info1 = new Info();
Student student = new Student();
student.setName("李华");
student.setAge(18);
info1.setCountry("中国");
info1.setStudent(student);
Info info2 = info1.clone();
info2.setCountry("美国");
info2.getStudent().setName("Tom");
info2.getStudent().setAge(20);
System.out.println(info1.getStudent());
System.out.println(info2.getStudent());
}
}
/**
* 具体原型类
*/
class Info implements Cloneable {
/** 国家信息 **/
private String country;
/** 学生基本信息 **/
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
@Override
protected Info clone() throws CloneNotSupportedException {
// 1、没有对引用数据类型的属性进行浅拷贝
return (Info) super.clone();
}
}
/**
* 引用数据类型
* 2、引用数据类型本身不支持浅拷贝
*/
class Student {
/** 姓名 **/
private String name;
/** 年龄 **/
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
结果:
Student{name='Tom', age=20}
Student{name='Tom', age=20}
解释:
可以看到两个输出值是一样的,说明浅拷贝对引用数据类型根据不适用
2.6、建造者模式
2.5.1、概述
现在需要做一件事情,但是这件事情非常麻烦,需要多步才能完成,因此我们需要将多个步骤进行分离,然后最终在写一个方法按照步骤进行汇总,调用者无需知道这些细节,只需要知道将想要的告诉我们,我们去调用具体方法来为他完成这些事情,然后将结果返回给调用者就可以了
拿创建共享单车举例吧,假设摩拜单车和ofo小黄车负责人都需要凤凰自行车厂生产自行车,做车要求商量齐全之后,两家共享单车机构负责人只用等着取车就可以了,不用关心自行车厂是如何把自行车制造出来的,可能这其中经过了制造车架、车座等等过程,并且制作车架、车座都是一个大步骤,其中还包含类一些小步骤,不过自行车厂负责人会把这些过程全部安排妥当,不需要共享单车机构负责人关心
2.5.2、涉及角色
- 指挥者类(Director):指导组装顺序和组装步骤,不关心实现细节,不做具体事情
- 抽象建造者类(Builder):只是说明需要所有需要做的事项,不做具体事情
- 具体建造者类(ConcreteBuilder):具体实现自己所做事项,不关心其他事项
- 产品类(Product):指挥者类的最终目的是组装产品类
拿上面举例的自行车制造过程来说,其中厂长就是指挥者类,它们只负责按照客户需求和组装顺序去说明组装产品的步骤;而工厂主管就是抽象建造者,他将向所有相关车间说明什么车间需要做什么;而生产车间就是具体建造者,他们根据上级命令来制造自行车的相关零部件,或者执行组装车辆的具体操作,类图如下:
2.5.3、优缺点
优点: 制造过程清晰,分工明确,用户使用方便,扩展方便,符合开闭原则
缺点: 要求产品建造过程基本一致,没有很多差异,否则不适合用建造者模式
2.5.4、实现方式
代码:
public class Client {
public static void main(String[] args) {
Director director = new Director(new MobikeBuilder());
System.out.println(director.construct());
}
}
/**
* 指挥者类
*/
class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public BaiKe construct() {
builder.buildFrame();
builder.buildSeat();
return builder.createBaiKe();
}
}
/**
* 抽象建造者类
*/
abstract class Builder {
/** 自行车产品 **/
protected BaiKe baiKe = new BaiKe();
/**
* 制作车架
*/
public abstract void buildFrame();
/**
* 制造车座
*/
public abstract void buildSeat();
/**
* 组装自行车
*/
public abstract BaiKe createBaiKe();
}
/**
* 具体建造者类1
*/
class MobikeBuilder extends Builder {
@Override
public void buildFrame() {
baiKe.setFrame("Mobike车架");
}
@Override
public void buildSeat() {
baiKe.setSeat("Mobike车座");
}
@Override
public BaiKe createBaiKe() {
return baiKe;
}
}
/**
* 具体建造者类2
*/
class OfoBuilder extends Builder {
@Override
public void buildFrame() {
baiKe.setFrame("Ofo车架");
}
@Override
public void buildSeat() {
baiKe.setSeat("Ofo车座");
}
@Override
public BaiKe createBaiKe() {
return baiKe;
}
}
/**
* 产品
*/
class BaiKe {
private String frame;
private String seat;
public String getFrame() {
return frame;
}
public void setFrame(String frame) {
this.frame = frame;
}
public String getSeat() {
return seat;
}
public void setSeat(String seat) {
this.seat = seat;
}
@Override
public String toString() {
return "BaiKe{" +
"frame='" + frame + '\'' +
", seat='" + seat + '\'' +
'}';
}
}
结果:
BaiKe{frame='Mobike车架', seat='Mobike车座'}
2.5.5、模式对比
2.5.5.1、和工厂方法模式、抽象工厂模式对比
- 工厂方法模式 VS 建造者模式:工厂方法模式注重产品整体的形成,而建造者模式注重细节操作步骤划分
- 抽象工厂模式 VS 建造者模式:抽象工厂模式注重产品族(一系列不同类型产品)的生产,而建造者模式注重单个产品的生产过程
3、结构型模式
说明:
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。
3.1、代理模式
3.1.1、概念
当某类A不适合或者不能作为直接引用目标的时候,那就需要提供代理类B进行增强
代理类分为静态代理和动态代理,其中静态代理的代理类在编译期生成,而动态代理在程序运行时才会生成
动态代理分为JDK代理和CGLIB代理两种,其中JDK动态代理要求类A需要实现接口,并且实现对应方法,毕竟JDK动态类B需要实现接口并且重写方法的;而CGLIB动态代理需要引入cglib依赖,并且要求类A和被代理方法不能是final修饰的,毕竟CGLIB是创建类A的动态代理子类B并重写对应方法的
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
3.1.2、涉及角色
- 抽象接口(Subject):定义抽象方法,其中JDK需要,而CGLIB不需要
- 真实类(Real Subject):实现抽象方法
- 代理类(Proxy):实现抽象方法,进行方法增强
3.1.3、使用场景
- 保护代理:可以使用代理,为不同用户提供不同访问权限
3.1.4、实现方式
说明: 以下代码编写基于这么一个场景,我需要去北京出差,但是没有时间去火车站买票,因此雇佣了一个跑腿小哥代我去买火车站买票
3.1.4.1、静态代理
代码:
public class Test {
public static void main(String[] args) {
ErrandBoy boy = new ErrandBoy();
boy.sellTicket();
}
}
/**
* 抽象接口
*/
interface SellTicketSystem {
public void sellTicket();
}
/**
* 真实类
*/
class TrainStation implements SellTicketSystem {
@Override
public void sellTicket() {
System.out.println("在火车站购买火车票");
}
}
/**
* 代理类——跑腿小哥
*/
class ErrandBoy implements SellTicketSystem {
private TrainStation station = new TrainStation();
@Override
public void sellTicket() {
System.out.println("跑腿小哥前往火车站买票……");
station.sellTicket();
System.out.println("跑腿小哥将火车票交给顾客。");
}
}
结果:
跑腿小哥前往火车站买票……
在火车站购买火车票
跑腿小哥将火车票交给顾客。
3.1.4.2、JDK动态代理
代码:
public class Test {
public static void main(String[] args) {
SellTicketSystem proxyObject = ProxyFactory.getProxyObject();
proxyObject.sellTicket();
}
}
/**
* 抽象接口
*/
interface SellTicketSystem {
public void sellTicket();
}
/**
* 真实类
*/
class TrainStation implements SellTicketSystem {
@Override
public void sellTicket() {
System.out.println("在火车站购买火车票");
}
}
/**
* 动态代理类——跑腿小哥
*/
class ProxyFactory {
private static TrainStation station = new TrainStation();
public static SellTicketSystem getProxyObject() {
SellTicketSystem s = (SellTicketSystem) Proxy.newProxyInstance(
// 类加载器
station.getClass().getClassLoader(),
// 实现接口
station.getClass().getInterfaces(),
/**
* proxy:代理对象
* method:真实方法实例
* args:真实参数
*/
(Object proxy, Method method, Object[] args) -> {
System.out.println("跑腿小哥前往火车站买票……");
Object result = method.invoke(station, args);
System.out.println("跑腿小哥将火车票交给顾客。");
return result;
});
return s;
}
}
结果:
跑腿小哥前往火车站买票……
在火车站购买火车票
跑腿小哥将火车票交给顾客。
3.1.4.3、CGLIB动态代理
引入依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
代码:
public class Test {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory();
TrainStation proxy = factory.getProxyObject();
proxy.sellTicket();
}
}
/**
* 真实类
*/
class TrainStation {
public void sellTicket() {
System.out.println("在火车站购买火车票");
}
}
/**
* 动态代理类——跑腿小哥
*/
class ProxyFactory implements MethodInterceptor {
private static TrainStation station = new TrainStation();
public TrainStation getProxyObject() {
// 创建Enhancer对象,类似于JDK动态代理中的Proxy类
Enhancer enhancer = new Enhancer();
// 设置父类字节码对象
enhancer.setSuperclass(TrainStation.class);
// 设置回调函数
enhancer.setCallback(this);
// 创建代理对象
TrainStation t = (TrainStation) enhancer.create();
return t;
}
/**
* 回调方法
* @param obj 代理对象
* @param method 真实方法实例
* @param args 真实参数
* @param proxy 代理方法实例
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("跑腿小哥前往火车站买票……");
Object result = proxy.invokeSuper(obj, args);
System.out.println("跑腿小哥将火车票交给顾客。");
return result;
}
}
结果:
跑腿小哥前往火车站买票……
在火车站购买火车票
跑腿小哥将火车票交给顾客。
3.1.4.4、对比
3.1.4.4.1、静态代理和动态代理
相比来说动态代理更好,即使真实类中的方法很多,但是动态代理类只需要写一次,而静态代理类需要写很多代码,另外即使增加真实类中的方法,动态代理类也不需要进行任何改变
3.1.4.4.2、JDK代理和CGLIB代理
- JDK代理要求真实类继承接口
- CGLIB代理使用ASM字节码技术生成代理类,需要引入cglib依赖,生成的动态代理类相当于真实类的子类,因此要求真实类和方法都不能被final修饰
- 在JDK1.6和1.7的时候,CGLIB代理效率更高,但是在JDK1.8中,JDK代理效率更高
3.2、适配器模式
3.2.1、通俗解释
我现在有A,但是我想要的是B,不过我还不想改动A,然后将A变成B的过程就是适配器模式
3.2.2、概述
生活中将A变成B的例子比比皆是,比如手机充电器就是如此,插座输出电压是220V,而手机只能接受5V电压,所以手机充电器就是适配器模式的最好体现,并且手机充电器的说明上也写的很清楚,如下:
电脑充电器和手机充电器的作用类似,我们不在详细说明,直接看图片:
3.2.3、涉及角色
- 目标接口:期望变成这种样子的接口,也就是B的一方,可以是接口或者抽象类
- 适配器类:继承或者实现目标抽象类/接口,然后可以继承适配者实现类,或者组合/聚合适配者类,把适配者接口的方法转换成目标接口方法,这就是把A变成B的过程
- 适配者接口(被适配):需要被适配的一方,期待变成B的一方,也就是A这一方,不一定存在
- 适配者实现类(被适配):实现适配者接口,做A这一方需要做的具体事情
3.2.4、使用场景
- 新系统想用老系统的功能,但是不想修改老系统,可以使用适配器类进行适配,这个情况我是遇到过的,公司有一套用户系统,但是已经成型了,并且权限在母公司,因此我们不能进行修改,所以公司领导就搞了一个适配项目用来进行适配,名称就叫做adapter-XXX,其实这个项目就是一个适配器
- 第三方提供的组件不符合我们的要求,我们需要使用适配器类将之改成成我们想要的样子
3.2.5、实现方式
3.2.5.1、类适配器模式
说明: 适配器类 继承 适配者实现类
代码:
public class Test {
public static void main(String[] args) {
B5VClass b = new B5VClass();
b.acceptPower();
}
}
/**
* 适配者接口:220V电压接口
*/
interface A220VInterface {
public void supplyPower();
}
/**
* 适配者接口实现类:220V电压接口实现类
*/
class A220VClass implements A220VInterface {
@Override
public void supplyPower() {
System.out.println("插座输出220V电压");
}
}
/**
* 目标接口:5V电压接口
*/
interface B5VInterface {
public void acceptPower();
}
/**
* 目标接口实现类:5V电压接口实现类
*/
class B5VClass implements B5VInterface {
/** 组合适配器 **/
private Adapter adapter = new Adapter();
@Override
public void acceptPower() {
// 适配器转换电压
adapter.acceptPower();
// 产生5V电压
System.out.println("手机接收到5V电压");
}
}
/**
* 适配器类
* 说明:继承了适配者接口实现类A220VClass,所以叫做类适配器
*/
class Adapter extends A220VClass implements B5VInterface {
@Override
public void acceptPower() {
supplyPower();
System.out.println("适配器转换电压,从220V=>5V");
}
}
结果:
插座输出220V电压
适配器转换电压,从220V=>5V
手机接收到5V电压
3.2.5.2、对象适配器模式
说明: 通过组合或者聚合方式,将适配者实现类加入适配器类中
代码:
public class Test {
public static void main(String[] args) {
B5VClass b = new B5VClass();
b.acceptPower();
}
}
/**
* 适配者接口:220V电压接口
*/
interface A220VInterface {
public void supplyPower();
}
/**
* 适配者接口实现类:220V电压接口实现类
*/
class A220VClass implements A220VInterface {
@Override
public void supplyPower() {
System.out.println("插座输出220V电压");
}
}
/**
* 目标接口:5V电压接口
*/
interface B5VInterface {
public void acceptPower();
}
/**
* 目标接口实现类:5V电压接口实现类
*/
class B5VClass implements B5VInterface {
/** 组合适配器 **/
private Adapter adapter = new Adapter();
@Override
public void acceptPower() {
// 适配器转换电压
adapter.acceptPower();
// 产生5V电压
System.out.println("手机接收到5V电压");
}
}
/**
* 适配器类
* 说明:组合了适配者接口实现类A220VClass对象,所以叫做对象适配器
*/
class Adapter implements B5VInterface {
/** 组合适配者接口实现类对象 **/
private A220VInterface a = new A220VClass();
@Override
public void acceptPower() {
a.supplyPower();
System.out.println("适配器转换电压,从220V=>5V");
}
}
结果:
插座输出220V电压
适配器转换电压,从220V=>5V
手机接收到5V电压
3.2.5.3、接口适配器模式
说明: 一个接口中存在很多抽象方法,但是我们不像使用那么多抽象方法,因此我们可以写一个抽象类来实现接口,那么我们只需要继承这个抽象方法,然后根据需要实现抽象类中的方法就可以了
代码:
public class Test {
public static void main(String[] args) {
C c = new C();
c.a();
}
}
/**
* 适配者接口
*/
interface A {
public void a();
public void b();
public void c();
}
/**
* 实现适配者接口所有方法的适配器类
*/
abstract class B implements A {
@Override
public void a() {}
@Override
public void b() {}
@Override
public void c() {}
}
/**
* 目标类:继承适配器类,重写所需方法
**/
class C extends B {
@Override
public void a() {
System.out.println("a()=>执行相关代码");
}
}
接口:
a()=>执行相关代码
3.2.6、使用案例
3.2.6.1、StreamDecoder将字符数组转换成整数值
其中Reader是目标接口,然后char[]是需要适配的,而StreamDecodeer是适配器类,用于将字符数组变成整数值
3.3、装饰者模式
3.3.1、通俗解释
接口或者抽象类已经存在多个子类A,但是其他类B也想使用所有这些子类A,但是又不想为每个子类编写一个类,因此我们在子类B中引入子类A的接口或者抽象类,其实这也是“依赖倒转原则”的一种体现
3.3.2、概念
不改变现有类结构的情况下,动态地给类增加一些功能
3.3.3、涉及角色
- 抽象构件:准备接收附加责任的接口或者抽象类
- 具体构件:实现抽象构件,通过装饰角色为其增加一些职责
- 抽象装饰:继承或者实现抽象构件,并将抽象构建实例聚合进来,然后来拓展抽象构建功能,抽象装饰不一定存在,当不存在的时候,抽象装饰相关的功能将会放到具体装饰上
- 具体装饰:实现抽象装饰的相关方法,并对具体构件对象添加附加责任
3.3.4、实现方式
代码:
public class Test {
public static void main(String[] args) {
Bacon bacon = new Bacon(new Egg(new Noodle()));
System.out.println("食物:" + bacon.info());
System.out.println("价格:" + bacon.cost() + "元");
}
}
/**
* 抽象构建角色
*/
abstract class FastFood {
/**
* 价格
**/
private Integer price;
/**
* 描述
**/
private String desc;
/**
* 总价格
*/
public Integer cost() {
return getPrice();
}
/**
* 总描述
*/
public String info() {
return getDesc();
}
public FastFood() {
}
public FastFood(Integer price, String desc) {
this.price = price;
this.desc = desc;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
/**
* 具体构件角色1
*/
class Rice extends FastFood {
public Rice() {
super(10, "米饭");
}
}
/**
* 具体构件角色2
*/
class Noodle extends FastFood {
public Noodle() {
super(5, "面条");
}
}
/**
* 抽象装饰角色
*/
abstract class Garnish extends FastFood {
private FastFood fastFood;
public Garnish(Integer price, String desc, FastFood fastFood) {
super(price, desc);
this.fastFood = fastFood;
}
@Override
public Integer cost() {
return getPrice() + fastFood.cost();
}
@Override
public String info() {
return getDesc() + fastFood.info();
}
}
/**
* 具体装饰角色1
*/
class Bacon extends Garnish {
public Bacon(FastFood fastFood) {
super(2, "培根", fastFood);
}
}
/**
* 具体装饰角色2
*/
class Egg extends Garnish {
public Egg(FastFood fastFood) {
super(1, "鸡蛋", fastFood);
}
}
结果:
食物:培根鸡蛋面条
价格:8元
UMl类图:
3.3.5、使用案例
代码:
public class Test {
public static void main(String[] args) {
BufferedWriter writer = null;
try {
FileWriter fileWriter = new FileWriter("C:\\Users\\mingming\\Desktop\\1.txt", true);
writer = new BufferedWriter(fileWriter);
writer.write("123");
writer.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
UML类图:
解释:
Reader
的子类不仅有FileWriter
,还有OutputStreamWriter
等,也是可以在BufferedWriter
中使用的
3.3.6、模式对比
2.5.5.1、和代理模式进行对比
- 相同点:实现和目标类相同的业务接口(特指JDK动态代理);在代理类或者装饰类中都要声明目标对象(特指JD动态代理);在不修改目标类的前提下增强目标方法
- 不同点:目的不同,其中装饰者是为了增强目标对象,而代理模式是为了保护和隐藏对象;代理对象构建的地方不同,装饰模式是一般使用聚合方式传入,而代理模式一般使用组合方式设置
3.4、桥接模式
3.3.1、通俗解释
现在存在两个维度,这两种维度可以组成一个类,如果使用继承的方式,那么会造成类爆炸,另外在扩展的时候,需要改名增加很多类,比较麻烦,比如我需要创建不同形状的图形,并且图形可以有多种颜色,这就存在两个维度,分别是形状和颜色,如果采用继承的思路,类爆炸UML类图如下:
根据合成复用原则,能用组合或者聚合就不用继承,而桥接模式就是基于这种思想的,使用桥接模式解决问题思路的UML类图如下:
3.3.2、概念
将接口和抽象类分开,使他们可以独立变化,使用聚合方式代替继承,将接口聚合到抽象类中进行工作,符合合成复用原则,降低了接口和抽象类两个维度的耦合度
3.3.3、涉及角色
- 实现化接口(维度之一):供扩展抽象类调用
- 实现化接口实现类:给出实现化接口的具体实现
- 扩展抽象类(维度之一):聚合实现化接口,定义抽象方法
- 扩展抽象类子类:继承扩展抽象类,可以实现化接口实现类来做事情
3.3.4、优缺点
- 优点:桥接模式提高了系统的可扩展性,符合开闭原则,无论哪个维度进行扩展,都不用改变原有代码
3.3.5、使用场景
- 当一个类中存在两个独立变化的维度,并且这两个维度都需要扩展时
- 当我方不希望通过继承或者多层继承导致类的个数急剧增加时
3.3.6、实现方式
代码:
public class Test {
public static void main(String[] args) {
Windows windows = new Windows(new Mp4());
windows.play();
}
}
/**
* 实现化接口(维度之一)
*/
interface Format {
public void decode();
}
/**
* 实现化接口实现类1
*/
class Mp4 implements Format {
@Override
public void decode() {
System.out.println("使用mp4格式播放视频");
}
}
/**
* 实现化接口实现类2
*/
class Avi implements Format {
@Override
public void decode() {
System.out.println("使用avi格式播放视频");
}
}
/**
* 扩展抽象类(维度之一)
*/
abstract class OperationSystem {
protected Format format;
public OperationSystem(Format format) {
this.format = format;
}
public abstract void play();
}
/**
* 扩展抽象类子类1
*/
class Windows extends OperationSystem {
public Windows(Format format) {
super(format);
}
@Override
public void play() {
System.out.println("当前是Windows操作系统");
format.decode();
}
}
/**
* 扩展抽象类子类2
*/
class Mac extends OperationSystem {
@Override
public void play() {
}
public Mac(Format format) {
super(format);
}
}
结果:
当前是Windows操作系统
使用mp4格式播放视频
3.5、外观模式
3.5.1、通俗解释
单个系统之间的类,或者多个系统之间,简称A调用B,如果A中需要写很多B相关的代码,显得非常耦合冗余,那我们就可以让B提供一个外观类 / 外观方法供我们调用,然后实现对应功能,这样A就不用关心B到底是怎么实现的了,并且会降低系统的复杂性,提高程序的可维护性,并且B中这个接口是可以复用的,这符合迪米特法则,毕竟没啥关系的就不要强关联了,最好用外观类来提供统一采用入口,演变过程图中左侧是不使用外观类的情况,右侧是使用外观类的情况,如下:
3.5.2、涉及角色
- 外观角色:为多个子系统提供统一接口(该接口用于综合多个子系统的功能,让客户系统调用更明了,该谁做的就让谁去做)
- 子系统角色:实现子系统的相关功能,客户端只需要调用外观角色,然后外观角色来调用子系统角色进行具体操作
3.5.3、优缺点
- 优点:使用外观角色可以降低客户系统和子系统的耦合度,让子系统的具体功能改变不影响客户系统的使用;减少了客户端系统调用的复杂性
- 缺点:不符合开闭原则,如果子系统需要接入外观角色中,需要更改外观类代码
3.5.4、使用场景
- 当A系统想调用一个B系统的某功能时,发现该功能是由多个子功能组成的,目前没有该功能的接口,我们可以使用外观模式创建接口方法让A系统调用
- 当客户系统与多个子系统存在很大关联,但是该客户系统不是专门处理系统关联关系的,我们可以创建外观系统来提供统一接口供客户系统使用,然后外观系统来和多个子系统打交道
3.5.5、实现方式
说明: 假设我们家里安装了很多小米电器,比如空调、电视机、灯,但是每天晚上回去都需要开空调、开电视机、开灯,然后睡觉的时候还需要关闭他们,这实在太麻烦了,我们可以借助于小米音响来完成这些操作,我们发出语音指令就可以完成对应的电器开关工作,其中小米音响就是外观模式的一个案例
代码:
public class Test {
public static void main(String[] args) {
XiaoMiSmartBox smartBox = new XiaoMiSmartBox();
smartBox.receive("打开电器");
System.out.println("================================");
smartBox.receive("关闭电器");
}
}
/**
* 外观角色:小米音箱
*/
class XiaoMiSmartBox {
private Light light = new Light();
private AirConditioner airConditioner = new AirConditioner();
private Television television = new Television();
/**
* 外观角色中统一方法
*/
public void receive(String message) {
if (message.contains("打开")) {
openAll();
System.out.println("电器已经打开了");
} else if (message.contains("关闭")) {
closeAll();
System.out.println("电器已经关闭了");
} else {
System.out.println("小米同学没有听懂呢!");
}
}
private void openAll() {
light.open();
airConditioner.open();
television.open();
}
private void closeAll() {
light.close();
airConditioner.close();
television.close();
}
}
/**
* 子系统1
*/
class Light {
public void open() {
System.out.println("开启电灯");
}
public void close() {
System.out.println("关闭电灯");
}
}
/**
* 子系统2
*/
class AirConditioner {
public void open() {
System.out.println("开启空调");
}
public void close() {
System.out.println("关闭空调");
}
}
/**
* 子系统3
*/
class Television {
public void open() {
System.out.println("开启电视");
}
public void close() {
System.out.println("关闭电视");
}
}
结果:
开启电灯
开启空调
开启电视
电器已经打开了
================================
关闭电灯
关闭空调
关闭电视
电器已经关闭了
3.6、组合模式
3.6.1、概念
把多个类似对象抽象为单一对象(比如目录和文件都是文件),并且使用树形结构来组装对象(目录里面有很多文件(文件以及目录)),如下图:
3.6.2、涉及角色
- 抽象根节点:定义类似对象的共有属性和方法,可以预定义默认属性和方法(抽象根节点例如下图中的MenuComponent)
- 树枝节点:树枝节点类似于目录,使用List集合存储抽象子节点(树枝节点例如下图中的Menu,其中
List<MenuComponent>
就是抽象子节点集合) - 叶子节点:叶子节点类似于文件,不存储抽象子节点(叶子节点例如下图中的MenuItem)
3.6.3、优缺点
- 优点:增加树枝节点或者叶子节点的时候不需要增加什么,符合开闭原则
3.6.4、组合模式分类
- 透明组合模式
在透明组合模式中,抽象根节点中声明了所有管理成员对象的方法,比如下图中的add、remove、getChild方法,这样做的好处是所有构建类都有相同的接口,这是标准组合模式,但是透明组合模式不够安全,比如叶子节点根本不存在add、remove、getChild方法,如果在运行时调用就抛出异常
- 安全组合模式
在透明组合模式中,抽象根节点中不声明管理成员对象的方法,而是在树枝节点中声明管理成员对象的方法,当然在叶子节点中就不声明管理成员对象的方法了,如下图:
3.6.5、实现方式
代码:
/**
* 抽象根节点
**/
abstract class MenuComponent {
/** 名称 **/
protected String name;
/** 级别 **/
protected int level;
/**
* 添加菜单
* @param menuComponent 抽象根节点对象
*/
public void add(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}
/**
* 移除菜单
* @param menuComponent 抽象根节点对象
*/
public void remove(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}
/**
* 获取子节点对象
*/
public MenuComponent getChild(int i){
throw new UnsupportedOperationException();
}
/**
* 获取菜单名称
*/
public String getName(){
return name;
}
/**
* 打印根节点信息
*/
public void print(){
throw new UnsupportedOperationException();
}
}
/**
* 树枝节点
**/
class Menu extends MenuComponent {
/** 抽象根节点对象集合 **/
private List<MenuComponent> menuComponentList;
public Menu(String name,int level){
this.level = level;
this.name = name;
menuComponentList = new ArrayList<MenuComponent>();
}
@Override
public void add(MenuComponent menuComponent) {
menuComponentList.add(menuComponent);
}
@Override
public void remove(MenuComponent menuComponent) {
menuComponentList.remove(menuComponent);
}
@Override
public MenuComponent getChild(int i) {
return menuComponentList.get(i);
}
@Override
public void print() {
for (int i = 1; i < level; i++) {
System.out.print("--");
}
System.out.println(name);
for (MenuComponent menuComponent : menuComponentList) {
menuComponent.print();
}
}
}
/**
* 叶子节点
*/
class MenuItem extends MenuComponent {
public MenuItem(String name,int level) {
this.name = name;
this.level = level;
}
@Override
public void print() {
for (int i = 1; i < level; i++) {
System.out.print("--");
}
System.out.println(name);
}
}
3.6.6、使用案例
3.6.6.1、公司用到的目录结构树类
说明: 上面这种格式来做目录确实不是很好,毕竟还需要区分树枝节点和叶子节点,我拿出我们公司的一个树形类例子吧,代码如下:
public class TreeView implements Serializable {
private static final long serialVersionUID = 1L;
// 目录id
protected String id;
// 目录名称
protected String caption;
// 是否有子节点
private boolean hasChild = true;
// 子级目录树
private List<TreeView> childs;
// 是否折叠
private boolean unfold = false;
// 是否被选中
private boolean selected = false;
// 常规图标
private String icon;
// 展开图标
private String unfoldIcon;
// 折叠图标
private String foldIcon;
public TreeView() {
}
public TreeView(String caption) {
this.caption = caption;
}
public TreeView(String id, String caption) {
this.id = id;
this.caption = caption;
}
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public String getCaption() {
return this.caption;
}
public void setCaption(String caption) {
this.caption = caption;
}
public boolean isHasChild() {
return this.hasChild;
}
public void setHasChild(boolean hasChild) {
this.hasChild = hasChild;
}
public boolean isUnfold() {
return this.unfold;
}
public void setUnfold(boolean unfold) {
this.unfold = unfold;
}
public String getIcon() {
return this.icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public List<TreeView> getChilds() {
return this.childs;
}
public void setChilds(List<TreeView> childs) {
this.childs = childs;
}
public void setChild(TreeView child) {
if (this.childs == null) {
this.childs = new ArrayList();
}
this.childs.add(child);
}
public String getUnfoldIcon() {
return this.unfoldIcon;
}
public void setUnfoldIcon(String unfoldIcon) {
this.unfoldIcon = unfoldIcon;
}
public String getFoldIcon() {
return this.foldIcon;
}
public void setFoldIcon(String foldIcon) {
this.foldIcon = foldIcon;
}
public boolean isSelected() {
return this.selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
}
3.7、享元模式
3.7.1、通俗解释
我想使用一些东西,但是现在不用,未来会用到,但是你现在需要给我准备出来,当我用到的时候直接让我可以使用
3.7.2、涉及角色
- 抽象享元角色:通常是接口或者抽象类,在里面声明公共方法,方法用来提供内部状态给外部
- 具体享元角色:实现/继承抽象享元角色,也被称作享元对象
- 享元工厂角色:负责创建和管理享元角色,并且可以用方法获取享元角色
3.7.3、优缺点
- 优点:多次创建相同对象的情况下使用享元模式,减少内存空间浪费,增强系统运行效率
- 缺点:需要使用持续存在的享元池来维护享元对象,这需要占用系统资源,因此只有在多次重复使用享元对象的时候,才能使用享元模式
3.7.4、使用场景
- 系统中多次创建相同或者类似的对象,造成内存空间浪费,拖慢系统运行效率,可以使用享元模式进行优化
3.7.5、实现方式
背景:
在俄罗斯方块游戏中,方块好像是取之不尽的,其实方块的种类也就那几种,需要哪一种就获取哪一种即可,可以使用享元模式来实现,我们在代码中I形、L形、O形(四方块形)方块演示享元模式
UML类图:
代码:
public class Test {
public static void main(String[] args) {
AbstractBox box = BoxFactory.getBox("I");
System.out.println("形状:" + box.getShape());
}
}
/**
* 享元工厂
**/
class BoxFactory {
/** 存储享元角色 **/
private static final Map<String, AbstractBox> boxMap = new HashMap<>();
/**
* 提前创建享元角色
*/
static {
IBox iBox = new IBox();
LBox lBox = new LBox();
OBox oBox = new OBox();
boxMap.put(iBox.getShape(), iBox);
boxMap.put(lBox.getShape(), lBox);
boxMap.put(oBox.getShape(), oBox);
}
/**
* 根据类型获取享元角色
* @param shape 类型
**/
public static AbstractBox getBox(String shape) {
return boxMap.get(shape);
}
}
/**
* 抽象享元角色
**/
abstract class AbstractBox {
public abstract String getShape();
}
/**
* 具体享元角色1
**/
class IBox extends AbstractBox {
@Override
public String getShape() {
return "I";
}
}
/**
* 具体享元角色2
**/
class LBox extends AbstractBox {
@Override
public String getShape() {
return "L";
}
}
/**
* 具体享元角色3
**/
class OBox extends AbstractBox {
@Override
public String getShape() {
return "O";
}
}
结果:
形状:I
3.7.6、使用案例
3.7.6.1、Integer.valueOf()方法
分析:
-128~127
之间的Integer对象经常被用到,如果每次使用都去创建,未免太浪费空间了,因此我们提前把-128~127
之间的数据放在缓存数组中,需要用到的时候直接使用就好
代码:
public final class Integer extends Number implements Comparable<Integer> {
/**
* 根据i值获取Integer对象,其中-128~127是已经在数组中准备好了,拿来即可用
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
/**
* 生成缓存数组cache
*/
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
}
4、行为型模式
4.1、模板方法模式
4.1.1、通俗解释
几件类似的事情,步骤都是那么几步,只是某些步骤的执行细节不一样,但是大部分执行细节是相同的,这种情况下可以把具体步骤写在模板方法中,而不一样的细节定义为抽象方法,让子类去进行重写即可
4.1.2、涉及角色
- 抽象类:定义模板方法和基本方法;
- 抽象类——模板方法:定义基本方法执行顺序,避免子类重写该方法,所以一般把该方法定义为final修饰的方法;
- 抽象类——基本方法——抽象方法:不同部分写成抽象方法交给子类实现
- 抽象类——基本方法——具体方法:抽象类中具体方法已经写好了默认实现,如果子类感觉不合适可以重新父类具体方法
- 抽象类——基本方法——钩子方法:方法返回值一般是布尔值,用于逻辑判断,比如判断子类重写的抽象方法返回值是否符合要求,该方法一般用在抽象类——模板方法中
- 具体子类:实现抽象类——基本方法——抽象方法,也可以重写抽象类——基本方法——具体方法
4.1.3、优缺点
- 优点:将相同代码放在父类中,提供代码复用性;子类重写父类抽象方法,父类在模板方法中调用重写之后的抽象方法,达到子类方法影响父类模板方法的执行效果,实现了反向控制,符合开闭原则
- 缺点:需要定义多个子类,导致类数量庞大;子类方法将反向控制父类模板方法的执行结果,导致代码难以阅读
4.1.4、使用场景
所做事情的每个小步骤以及步骤执行顺序基本上已经固定了,但是部分小步骤可能会发生变化,这就可以使用模板方法模式,可能变化的小步骤定义为抽象方法,在抽象父级中定义模板方法来按照步骤完成事情
4.1.5、实现方法
说明: 我们现在准备炒两个菜,分别是土豆炒青椒和西红柿炒鸡蛋,其实炒菜的步骤基本上是倒油、倒菜、翻炒,其中倒油和翻炒的步骤是一样的,但是倒菜不一样,毕竟两种菜不一样
代码:
public class Test {
public static void main(String[] args) {
AbstractClass food = new tuDouAndQingJiao();
food.cookStep();
}
}
/**
* 抽象类
*/
abstract class AbstractClass {
/**
* 模板方法
*/
public final void cookStep() {
this.pourOil();
this.pourVegetables();
this.stirFry();
}
/**
* 基本方法——具体方法1
*/
protected void pourOil() {
System.out.println("倒油");
};
/**
* 基本方法——抽象方法1
*/
protected abstract void pourVegetables();
/**
* 基本方法——具体方法2
*/
protected void stirFry() {
System.out.println("翻炒");
};
}
/**
* 具体子类1
**/
class tuDouAndQingJiao extends AbstractClass {
@Override
protected void pourVegetables() {
System.out.println("倒入土豆和青椒");
}
}
/**
* 具体子类2
**/
class xiHongShiAndJiDan extends AbstractClass {
@Override
protected void pourVegetables() {
System.out.println("倒入西红柿和鸡蛋");
}
}
结果:
倒油
倒入土豆和青椒
翻炒
4.1.6、使用案例
4.1.6.1、InputStream中的read()方法
代码:
public class Test {
public static void main(String[] args) {
InputStream in = null;
try {
in = new FileInputStream("C:\\test\\20220713\\test.txt");
byte[] bytes = new byte[4];
int read;
// 注意:此处使用了InputStream的read()方法
while ((read=in.read(bytes))!=-1){
System.out.print(new String(bytes,0,read));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in!=null){
try {
in.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
}
结果:
test
源码分析:
进入java.io.InputStream#read(byte[])
方法,该方法中调用了java.io.InputStream#read(byte[], int, int)
,该方法中调用了抽象方法java.io.InputStream#read()
,该抽象方法的实现方法是FileInputStream类中的read()方法,这就抽成了模板方法模式,其中java.io.InputStream#read(byte[])
是模板方法,java.io.InputStream#read()
是抽象方法,FileInputStream类中的read()方法是实现方法,这就是模板方法模式,用图描述该过程如下:
4.1.6.2、公司案例
在公司一个项目中,我需要去调用三方厂商接口去获取百科图谱数据,但是获取图谱数据需要经过多个流程,分别是:①获取token(根据用户名称和密码) ②根据fileId获取词条实体id ③根据实体id获取图谱数据
在之前交付给单位A的项目中使用的是图谱公司A,后来客户看了图谱公司A的实现效果,觉得图谱公司A的呈现效果不好,所以让我们在未来交付给其他单位的图谱进行升级,然后要求我们使用图谱公司B,经过和图谱公司B的协商,我们决定继续沿用原有图谱公司A的接口实现思路,但是部分流程可能会早轻微改变;
对于已经交付到单位A的项目,我们依然使用图谱公司A,但是后续交付其他单位的项目,我们需要使用图谱公司B,但是客户的品味也会变化,所以我们依然在一套代码中完成和图谱公司A、图谱公司B的对接工作,因此我们使用模板方法模式+简单工厂模式(对接图谱公司不多)来完成该功能的改造。
4.2、策略模式
4.2.1、通俗解释
很多时候对于同样的事情,我们会面临多种选择,比如在十字路口选择往哪边走、开发人员选择什么IDE工具、消息中间件选择RabbitMQ还是Kafka呢,反正很多很多了,我们可以选择任意一种,这就是策略模式
4.2.2、概念
不同的计算方法放在不同的类中,用户端可以随意选择所用的算法类,即使算法类发生了变化,也不会影响客户的使用
4.2.3.、涉及角色
- 抽象策略角色:一般是抽象类或者接口,定义抽象方法
- 具体策略角色:实现抽象类或者接口中的抽象方法,用于具体实现,具体策略类一般有多个
4.2.4、优缺点
- 优点:新增策略角色方便,符合开闭原则;避免多次if判断,符合面向对象思想
- 缺点:客户端必须清除所用的策略类;策略类对象可能产生很多,出现该情况可以使用享元模式减少对象数量
4.2.5、使用场景
- 一个系统实现同样的事情,但是面临多种选择的时候
- 多种行为被定义,但是都写在一个类中通过if来区分,我们可以把if判断体用具体策略角色来代替
- 我们系统需要提供给别人使用,并且有多个选项可以供人选择,但是我们还想把方法实现隐藏在我们系统内部,不想暴露给用户
- 多个类只是具体行为不同,但是所做事情相同,可以升级为策略模式
4.2.6、实现方法
代码:
public class Test {
public static void main(String[] args) {
MqServer mqServer = new RabbitMqServer();
mqServer.bindUser("01");
}
}
/**
* 抽象策略角色
*/
interface MqServer {
// 绑定用户
void bindUser(String... userIds);
// 绑定群组
void bindGroup(String... groupIds);
// 绑定群组用戶
void bindGroupUser(String groupId, String... userIds);
}
/**
* 具体策略角色1
*/
class KafkaMqServer implements MqServer{
@Override
public void bindUser(String... userIds) {
System.out.println("使用kafka绑定用户");
}
@Override
public void bindGroup(String... groupIds) {
System.out.println("使用kafka绑定群组");
}
@Override
public void bindGroupUser(String groupId, String... userIds) {
System.out.println("使用kafka绑定群组用戶");
}
}
/**
* 具体策略角色2
*/
class RabbitMqServer implements MqServer{
@Override
public void bindUser(String... userIds) {
System.out.println("使用rabbitmq绑定用户");
}
@Override
public void bindGroup(String... groupIds) {
System.out.println("使用rabbitmq绑定群组");
}
@Override
public void bindGroupUser(String groupId, String... userIds) {
System.out.println("使用rabbitmq绑定群组用戶");
}
}
结果:
使用rabbitmq绑定用户
4.2.7、使用案例
4.2.7.1、Comparator源码解析
代码:
public class Test {
public static void main(String[] args) {
// 抽象策略角色
// Comparator接口
// 具体策略角色1
Comparator<String> stringComparator = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
};
// 具体策略角色2
Comparator<Integer> intgerComparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
// 数据准备
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(3);
list.add(2);
list.add(0);
// 数据排序
Collections.sort(list, intgerComparator);
// 打印数据
for (Integer integer : list) {
System.out.println(integer);
}
}
}
结果;
0
1
2
3
4.3、命令模式
4.3.1、通俗解释
抽象命令角色是具体命令角色的上级,接受者在具体命令角色中,保证自己会被调用到,而调用者只需要告诉具体命令角色执行命令即可
我们拿淘宝买商品举例,店铺老板说我需要找一家快递合作,然后有顺丰、中通、韵达等可以选择,假设老板选择了顺丰,那么顺丰的作用就是把快递送到客户手上,然后让顾客签收
其中店铺老板就是调用者,快递商就是抽象命令角色,而顺丰快递就是其中一个具体命令角色,然后顾客就是接受者;其中店铺老板只管把快递就给顺丰,然后告诉顺丰需要把快递送到武汉市硚口区XXX小区XXX手上就好了,店铺老板不用管顺丰怎么送的,只要送到客户手上就好了,客户只需要把快递签收就完成了
4.3.2、概念
将一个请求封装成一个命令对象,通过命令对象将调用者和接收者分开,并且可以通过命令对象进行联系,并且命令对象扩展也比较方便,符合开闭原则
4.3.3、涉及角色
- 抽象命令角色:声明执行的命令方法,一般是接口或者抽象类
- 具体命令角色:实现 / 继承抽象命令角色,重写抽象命令方法,通常将接受者当做成员变量,然后在重写的命令方法中调用接收者的方法来完成功能
- 命令调用者:在调用方法中调用具体命令角色的具体命令方法,通常将具体命令对象当做成员变量,然后在调用方法中调用具体命令方法
- 命令接收者:接收具体命令角色的调用,并执行相应的功能
总结: 命令调用者中有具体命令角色,可以调用具体命令方法,然后具体命令角色中有命令接收者,可以调用真正功能方法
4.3.4、优缺点
- 优点:将命令调用者和命令接收者解耦;增加或者删除命令方法,或者增加具体命令角色,不会影响命令调用者和命令接收者;
- 缺点:具体命令角色可能会过多;类结构更加复杂;
4.3.5、使用场景
需要将请求调用者和请求接收者解耦,可以使用命令模式
4.3.6、实现方法
说明:
下面来说明一个饭店点餐的例子,每一桌的顾客都会点不同的食物,份数也不一样,然后服务员负责记录每一桌顾客的菜单,之后把菜单告诉后厨工作人员,然后后厨工作人员会安排大厨做出对应的饭菜
代码:
public class Test {
public static void main(String[] args) {
// 准备订单
String number1 = "1";
Map<String, Integer> foodMap1 = new HashMap<>();
foodMap1.put("胡辣汤", 2);
foodMap1.put("油条", 2);
foodMap1.put("小笼包", 1);
Order order1 = new Order(number1, foodMap1);
String number2 = "2";
Map<String, Integer> foodMap2 = new HashMap<>();
foodMap2.put("豆腐脑", 2);
foodMap2.put("油条", 2);
foodMap2.put("水煎包", 1);
Order order2 = new Order(number2, foodMap2);
// 准备具体命令对象集合
OrderCommand orderCommand1 = new OrderCommand(new Chef(), order1);
OrderCommand orderCommand2 = new OrderCommand(new Chef(), order2);
List<OrderCommand> orderCommandList = new ArrayList<>();
orderCommandList.add(orderCommand1);
orderCommandList.add(orderCommand2);
// 准备命令调用者
Waiter waiter = new Waiter(orderCommandList);
// 调用命令调用者中的方法
waiter.notice();
}
}
/**
* 抽象命令角色
*/
interface Command {
/**
* 抽象命令方法
*/
public void execute();
}
/**
* 具体命令角色
*/
class OrderCommand implements Command {
/** 命令接收者对象 **/
private Chef chef;
/** 额外对象 **/
private Order order;
public OrderCommand(Chef chef, Order order) {
this.chef = chef;
this.order = order;
}
/**
* 实现命令方法
*/
@Override
public void execute() {
System.out.println("》》》" + order.getNumber() + "号桌的菜开始烹饪");
Set<Map.Entry<String, Integer>> entries = order.getFoodMap().entrySet();
for (Map.Entry<String, Integer> entry : entries) {
String name = entry.getKey();
Integer number = entry.getValue();
// 调用命令接收者的执行方法进行具体操作
chef.cook(name, number);
}
System.out.println("》》》" + order.getNumber() + "号桌的菜结束烹饪\n");
}
}
class Waiter {
/** 具体命令对象集合 **/
public List<OrderCommand> commandList = new ArrayList<>();
public Waiter(List<OrderCommand> commandList) {
this.commandList = commandList;
}
public void notice() {
for (OrderCommand orderCommand : commandList) {
// 调用具体命令对象中方法
orderCommand.execute();
}
}
}
/**
* 命令接收者
*/
class Chef {
public void cook(String name, Integer number) {
System.out.println(number + "份“" + name + "”已经做好了……");
}
}
/**
* 额外业务类
*/
class Order {
/** 订单编号 **/
private String number;
/** 食物清单 **/
private Map<String, Integer> foodMap = new HashMap<>();
public Order(String number, Map<String, Integer> foodMap) {
this.number = number;
this.foodMap = foodMap;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public Map<String, Integer> getFoodMap() {
return foodMap;
}
public void setFoodMap(Map<String, Integer> foodMap) {
this.foodMap = foodMap;
}
}
结果:
》》》1号桌的菜开始烹饪
1份“小笼包”已经做好了……
2份“油条”已经做好了……
2份“胡辣汤”已经做好了……
》》》1号桌的菜结束烹饪
》》》2号桌的菜开始烹饪
2份“油条”已经做好了……
2份“豆腐脑”已经做好了……
1份“水煎包”已经做好了……
》》》2号桌的菜结束烹饪
4.3.7、使用案例
4.3.7.1、抽象命令对象Runnable和命令调用者Thread
代码:
public class Test {
public static void main(String[] args) {
// 准备具体命令角色对象
RunnableCommand runnableCommand = new RunnableCommand(new Receiver());
// 准备命令调用者对象
Thread thread = new Thread(runnableCommand);
// 调用具体命令,操作命令调用者对象中的run()方法,进而调用命令接收者中的receive()方法
thread.start();
}
}
/**
* 具体命令对象
*/
class RunnableCommand implements Runnable {
/** 命令接收者对象 **/
Receiver receiver;
public RunnableCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void run() {
// 调用真正执行方法
receiver.receive("Hello World!");
}
}
/**
* 命令接收者
*/
class Receiver {
public void receive(String info) {
System.out.println("接收者已经接收到信息,内容如下:" + info);
}
}
结果:
接收者已经接收到信息,内容如下:Hello World!
解释:
在上例中,Runnable是抽象命令角色,RunnableCommand是具体命令角色,Thread是命令调用者,Receiver是命令接收者
4.4、责任链模式
4.4.1、通俗解释
工作中的请假、报销等流程都需要经过多人审批,但是审批人的审批程度不同,比如张三是个小领导,他只能审批1000块钱以下的报销,现在有一个1万块的报销单,他经过判断发现无法审批,所以直接给老板进行审批,这就是一个责任链模式,也就是你能处理你就处理,你不能处理你就交给上级领导处理
4.4.2、概念
避免多个请求处理者耦合在一起,我们让当前处理者记录下一个处理者,如果当前处理者能处理那就直接处理,否则就把请求交给下一个处理者,这样将多个请求处理者分成了一条链上的多个节点,解除了请求处理者之间的耦合
对于请求发送者来说,只需要将请求交给第一个处理者,然后让请求沿着处理者链往下执行即可,直到有处理者可以处理请求为止,这样请求发送者不用关心到底谁来处理请求,毕竟程序会自动分配的
4…4.3、涉及角色
- 抽象处理者角色:一般是抽象类,包含一个抽象方法和一个判断方法(判断具体处理者自己处理或者交给上级处理者处理)
- 具体处理者角色:实现或者继承抽象处理者角色,重写抽象方法
- 客户类角色:创建处理链,并向第一个处理者发出判断请求,它无需关心处理的细节,只关心处理结果
4.4.4、优缺点
- 优点:降低请求处理者之间的耦合;增强系统的可扩展性,虽然我们可以把下一个处理者定死,但是也可以在客户类中通过set方法进行动态指定;简化请求处理者之间的连接,毕竟当前处理者只用关心自己下一个处理者是谁,并且可以少些很多if else语句;符合单一职责原则,只处理自己能力范围之内的事情,如果处理不了,那就交给下一个处理者进行处理
- 缺点:如果执行链较长,判断请求处理可能要点时间;职责链的合理性也可以让用户自己来设置(比如客户类中定义某处理者对象的下一个处理者),这样可能会导致执行链不能完美连通,甚至会造成循环调用
4.4.5、实现方式
代码:
public class Test {
public static void main(String[] args) {
LeaveRequest request = new LeaveRequest("李华", 5, "事假");
// 创建处理链
Manager manager = new Manager();
// 向链头的具体执行者提交请求
manager.judge(request);
}
}
/**
* 抽象处理者角色
*/
abstract class Handler {
/** 当前对象可处理的最小请假天数 **/
private Integer minNum;
/** 当前对象可处理的最大请假天数 **/
private Integer maxNum;
/** 上级领导 **/
private Handler nextHandler;
public Handler(Integer minNum, Integer maxNum, Handler nextHandler) {
this.minNum = minNum;
this.maxNum = maxNum;
this.nextHandler = nextHandler;
}
/**
* 处理请假条
* @param leaveRequest 请假条对象
*/
public abstract void handleLeaveRequest(LeaveRequest leaveRequest);
/**
* 判断请假条
* @param leaveRequest 请假条对象
*/
public final void judge(LeaveRequest leaveRequest) {
// 请假天数 <= 0
if (leaveRequest.getNumber() <= 0) {
System.out.println("请假天数不符合要求,系统自动拦截!");
return;
}
// 请假天数 > 当前处理人最大可处理天数
if (leaveRequest.getNumber() > maxNum && nextHandler != null) {
// 交给上级领导处理
nextHandler.judge(leaveRequest);
return;
}
// 请假天数符合当前处理区间
if (leaveRequest.getNumber() >= minNum && leaveRequest.getNumber() <= maxNum) {
this.handleLeaveRequest(leaveRequest);
return;
}
// 目前请假人为空,当前请假天数 > 最大天数,无法处理
System.out.println("系统天数最多不能超过" + maxNum + "天,系统自动拦截!");
}
}
/**
* 具体处理者角色1
*/
class Manager extends Handler {
public Manager() {
super(1, 3, new Boss());
}
@Override
public void handleLeaveRequest(LeaveRequest leaveRequest) {
System.out.println("部门经理同意" + leaveRequest.getName() + "请假" + leaveRequest.getNumber() + "天。");
}
}
/**
* 具体处理者角色2
*/
class Boss extends Handler {
public Boss() {
super(4, 7, null);
}
@Override
public void handleLeaveRequest(LeaveRequest leaveRequest) {
System.out.println("老板同意" + leaveRequest.getName() + "请假" + leaveRequest.getNumber() + "天。");
}
}
/**
* 请假条
*/
class LeaveRequest {
/** 请假人姓名 **/
private String name;
/** 请假天数 **/
private Integer number;
/** 请假原因 **/
private String reason;
public LeaveRequest(String name, Integer number, String reason) {
this.name = name;
this.number = number;
this.reason = reason;
}
public String getName() {
return name;
}
public Integer getNumber() {
return number;
}
public String getReason() {
return reason;
}
}
结果:
老板同意李华请假5天。
4.4.6、使用案例
4.4.6.1、模拟FilterChain处理器执行链
/**
* 客户类
*/
public class Test {
public static void main(String[] args) {
FilterChain filterChain = new FilterChain();
FirstFilter firstFilter = new FirstFilter();
SecondFilter secondFilter = new SecondFilter();
// 将处理器添加到处理器执行链中,并定义处理器执行链执行顺序
filterChain.addFilter(firstFilter).addFilter(secondFilter);
// 处理器链开始执行
filterChain.doFilter(null, null);
}
}
/**
* 抽象处理者角色
* 说明:模拟javax.servlet.Filter
*/
interface Filter {
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain);
}
/**
* 具体处理者角色1
*/
class FirstFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
System.out.println("FirstFilter前置处理……");
// 通知处理器链执行下一个
chain.doFilter(request, response);
System.out.println("FirstFilter后置处理……");
}
}
/**
* 具体处理者角色2
*/
class SecondFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
System.out.println("SecondFilter前置处理……");
// 通知处理器链执行下一个
chain.doFilter(request, response);
System.out.println("SecondFilter后置处理……");
}
}
/**
* 抽象处理者角色的延伸
* 说明:模拟org.mortbay.jetty.servlet.ServletHandler.Chain,
* 该类是javax.servlet.FilterChain接口的实现类,我们直接写接口就行了,
* 就不写那么麻烦了
*/
class FilterChain {
/** 定义当前处理器下标 **/
private int _filter = 0;
/** 定义处理器链 **/
private List<Filter> _chain = new ArrayList<>();
/** 添加处理器到处理器链中 **/
public FilterChain addFilter(Filter filter) {
_chain.add(filter);
return this;
}
/**
* 判断具体哪个处理器要执行方法
* @param request 请求
* @param response 响应
*/
public void doFilter(ServletRequest request, ServletResponse response) {
if (_filter == _chain.size()) {
return;
}
// 获取当前处理器
Filter filter = _chain.get(_filter++);
// 执行处理器内容
filter.doFilter(request, response, this);
}
}
结果:
FirstFilter前置处理……
SecondFilter前置处理……
SecondFilter后置处理……
FirstFilter后置处理……
4.4.6.2、将京东零售-asyncTool项目改造成责任链模式
目前京东零售-asyncTool项目会在类A中声明前面执行的类和后面执行的类,这其实和责任链模式很像,毕竟责任链也是声明前后执行类,因此可以改造
4.5、状态模式
4.5.1、通俗解释
状态多种多样,并且状态和动作相互对应,执行下一步动作需要取决于当前状态,这就需要用到状态模式
4.5.2、概念
定义状态对象,把判断逻辑放在状态对象中,定义环境对象,记录当前状态对象,并且把环境对象传递到当前状态对象中,当前状态对象需要改变状态的时候,只需要告诉环境对象,现在需要改变成某种状态,你去调用该状态对象即可
4.5.3、涉及角色
- 环境角色:维护当前状态对象,并将操作委托给当前状态对象去处理
- 抽象状态角色:定义接口,用以定义状态对象行为
- 具体状态角色:实现各种行为
4.5.4、优缺点
- 优点:将状态方法变成了状态类,结构上更加清晰;
- 缺点:状态类的个数增多了;对开闭原则依然支持很差,比如再增加一个状态,那么需要改变抽象状态角色和所有具体状态角色,以及环境角色,变动还是很大的;
如果不使用状态模式,可以使用判断来完成效果,下面模拟电梯门的开关状态、电梯运行停止状态为例来说明,如下:
public interface ILift {
//电梯的4个状态
//开门状态
public final static int OPENING_STATE = 1;
//关门状态
public final static int CLOSING_STATE = 2;
//运行状态
public final static int RUNNING_STATE = 3;
//停止状态
public final static int STOPPING_STATE = 4;
//设置电梯的状态
public void setState(int state);
//电梯的动作
public void open();
public void close();
public void run();
public void stop();
}
public class Lift implements ILift {
private int state;
@Override
public void setState(int state) {
this.state = state;
}
//执行关门动作
@Override
public void close() {
switch (this.state) {
case OPENING_STATE:
System.out.println("电梯关门了。。。");//只有开门状态可以关闭电梯门,可以对应电梯状态表来看
this.setState(CLOSING_STATE);//关门之后电梯就是关闭状态了
break;
case CLOSING_STATE:
//do nothing //已经是关门状态,不能关门
break;
case RUNNING_STATE:
//do nothing //运行时电梯门是关着的,不能关门
break;
case STOPPING_STATE:
//do nothing //停止时电梯也是关着的,不能关门
break;
}
}
//执行开门动作
@Override
public void open() {
switch (this.state) {
case OPENING_STATE://门已经开了,不能再开门了
//do nothing
break;
case CLOSING_STATE://关门状态,门打开:
System.out.println("电梯门打开了。。。");
this.setState(OPENING_STATE);
break;
case RUNNING_STATE:
//do nothing 运行时电梯不能开门
break;
case STOPPING_STATE:
System.out.println("电梯门开了。。。");//电梯停了,可以开门了
this.setState(OPENING_STATE);
break;
}
}
//执行运行动作
@Override
public void run() {
switch (this.state) {
case OPENING_STATE://电梯不能开着门就走
//do nothing
break;
case CLOSING_STATE://门关了,可以运行了
System.out.println("电梯开始运行了。。。");
this.setState(RUNNING_STATE);//现在是运行状态
break;
case RUNNING_STATE:
//do nothing 已经是运行状态了
break;
case STOPPING_STATE:
System.out.println("电梯开始运行了。。。");
this.setState(RUNNING_STATE);
break;
}
}
//执行停止动作
@Override
public void stop() {
switch (this.state) {
case OPENING_STATE: //开门的电梯已经是是停止的了(正常情况下)
//do nothing
break;
case CLOSING_STATE://关门时才可以停止
System.out.println("电梯停止了。。。");
this.setState(STOPPING_STATE);
break;
case RUNNING_STATE://运行时当然可以停止了
System.out.println("电梯停止了。。。");
this.setState(STOPPING_STATE);
break;
case STOPPING_STATE:
//do nothing
break;
}
}
}
public class Client {
public static void main(String[] args) {
Lift lift = new Lift();
lift.setState(ILift.STOPPING_STATE);//电梯是停止的
lift.open();//开门
lift.close();//关门
lift.run();//运行
lift.stop();//停止
}
}
4.5.5、使用场景
- 当一个对象的行为取决于它的状态,并且需要根据当前状态来改变它的行为,可以考虑使用状态模式
- 一个操作中含有庞大的分支结构,并且这些分支取决于对象状态
4.5.6、实现方式
说明: 下面实现电梯门的开关状态、电梯运行停止状态的控制,各种状态各司其职,能处理就处理,不能处理就抛给其他处理者
代码:
public class Test {
public static void main(String[] args) throws Exception {
CloseState state = new CloseState();
Context context = new Context();
context.setLiftState(state);
context.close();
context.run();
context.stop();
context.run();
}
}
abstract class LiftState {
/** 环境角色对象,用来让当前状态对象和当前环境角色对象通信 **/
protected Context context;
public void setContext(Context context) {
this.context = context;
}
/**
* 电梯开门动作
*/
public abstract void open();
/**
* 电梯关门动作
*/
public abstract void close();
/**
* 电梯运行动作
*/
public abstract void run();
/**
* 电梯停止动作
*/
public abstract void stop();
}
/**
* 电梯开门状态对象
*/
class OpenState extends LiftState {
@Override
public void open() {
System.out.println("打开电梯中……");
System.out.println("电梯门已经打开……");
}
@Override
public void close() {
// 告诉环境角色对象,下一个状态是电梯关门状态,其他类似,不在说明
context.setLiftState(Context.CLOSE_STATE);
// 让电梯关门状态对象执行电梯关门动作,其他类似,不在说明
context.close();
}
@Override
public void run() {
System.out.println("电梯门已经打开,无法运行");
}
@Override
public void stop() {
System.out.println("电梯门已经打开,已经停止");
}
}
/**
* 电梯关门状态对象
* 说明:默认处于停止关门状态
*/
class CloseState extends LiftState {
@Override
public void open() {
context.setLiftState(Context.OPEN_STATE);
context.open();
}
@Override
public void close() {
System.out.println("关闭电梯门中……");
System.out.println("电梯门已经关闭……");
}
@Override
public void run() {
context.setLiftState(Context.RUN_STATE);
context.run();
}
@Override
public void stop() {
System.out.println("电梯门已经关闭,已经停止");
}
}
/**
* 电梯运行状态对象
*/
class RunState extends LiftState {
@Override
public void open() {
System.out.println("电梯正在运行,无法开门");
}
@Override
public void close() {
System.out.println("电梯状态运行,电梯门已经关闭");
}
@Override
public void run() {
System.out.println("启动电梯中……");
System.out.println("电梯正在运行……");
}
@Override
public void stop() {
context.setLiftState(Context.STOP_STATE);
context.stop();
}
}
/**
* 电梯停止状态对象
* 说明:默认关门停止状态
*/
class StopState extends LiftState {
@Override
public void open() {
context.setLiftState(Context.OPEN_STATE);
context.open();
}
@Override
public void close() {
System.out.println("电梯门已经关闭,无需操作");
}
@Override
public void run() {
context.setLiftState(Context.RUN_STATE);
context.run();
}
@Override
public void stop() {
System.out.println("停止电梯中……");
System.out.println("电梯已经停止运行……");
}
}
/**
* 环境角色对象
*/
class Context {
// 电梯开门状态对象
public static final OpenState OPEN_STATE = new OpenState();
// 电梯关门状态对象
public static final CloseState CLOSE_STATE = new CloseState();
// 电梯运行状态对象
public static final RunState RUN_STATE = new RunState();
// 电梯停止状态对象
public static final StopState STOP_STATE = new StopState();
// 当前状态对象
private LiftState liftState;
// 设置当前状态对象,并通知当前状态对象 》 环境角色对象是谁
public void setLiftState(LiftState liftState) {
this.liftState = liftState;
this.liftState.setContext(this);
}
// 下面这几种状态都需要当前状态对象去执行
public void open() {
liftState.open();
}
public void close() {
liftState.close();
}
public void run() {
liftState.run();
}
public void stop() {
liftState.stop();
}
}
结果:
关闭电梯门中……
电梯门已经关闭……
启动电梯中……
电梯正在运行……
停止电梯中……
电梯已经停止运行……
启动电梯中……
电梯正在运行……
4.6、观察者模式
4.6.1、概念
观察者模式又叫做发布-订阅模式, 它定义了一对多的依赖关系 。让多个观察者同时监听一个被观察者,当这个被观察者发生变化时,会通知所有的观察者,然后让观察者响应被观察者的变化。
4.6.2、涉及角色
- 抽象被观察者:定义管理观察者集合的方法、通知观察者的方法
- 具体被观察者:将观察者集合当做成员变量,重写管理观察者集合的方法、通知观察者的方法(说明:该方法用于调用观察者集合中所有观察者的通知方法,完成通知功能)
- 抽象观察者:定义通知方法
- 具体被观察者:重写通知方法,准备被具体被观察者调用
4.6.3、优缺点
- 优点:
解耦合、易扩展:观察者和被观察者可以实现接口/继承抽象类,所以两者的耦合性大大降低;另外新添加的观察者只用实现对应的接口/继承抽象类,然后重写对应方法,所以扩展简单方便
建立触发机制 :当被观察者的状态发生变化的时候,然后就可以将状态的变化通知到其他的观察者,这就建立了一套触发机制 - 缺点
解除耦合不彻底:观察者模式在一定程度上解除了耦合,但是耦合依然存在,毕竟在被观察者实现类中依然存在观察者对象集合
通知方式不灵活:被观察者不支持对特定观察者对象的特定方法进行通知,所以通知方式不够灵活
4.6.4、使用场景
当一个对象中的状态发生改变,然后肯定要影响其他地方做出相应的改变,但是并不知道有多少地方需要做出调整,这就需要用到观察者模式
4.6.5、实现方式
说明: 考试结束之后,老师需要把学生成绩信息发送给学长和家长,我们使用观察者模式来模拟这个过程
类图:
代码:
public class Test {
public static void main(String[] args) {
Subject teacher = new Teacher();
ObServer parent = new Parent();
ObServer student = new Student();
teacher.register(parent);
teacher.register(student);
teacher.sendNotice("李华本次考试成绩很棒!!!");
}
}
/**
* 被观察者接口
*/
interface Subject {
public void register(ObServer o);
public void sendNotice(String msg);
}
/**
* 被观察者实现类
*/
class Teacher implements Subject {
private List<ObServer> obServers = new ArrayList<>();
@Override
public void register(ObServer o) {
obServers.add(o);
}
@Override
public void sendNotice(String msg) {
for (ObServer o : obServers) {
o.notify(msg);
}
}
}
/**
* 观察者接口
*/
interface ObServer {
public void notify(String msg);
}
/**
* 观察者实现类1
*/
class Parent implements ObServer {
@Override
public void notify(String msg) {
System.out.println("李华家长接收到消息,消息内容:" + msg);
}
}
/**
* 观察者实现类2
*/
class Student implements ObServer {
@Override
public void notify(String msg) {
System.out.println("学生李华接收到消息,消息内容:" + msg);
}
}
结果:
李华家长接收到消息,消息内容:李华本次考试成绩很棒!!!
学生李华接收到消息,消息内容:李华本次考试成绩很棒!!!
4.6.6、使用案例
4.6.6.1、使用观察者模式监听公司知识文档的发布、撤销、删除等状态
4.6.6.1.1、说明
公司的监听回调就是典型的观察者模式
4.6.6.1.2、涉及模块
- com.xxx.kms.core.read
- com.xxx.kms.topic
注意:com.xxx.kms.core.read模块在下面简称"知识模块",而com.xxx.kms.topic模块在下面简称"专题模块"
4.6.6.1.3、上述实现方式代码和公司代码对比
4.6.6.1.3.1、被观察者接口
知识模块中的com.xxx.kms.core.read.service.impl.KnowledgeServiceImpl类充当被观察者接口和对应实现类,其中execStatusEvent方法就是状态改变通知方法;而下面的代码相当于被观察者实现类中的观察者对象集合,在tomcat启动的时候会直接通过配置文件找到所有对应对象,并放到集合中,所以不需要手动赋值了 。
代码对比截图如下(左边是上面"一、观察者模式"中的示例代码,右边是公司代码):
4.6.6.1.3.2、观察者接口
知识模块中的com.leadal.kms.core.read.event.KnowledgeStatusEvent接口就是观察者接口,代码对比截图如下(左边是上面"一、观察者模式"中的示例代码,右边是公司代码):
4.6.6.1.3.3、观察者实现类
知识模块中的com.xxx.kms.core.read.event.lis.KnowledgeStatusListener、专题模块中的com.xxx.kms.qb.event.lis.KnowledgeStatusListener都是观察者的实现类,我们本次以观察者实现类1和知识模块中的KnowledgeStatusListener为例来说明,代码对比截图如下(左边是上面"一、观察者模式"中的示例代码,右边是公司代码):
4.6.6.2、Observable类(抽象被观察者)和Observer接口(抽象观察者)定义了观察者模式
4.6.6.2.1、背景说明
依然是模拟上面的案例,只是采用Observable类(抽象被观察者)和Observer接口(抽象观察者)完成功能
4.6.6.2.2、Observable类和Observer 接口说明
Observable类:
Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
- void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。
- void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。
- void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。
Observer 接口:
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作。
4.6.6.2.3、代码编写
代码:
import java.util.Observable;
import java.util.Observer;
public class Test {
public static void main(String[] args) {
Teacher teacher = new Teacher();
Observer parent = new Parent();
Observer student = new Student();
teacher.addObserver(parent);
teacher.addObserver(student);
// notifyObservers()方法正确执行,需要保证changed是true,但是changed默认是false,此处作用就是设置changed为true
teacher.setChanged();
teacher.notifyObservers("李华本次考试成绩很棒!!!");
}
}
/**
* 被观察者实现类
*/
class Teacher extends Observable {
@Override
protected void setChanged() {
super.setChanged();
}
}
/**
* 观察者实现类1
*/
class Parent implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("李华家长接收到消息,消息内容:" + arg);
}
}
/**
* 观察者实现类2
*/
class Student implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("学生李华接收到消息,消息内容:" + arg);
}
}
结果:
学生李华接收到消息,消息内容:李华本次考试成绩很棒!!!
李华家长接收到消息,消息内容:李华本次考试成绩很棒!!!
4.6.6.3、AbstractAuthenticator类(抽象被观察者,来自shiro框架)
4.6.6.3.1、背景说明
在看若依中用到的shiro框架时,发现里面使用到了观察者模式,目前没有发现官方写的观察者,只有被观察者,当用户登录成功或者登录失败的时候,观察者就能收到请求,不过需要继承AbstractAuthenticator接口
4.6.6.3.2、代码截图
4.6.6.4、org.apache.shiro.session.mgt.AbstractNativeSessionManager类(抽象被观察者,来自shiro框架)
4.6.6.4.1、代码截图
4.6.7、扩展
4.6.7.1、事件委托
4.6.7.1.1、概念
将委托者需要做的事情,交给被委托者去做
4.6.7.1.2、优点
解决观察者模式中的缺点
4.6.7.1.3、应用场景
当一个对象中的状态发生改变,然后肯定要影响其他地方做出相应的改变,但是并不知道有多少地方需要做出调整,有可能这些地方方法是不同的,那么观察者模式就不能完成任务了,所以就需要使用事件委托了
4.6.7.1.4、举例
说明: 考试结束之后,老师需要把学生成绩发送给学长和家长,但是通知学生和家长的方式和信息是不同的,我们在上面实现方式代码基础上进行改造
代码:
public class Test {
public static void main(String[] args) {
Score score = new Score("全科", 750.00);
String msg = "李华本次考试成绩很棒!!!";
Subject teacher = new Teacher();
Event parent = new Event("message", new Parent(), score);
Event student = new Event("phone", new Student(), msg);
teacher.register(parent);
teacher.register(student);
teacher.sendNotice();
}
}
/**
* 被委托类
*/
class Event {
private String methodName;
private Object object;
private Class[] paramTypes;
private Object[] params;
public Event(String methodName, Object object, Object... params) {
this.methodName = methodName;
this.object = object;
this.params = params;
createParamsType(params);
}
private void createParamsType(Object[] params) {
if (params != null) {
this.paramTypes = new Class[params.length];
for (int i = 0; i < params.length; i++) {
paramTypes[i] = params[i].getClass();
}
}
}
public void invoke() {
try {
if (object != null) {
Method method = object.getClass().getMethod(methodName, paramTypes);
if (method != null) {
method.invoke(object, params);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 被观察者接口
*/
interface Subject {
public void register(Event e);
public void sendNotice();
}
/**
* 被观察者实现类
*/
class Teacher implements Subject {
private List<Event> events = new ArrayList<>();
@Override
public void register(Event e) {
events.add(e);
}
@Override
public void sendNotice() {
for (Event e : events) {
e.invoke();
}
}
}
/**
* 观察者接口
*/
interface ObServer {
public void message(Score s);
public void phone(String msg);
}
/**
* 观察者实现类1
*/
class Parent implements ObServer {
@Override
public void message(Score s) {
System.out.println("李华家长接收到消息,科目:" + s.getSubject() + ";分数:" + s.getFraction());
}
@Override
public void phone(String msg) {
}
}
/**
* 观察者实现类2
*/
class Student implements ObServer {
@Override
public void phone(String msg) {
System.out.println("学生李华接收到消息,消息内容:" + msg);
}
@Override
public void message(Score s) {
}
}
/**
* 成绩类
*/
class Score {
// 科目
private String subject;
// 分数
private Double fraction;
public Score(String subject, Double fraction) {
this.subject = subject;
this.fraction = fraction;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public Double getFraction() {
return fraction;
}
public void setFraction(Double fraction) {
this.fraction = fraction;
}
}
结果:
李华家长接收到消息,科目:全科;分数:750.0
学生李华接收到消息,消息内容:李华本次考试成绩很棒!!!
4.7、中介者模式
4.7.1、通俗解释
就拿租房这事来说,存在房东和租客两方,其中房东和租客是多对多的关系,这种情况下房东和租客都累,毕竟一个人需要和多个人打交道,但是房产中介出现之后就不一样了。
对于房东来说,我们只用把房子交给房产中介,房产中介会去帮我们寻找客户;
对于租客来说,我们只用把我们的租房需求告诉房产中介,房产中介自然会为我们寻找合适的房子;
这样来看房东和租客都省事了,那就让房东少赚点,租客多出点,也让房产中介得点甜头,好像也不过分
4.7.2、概念
又叫调停模式,定义中介角色来封装一系列对象之间的交互,解除原有对象之间的耦合,那么所有对象只需要和中介者角色有关系就可以了,中介者去联系所有对象
4.7.3、涉及角色
- 抽象中介者角色:设定接收消息的方法
- 具体中介者角色:继承或者实现抽象中介者,定义集合对象来管理具体顾客类,实现接收消息的方法,并且协调顾客对象之间的交互关系
- 抽象顾客角色:定义和中介者交互的方法,比如接收方法和发送方法
- 具体顾客角色:继承或者实现抽象顾客角色,只需要和中介者进行交互,不需要顾客之间进行交互
4.7.4、优缺点
- 优点:松耦合,对象之间直接交互变成对象和中介者的交互,减少对象之间的耦合;集中控制交互,对象之间的交互写在对象代码里面,但是现在对象之间的交互写在中介者里面,当需要改变逻辑的时候,只需要改变中介者的逻辑就行了;对于顾客角色来说,其中多对多关联变成一对一关联,即每一个顾客角色只需要和中介者联系就行了,中介者承担承担了全部压力,减少了顾客对象的压力,减少顾客对象之间的耦合;
- 缺点:当顾客角色变得非常多的时候,中介者角色的代码将变得很多很复杂,甚至导致需要难以维护
4.7.5、使用场景
- 对象之间引用关系复杂
- 需要创建一个类运行于多个类之间
4.7.6、实现方式
说明:
代码:
public class Test {
public static void main(String[] args) {
ActualMediator mediator = new ActualMediator();
HomeOwner homeOwner = new HomeOwner("李阿姨", mediator);
Tenant tenant = new Tenant("小王", mediator);
mediator.addHomeowner(homeOwner);
mediator.addTenant(tenant);
tenant.send("有没有三室一厅的房子?");
homeOwner.send("想租三室一厅房子吗?");
}
}
/**
* 抽象中介者角色
*/
abstract class AbstractMediator {
public abstract void receive(Customer customer, String message);
}
/**
* 实际中介者角色
*/
class ActualMediator extends AbstractMediator {
/** 房东集合 **/
private List<HomeOwner> homeOwners = new ArrayList<>();
/** 租客集合 **/
private List<Tenant> tenants = new ArrayList<>();
/**
* 添加房东
* @param homeowner 房东
*/
public void addHomeowner(HomeOwner homeowner) {
homeOwners.add(homeowner);
}
/**
* 添加租客
* @param tenant 租客
*/
public void addTenant(Tenant tenant) {
tenants.add(tenant);
}
/**
* 接收来自房东和租客的消息
* @param customer 顾客对象
* @param message 消息
*/
@Override
public void receive(Customer customer, String message) {
// 房东
if (customer instanceof HomeOwner) {
// 通知所有租客
for (Tenant tenant : tenants) {
tenant.receive(message);
}
}
// 租客
else {
// 通知所有房东
for (HomeOwner homeowner : homeOwners) {
homeowner.receive(message);
}
}
}
}
/**
* 抽象顾客角色:可以是房东,也可以是租客
*/
abstract class Customer {
/** 顾客名称 **/
protected String name;
/** 中介者对象 **/
protected AbstractMediator abstractMediator;
public Customer(String name, AbstractMediator abstractMediator) {
this.name = name;
this.abstractMediator = abstractMediator;
}
/**
* 发送消息到中介者
* @param message 消息内容
*/
public abstract void send(String message);
/**
* 接收来自中介者的消息
* @param message 消息
*/
public abstract void receive(String message);
}
/**
* 具体顾客角色1:房东
*/
class HomeOwner extends Customer {
public HomeOwner(String name, AbstractMediator abstractMediator) {
super(name, abstractMediator);
}
@Override
public void send(String message) {
abstractMediator.receive(this, message);
}
@Override
public void receive(String message) {
System.out.println("房东接收到中介者的消息,消息内容:" + message);
}
}
/**
* 具体顾客角色2:租客
*/
class Tenant extends Customer {
public Tenant(String name, AbstractMediator abstractMediator) {
super(name, abstractMediator);
}
@Override
public void send(String message) {
abstractMediator.receive(this, message);
}
@Override
public void receive(String message) {
System.out.println("租客接收到中介者的消息,消息内容:" + message);
}
}
结果:
房东接收到中介者的消息,消息内容:有没有三室一厅的房子?
租客接收到中介者的消息,消息内容:想租三室一厅房子吗?
4.8、迭代器模式
4.8.1、概念
提供一个迭代器类来访问聚合对象中的数据集合,增加一种遍历数据的方式
4.8.2、涉及角色
- 抽象聚合角色:存储聚合元素的成员变量,提供添加、删除聚合元素的方法,以及获取迭代器对象的方法
- 具体聚合角色:继承或者实现抽象聚合角色中的各种方法
- 抽象迭代器角色:定义判断是否还存在聚合元素、获取下一个聚合元素、删除聚合元素的方法,比如hasNext()、next()、remove()等
- 具体迭代器角色:继承或者实现抽象聚合角色中的各种方法
4.8.3、优缺点
- 优点:增加了另外一个遍历聚合元素集合的方式;减轻了聚合类的功能;避免暴露聚合对象的内部细节
- 缺点:在聚合角色的基础上,又增加了迭代器角色,增加了系统的复杂度
4.8.4、使用场景
- 当需要为聚合对象提供多种遍历方式时
- 当需要为不同的聚合结构提供统一遍历接口时
- 当需要遍历聚合对象,但是不想暴露聚合迭代内部细节时
4.8.5、实现方式
说明: 本次一改常态,毕竟最好的例子就是Iterator迭代器,所以我们就拿它来分析一下迭代器模式
代码:
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
System.out.println(next);
}
}
}
结果:
1
2
3
分析:
角色分析:按照上面的设计角色,其中List接口就是抽象聚合角色,ArrayList就是具体聚合角色,Iterator是抽象迭代器角色,ArrayList.Itr是具体迭代器角色
接口分析:List接口中存在add(E e)
、iterator()
方法;ArrayList实现类中实现了List接口中的抽象方法;Iterator接口中存在hasNext()
、remove()
、next()
方法;ArrayList.Itr实现类中实现了Iterator接口中的抽象方法。
4.9、访问者模式
4.9.1、通俗解释
现在部分类,这些类的方法基本上已经非常完善了,但是我们可能要经常对这些类进行方法组合实现对象功能,这个时候就可以使用访问者模式,我们想要什么功能,那就加一个什么类型的访问者,之后去访问类中的具体方法就可以了,所以访问者模式的具体作用就是根据我们的需要操作已经成型的类中方法,不在类中添加额外方法
4.9.2、概念
封装操作某类的方法,在不改变该类的前提下操作该类中的方法
4.9.3、涉及角色
- 抽象访问者角色:定义对每一个具体元素对象的访问方法,一般会定义一个抽象方法,方法参数就是当前元素对象
- 具体访问者角色:给出对每一个元素对象的具体操作步骤
- 抽象元素角色:定义操作访问者的方法,其中方法的参数就是抽象访问者角色
- 具体元素角色:实现抽象元素角色中的抽象方法,然后方法中调用操作访问者角色的方法
- 对象结构角色:定义抽象元素对象集合,用于存储所有需要被访问的具体元素,然后定义添加方法来添加具体元素;并且可以遍历元素集合,并且调用具体元素中接收访问者方法的代码,相当于可以让我们批量完成操作了
4.9.4、优缺点
- 优点:符合单一职责原则,访问者的作用就是来操作具体元素中的方法的,这就是它的职责,实现了在不改变具体元素方法的前提下,可以实现具体元素方法的组合调用;扩展性良好,元素类可以接受不同的访问者来完成不同的操作,毕竟不同的访问者可以调用不同的方法嘛。
- 缺点:如果想新增具体元素类是比较麻烦的,因为我们需要在抽象访问者角色定义一个具体元素的抽象操作方法,并且让所有的具体访问者角色重写该操作方法,所以新增元素类比较麻烦,需要调整访问者角色结构
4.9.5、使用情况
- 一个接口下面有很多个实现类,想对这些实现类进行一些基于它们方法的操作,但是又不想把代码写到实现类里面
- 需要使用元素类中的多个方法来完成操作,但是又不想把代码写在元素类里面,毕竟不像污染元素类
- 当元素方法被很多应用共享的时候,我们可以通过访问者类来完成具体的功能,然后应用只需要去调用元素中方法的时候传入对应的访问者类就可以了
- 元素类的结构几乎很少变化,但是我们需要操作元素类中的多个方法,然后实现一个操作
4.9.6、实现方式
代码:
public class Test {
public static void main(String[] args) {
// 创建元素对象
Element element1 = new ConcreteElement1();
Element element2 = new ConcreteElement2();
// 创建对象结构对象
ObjectStruture struture = new ObjectStruture();
struture.addElement(element1);
struture.addElement(element2);
// 遍历元素对象集合,调用元素对象方法操作访问者,然后访问者在回调元素对象中原有方法
for (Element element : struture.elements) {
element.accpet(new Visitor());
}
}
}
/**
* 抽象元素角色
*/
abstract class Element {
/**
* 原有方法,将来会被访问者回调
*/
public abstract void doSomething();
/**
* 沟通访问者
* @param visitor 访问者对象
*/
public abstract void accpet(IVisitor visitor);
}
/**
* 具体元素角色1
*/
class ConcreteElement1 extends Element {
@Override
public void doSomething() {
System.out.println("ConcreteElement1类中的doSomething()方法被调用");
}
@Override
public void accpet(IVisitor visitor) {
visitor.visit(this);
}
}
/**
* 具体元素角色2
*/
class ConcreteElement2 extends Element {
@Override
public void doSomething() {
System.out.println("ConcreteElement2类中的doSomething()方法被调用");
}
@Override
public void accpet(IVisitor visitor) {
visitor.visit(this);
}
}
/**
* 抽象访问者角色
*/
interface IVisitor {
/**
* 被元素类沟通
* @param element 元素类
*/
public void visit(Element element);
}
/**
* 具体访问者角色1
*/
class Visitor implements IVisitor {
@Override
public void visit(Element element) {
// 调用元素类中原有方法
element.doSomething();
}
}
/**
* 对象结构角色
*/
class ObjectStruture {
// 元素集合
public List<Element> elements = new ArrayList<>();
/**
* 添加元素集合方法
* @param element 元素对象
*/
public void addElement(Element element) {
elements.add(element);
}
}
结果:
ConcreteElement1类中的doSomething()方法被调用
ConcreteElement2类中的doSomething()方法被调用
4.9.7、拓展
4.9.7.1、分派
变量被声明时的类型叫做变量的静态类型,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型。比如 Map map = new HashMap()
,map变量的静态类型是 Map
,实际类型是 HashMap
。根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派和动态分派。
静态分派(Static Dispatch) 发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。
动态分派(Dynamic Dispatch) 发生在运行时期,动态分派动态地置换掉某个方法。方法重写就是动态分派。
4.9.7.2、静态分派(方法重载支持静态分派)
代码:
public class Test {
public static void main(String[] args) {
Animal a = new Animal();
Animal a1 = new Dog();
Animal a2 = new Cat();
Execute exe = new Execute();
exe.execute(a);
exe.execute(a1);
exe.execute(a2);
}
}
class Animal {
}
class Dog extends Animal {
}
class Cat extends Animal {
}
class Execute {
public void execute(Animal a) {
System.out.println("Animal");
}
public void execute(Dog d) {
System.out.println("dog");
}
public void execute(Cat c) {
System.out.println("cat");
}
}
结果:
Animal
Animal
Animal
分析:
重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。
4.9.7.3、动态分派(方法重写支持动态分派)
代码:
public class Test {
public static void main(String[] args) {
Animal a = new Animal();
Animal d = new Dog();
Animal c = new Cat();
Execute exe = new Execute();
a.accept(exe);
d.accept(exe);
c.accept(exe);
}
}
class Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
class Dog extends Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
class Cat extends Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
class Execute {
public void execute(Animal a) {
System.out.println("animal");
}
public void execute(Dog d) {
System.out.println("dog");
}
public void execute(Cat c) {
System.out.println("cat");
}
}
结果:
animal
dog
cat
分析:
Java编译器在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型。
4.9.7.4、双分派(方法重写+方法重载支持双分派)
代码:
public class Test {
public static void main(String[] args) {
Animal a = new Animal();
Animal d = new Dog();
Animal c = new Cat();
Execute exe = new Execute();
a.accept(exe);
d.accept(exe);
c.accept(exe);
}
}
class Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
class Dog extends Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
class Cat extends Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
class Execute {
public void execute(Animal a) {
System.out.println("animal");
}
public void execute(Dog d) {
System.out.println("dog");
}
public void execute(Cat c) {
System.out.println("cat");
}
}
结果:
animal
dog
cat
分析:
所谓双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者(receiver)的运行时区别,还要根据参数的运行时区别。双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了。
4.10、备忘录模式
4.10.1、通俗解释
备忘录模式就是将发起人角色的某种状态记录下来,然后还能恢复到该状态
4.10.2、概念
又叫做快照模式,在不破坏封装性的前提下,捕获对象A的内部状态,并在该对象A之外保存这个状态,以便在后来能将对象A的状态进行恢复
4.10.3、涉及角色
- 发起人角色Originator:负责记录自身当前状态,提供创建“存储当前状态的备忘录角色对象”的方法,提供“根据备忘录角色对象”将“发起人角色对象恢复到某种状态”的方法
- 备忘录角色Memento:提供存储发起人角色状态的结构,根据要求设置字段属性即可;窄接口和宽接口:由于管理者角色不用于操作备忘录角色,只是存储备忘录角色,而发起人角色可以生成备忘录角色对象,或者根据备忘录角色对象进行发起人状态恢复,所以对于备忘录角色来说,其中管理者角色就是窄接口,而发起人角色就是深宽接口
- 管理者角色Caretaker:负责存储备忘录角色对象,添加和提供备忘录角色对象,但是不能操作备忘录角色对象进行发起人状态恢复,毕竟状态恢复是发起人提供的功能
4.10.4、优缺点
- 优点:简化发起人功能,发起人不需要在保存备忘录对象了
- 缺点:资源消耗,毕竟需要一个管理者角色类来存储发起人状态信息
4.10.5、实现方式
说明: 以游戏大战Boss为例,当游戏开始的时候,把用户状态存储下来,如果用户由于操作失误,可能快输了,可以在游戏中途过程进行用户最初状态恢复,让用户重新愉快的游戏
代码:
public class Test {
public static void main(String[] args) {
// 初始化发起人角色状态
Originator originator = new Originator(100, 100, 100);
System.out.println("发起人原始状态:" + originator);
// 存储发起人角色状态
Caretaker caretaker = new Caretaker();
caretaker.setMemento(originator.createMemento());
// 模拟发起人状态中途变更
originator.setAggressivity(50);
originator.setVitality(50);
originator.setDefencivity(50);
System.out.println("发起人中途状态:" + originator);
// 恢复发起人角色状态
originator.setMemento(caretaker.getMemento());
System.out.println("发起人恢复之后的状态:" + originator);
}
}
/**
* 发起人角色
*/
class Originator {
/** 活力 **/
private Integer aggressivity;
/** 攻击性 **/
private Integer vitality;
/** 防御能力 **/
private Integer defencivity;
public Originator(Integer aggressivity, Integer vitality, Integer defencivity) {
this.aggressivity = aggressivity;
this.vitality = vitality;
this.defencivity = defencivity;
}
public void setAggressivity(Integer aggressivity) {
this.aggressivity = aggressivity;
}
public void setVitality(Integer vitality) {
this.vitality = vitality;
}
public void setDefencivity(Integer defencivity) {
this.defencivity = defencivity;
}
/**
* 创建备忘录对象(存储当前发起人角色状态)
*/
public Memento createMemento() {
Memento memento = new ConcreteMemento(aggressivity, vitality, defencivity);
return memento;
}
/**
* 根据备忘录对象恢复之前的发起人角色状态
*/
public void setMemento(Memento memento) {
ConcreteMemento concreteMemento = (ConcreteMemento)memento;
this.aggressivity = concreteMemento.getAggressivity();
this.vitality = concreteMemento.getVitality();
this.defencivity = concreteMemento.getDefencivity();
}
@Override
public String toString() {
return "Originator{" +
"aggressivity=" + aggressivity +
", vitality=" + vitality +
", defencivity=" + defencivity +
'}';
}
/**
* 具体备忘录角色
*/
private class ConcreteMemento implements Memento {
// 下面三个属性对应发起人角色的属性,需要哪些属性存储哪些属性,根据需要来定
/** 活力 **/
private Integer aggressivity;
/** 攻击性 **/
private Integer vitality;
/** 防御能力 **/
private Integer defencivity;
public ConcreteMemento(Integer aggressivity, Integer vitality, Integer defencivity) {
this.aggressivity = aggressivity;
this.vitality = vitality;
this.defencivity = defencivity;
}
public Integer getAggressivity() {
return aggressivity;
}
public void setAggressivity(Integer aggressivity) {
this.aggressivity = aggressivity;
}
public Integer getVitality() {
return vitality;
}
public void setVitality(Integer vitality) {
this.vitality = vitality;
}
public Integer getDefencivity() {
return defencivity;
}
public void setDefencivity(Integer defencivity) {
this.defencivity = defencivity;
}
}
}
/**
* 抽象备忘录角色
*/
interface Memento {}
/**
* 管理者角色
*/
class Caretaker {
/** 备忘录角色对象 **/
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
结果:
发起人原始状态:Originator{aggressivity=100, vitality=100, defencivity=100}
发起人中途状态:Originator{aggressivity=50, vitality=50, defencivity=50}
发起人恢复之后的状态:Originator{aggressivity=100, vitality=100, defencivity=100}
4.10.6、扩展
4.10.6.1、白箱备忘录模式
说明: 备忘录角色为任何对象都提供一个宽接口,备忘录对象所存储的状态对所有对象公开,备忘录角色不再是发起人角色的宽接口,它而是所有对象的宽接口
代码:
public class Test {
public static void main(String[] args) {
// 初始化发起人角色状态
Originator originator = new Originator(100, 100, 100);
System.out.println("发起人原始状态:" + originator);
// 存储发起人角色状态
Caretaker caretaker = new Caretaker();
caretaker.setMemento(originator.createMemento());
// 模拟发起人状态中途变更
originator.setAggressivity(50);
originator.setVitality(50);
originator.setDefencivity(50);
System.out.println("发起人中途状态:" + originator);
// 恢复发起人角色状态
originator.setMemento(caretaker.getMemento());
System.out.println("发起人恢复之后的状态:" + originator);
}
}
/**
* 发起人角色
*/
class Originator {
/** 活力 **/
private Integer aggressivity;
/** 攻击性 **/
private Integer vitality;
/** 防御能力 **/
private Integer defencivity;
public Originator(Integer aggressivity, Integer vitality, Integer defencivity) {
this.aggressivity = aggressivity;
this.vitality = vitality;
this.defencivity = defencivity;
}
public void setAggressivity(Integer aggressivity) {
this.aggressivity = aggressivity;
}
public void setVitality(Integer vitality) {
this.vitality = vitality;
}
public void setDefencivity(Integer defencivity) {
this.defencivity = defencivity;
}
/**
* 创建备忘录对象(存储当前发起人角色状态)
*/
public Memento createMemento() {
Memento memento = new Memento(aggressivity, vitality, defencivity);
return memento;
}
/**
* 根据备忘录对象恢复之前的发起人角色状态
*/
public void setMemento(Memento memento) {
this.aggressivity = memento.getAggressivity();
this.vitality = memento.getVitality();
this.defencivity = memento.getDefencivity();
}
@Override
public String toString() {
return "Originator{" +
"aggressivity=" + aggressivity +
", vitality=" + vitality +
", defencivity=" + defencivity +
'}';
}
}
/**
* 具体备忘录角色
*/
class Memento {
// 下面三个属性对应发起人角色的属性,需要哪些属性存储哪些属性,根据需要来定
/** 活力 **/
private Integer aggressivity;
/** 攻击性 **/
private Integer vitality;
/** 防御能力 **/
private Integer defencivity;
public Memento(Integer aggressivity, Integer vitality, Integer defencivity) {
this.aggressivity = aggressivity;
this.vitality = vitality;
this.defencivity = defencivity;
}
public Integer getAggressivity() {
return aggressivity;
}
public void setAggressivity(Integer aggressivity) {
this.aggressivity = aggressivity;
}
public Integer getVitality() {
return vitality;
}
public void setVitality(Integer vitality) {
this.vitality = vitality;
}
public Integer getDefencivity() {
return defencivity;
}
public void setDefencivity(Integer defencivity) {
this.defencivity = defencivity;
}
}
/**
* 管理者角色
*/
class Caretaker {
/** 备忘录角色对象 **/
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
结果:
发起人原始状态:Originator{aggressivity=100, vitality=100, defencivity=100}
发起人中途状态:Originator{aggressivity=50, vitality=50, defencivity=50}
发起人恢复之后的状态:Originator{aggressivity=100, vitality=100, defencivity=100}
4.10.6.2、黑箱备忘录模式
上面实际方法中的代码就是黑箱备忘录模式,原则是让备忘录角色只成为发起人的宽接口,也就是只有发起人角色能操纵它,而对于其他对象来说,备忘录角色是窄接口,是不可见的接口
4.11、解释器模式
4.11.1、通俗解释
如果输入值不可预知,但是有一定规律,这就可以使用解释器模式,我们开发一个解释器来解释这件未知有规律的事情
4.11.2、概念
定义一件事情的文法表示,其中文法是用于描述语言的语法结构形式规则,然后由此定义出一个解释器,使用该解释器来解释这件事情的细节
加减法文法:
说明:就是加减运算,这就不用多说了吧
expression ::= integer | plus | minus
plus ::= expression ‘+’ expression
minus ::= expression ‘-’ expression
value ::= integer
注意: 这里的符号“::=”表示“定义为”的意思,竖线 | 表示或,左右的其中一个,引号内为字符本身,引号外为语法。
表达式可以是一个值(终结符表达式角色),也可以是plus加或者minus减运算(非终结符表达式角色),而plus加运算和minus减运算又是由表达式结合运算符构成
韶粵通:
说明:“韶粵通”公交车读卡器可以判断乘客的身份,如果是“韶关”或者“广州”的“老人” “妇女”“儿童”就可以免费乘车,其他人员乘车一次扣 2 元。
<expression> ::= <city> 的 <person> //分隔符
<city> ::= 韶关|广州 //城市
<person> ::= 老人|妇女|儿童 //对象
4.11.3、涉及角色
- 抽象表达式角色:定义抽象方法interpret(),用来解释中间操作
- 终结符表达式角色:实现或者继承抽象表达式角色,重写interpret()方法,完成终结操作
- 非终结符表达式角色:实现或者继承抽象表达式对象,重写interpret()方法,完成中间操作,一般会将下一步用到的抽象表达式角色通过成员变量形式保存在内部
- 环境角色:将数据从此处传到下面的表达式角色对象中
4.11.4、优缺点
- 优点:文法固定,实现逻辑清晰;扩展性好,比如我们可以写加减法解释器,然后还可以在此基础上添加乘除逻辑,只需要在增加乘法和除法的非终结符表达式类
- 缺点:会造成类增多;由于文法清晰的场景比较少,所以应用场景受限
4.11.5、实现方式
4.11.5.1、代码1(加减法混合运算)
public class Test {
public static void main(String[] args) {
Context context = new Context();
Value a = new Value(1);
Value b = new Value(2);
Value c = new Value(3);
Value d = new Value(4);
Value e = new Value(5);
AbstractExpression expression = new Minus(new Plus(new Plus(new Plus(a, b), c), d), e);
System.out.println(expression + " = " + context.ending(expression));
}
}
/**
* 抽象表达式角色
*/
abstract class AbstractExpression {
/**
* 中间计算操作方法
*/
public abstract int interpret();
}
/**
* 终结符表达式角色(说明:处理纯数字)
*/
class Value extends AbstractExpression {
private int value;
public Value(int value) {
this.value = value;
}
@Override
public int interpret() {
return value;
}
@Override
public String toString() {
return new Integer(value).toString();
}
}
/**
* 非终结符表达式角色——加法表达式(说明:中间操作,两数相加)
*/
class Plus extends AbstractExpression {
// 左侧表达式
private AbstractExpression left;
// 右侧表达式
private AbstractExpression right;
public Plus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() + right.interpret();
}
@Override
public String toString() {
return "(" + left.toString() + " + " + right.toString() + ")";
}
}
/**
* 非终结符表达式角色——减法表达式(说明:中间操作,两数相减)
*/
class Minus extends AbstractExpression {
// 左侧表达式
private AbstractExpression left;
// 右侧表达式
private AbstractExpression right;
public Minus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() - right.interpret();
}
@Override
public String toString() {
return "(" + left.toString() + " - " + right.toString() + ")";
}
}
/**
* 环境类
*/
class Context {
/**
* 计算最终结果
* @param expression 表达式对象
*/
public Integer ending(AbstractExpression expression) {
return expression.interpret();
}
}
结果:
((((1 + 2) + 3) + 4) - 5) = 5
4.11.5.2、代码2(是否免费坐公交)
public class Test {
public static void main(String[] args) {
System.out.println("说明:如果您是老人、妇女、儿童任意一种,并且乘车城市为韶关或者广州,乘坐公交车免费!");
Context context = new Context();
context.freeRide("韶关的老人");
context.freeRide("韶关的年轻人");
context.freeRide("广州的妇女");
context.freeRide("广州的儿童");
context.freeRide("山东的儿童");
}
}
/**
* 抽象表达式角色
*/
interface Expression {
/**
* 判断方法
*/
public boolean interpret(String info);
}
/**
* 终结符表达式角色——城市或者人员判断对象
*/
class TerminalExpression implements Expression {
/** 城市或者人员集合 **/
private Set<String> set = new HashSet<>();
public TerminalExpression(String[] arr) {
set.addAll(Arrays.asList(arr));
}
@Override
public boolean interpret(String info) {
return set.contains(info);
}
}
/**
* 非终结符表达式角色——与判断对象
*/
class AndExpression implements Expression {
/** 城市对象 **/
private Expression city;
/** 人员对象 **/
private Expression person;
public AndExpression(Expression city, Expression person) {
this.city = city;
this.person = person;
}
@Override
public boolean interpret(String info) {
// 截取字符串,比如
String[] infoArr = info.split("的");
return city.interpret(infoArr[0]) && person.interpret(infoArr[1]);
}
}
/**
* 环境类
*/
class Context {
// 城市数组
private String[] citys = {"韶关", "广州"};
// 人员数组
private String[] persons = {"老人", "妇女", "儿童"};
// 非终结符表达式角色对象
private Expression andExpression;
/**
* 将数据向下传递
*/
public Context() {
Expression cityExpression = new TerminalExpression(citys);
Expression personExpression = new TerminalExpression(persons);
andExpression = new AndExpression(cityExpression, personExpression);
}
/**
* 判断是否免费乘坐公交车
*/
public void freeRide(String info) {
boolean ok = andExpression.interpret(info);
if (ok) {
System.out.println(info + " 》 本次乘车免费");
} else {
System.out.println(info + " 》 本次乘车扣费2元");
}
}
}
结果:
说明:如果您是老人、妇女、儿童任意一种,并且乘车城市为韶关或者广州,乘坐公交车免费!
韶关的老人 》 本次乘车免费
韶关的年轻人 》 本次乘车扣费2元
广州的妇女 》 本次乘车免费
广州的儿童 》 本次乘车免费
山东的儿童 》 本次乘车扣费2元