第八章 常用设计模式(下)

十三、桥接模式

13.1 模式动机
设想如果要绘制矩形、圆形、三角形,我们至少需要 3 个形状类,但是如果绘制的图形需要具有不同的颜色,如红色、绿色、蓝色等,此时至少有如下两种设计方案:
  
- 第一种设计方案是为每一种形状都提供一套各种颜色的版本。

    
- 第二种设计方案是根据实际需要对形状和颜色进行组合。

  
对于有两个变化维度(即两个变化的原因)的系统,采用方案二来进行设计系统中类的个数更少,且系统扩展更为方便。设计方案二即是桥接模式的应用。桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量。
   
13.2 模式定义
桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
  
13.3 模式结构

  
桥接模式包含如下角色:
• Abstraction:抽象类
• RefinedAbstraction:扩充抽象类
• Implementor:实现类接口
• ConcreteImplementor:具体实现类
  

13.4 模式分析
理解桥接模式,重点需要理解如何将抽象化 (Abstraction) 与实现化 (Implementation) 脱耦,使得二者可以独立地变化。
  
- 抽象化:抽象化就是忽略一些信息,把不同的实体当作同样的实体对待。在面向对象中,将对象的共同性质抽取出来形成类的过程即为抽象化的过程。

- 实现化:针对抽象化给出的具体实现,就是实现化,抽象化与实现化是一对互逆的概念,实现化产生的对象比抽象化更具体,是对抽象化事物的进一步具体化的产物。

- 脱耦:脱耦就是将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为关联关系。桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化,这就是桥接模式的用意。
  
典型的实现类接口代码:
public interface Implementor {
	public void operationImpl();
}
  
典型的抽象类代码:
public abstract class Abstraction {

    protected Implementor impl;

    public void setImpl(Implementor impl) {
        this.impl=impl;
    }
     
    public abstract void operation();

}
  
典型的扩充抽象类代码:
public class RefinedAbstraction extends Abstraction {
    public void operation() {
        // 代码
        impl.operationImpl();
        //代码
    }
}
  
13.5 案例与解析
实例一:模拟毛笔
- 现需要提供大中小 3 种型号的画笔,能够绘制 5 种不同颜色,如果使用蜡笔,我们需要准备 3*5=15 支蜡笔,也就是说必须准备 15 个具体的蜡笔类。而如果使用毛笔的话,只需要 3 种型号的毛笔,外加 5 个颜料盒,用 3+5=8 个类就可以实现 15 支蜡笔的功能。本实例使用桥接模式来模拟毛笔的使用过程。

  
实例源码:
// 抽象类
public abstract class Pen {
    protected Color color;
    public void setColor(Color color) {
        this.color=color;
    }
    public abstract void draw(String name);
}
// 扩充抽象类
public class SmallPen extends Pen {
    public void draw(String name) {
        String penType="小号毛笔绘制";
        this.color.bepaint(penType,name);
    }
}
// 扩充抽象类
public class MiddlePen extends Pen {
    public void draw(String name) {
        String penType="中号毛笔绘制";
        this.color.bepaint(penType,name);
    }
}
// 扩充抽象类
public class BigPen extends Pen {
    public void draw(String name) {
        String penType="大号毛笔绘制";
        this.color.bepaint(penType,name);
    }
}
// 实现类接口
public interface Color {
    void bepaint(String penType,String name);
}
// 扩充实现类
public class Red implements Color {
    public void bepaint(String penType,String name) {
        System.out.println(penType + "红色的"+ name + ".");
    }
}
// 扩充实现类
public class Green implements Color {
    public void bepaint(String penType,String name) {
        System.out.println(penType + "绿色的"+ name + ".");
    }
}
// 扩充实现类
public class Blue implements Color {
    public void bepaint(String penType,String name) {
        System.out.println(penType + "蓝色的"+ name + ".");
    }
}
// 扩充实现类
public class White implements Color {
    public void bepaint(String penType,String name) {
        System.out.println(penType + "白色的"+ name + ".");
    }
}
// 扩充实现类
public class Black implements Color {
    public void bepaint(String penType,String name) {
        System.out.println(penType + "黑色的"+ name + ".");
    }
}
  
配置文件 configPen.xml
<?xml version="1.0"?>
<config>
 <className>Blue</className>
 <className>SmallPen</className>
</config>
//使用java反射创建具体的颜色和画笔
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;

public class XMLUtilPen {
    //该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
    public static Object getBean(String args) {
        try {
            //创建文档对象
            DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = dFactory.newDocumentBuilder();
            Document doc;                            
            doc = builder.parse(new File("configPen.xml")); 
            NodeList nl=null;
            Node classNode=null;
            String cName=null;
            nl = doc.getElementsByTagName("className");
            
            if(args.equals("color")) {
                //获取包含类名的文本节点
                classNode=nl.item(0).getFirstChild();
            } else if(args.equals("pen")) {
                //获取包含类名的文本节点
                classNode=nl.item(1).getFirstChild();
            }
            
            cName=classNode.getNodeValue();
            //通过类名生成实例对象并将其返回
            Class c=Class.forName(cName);
            Object obj=c.newInstance();
            return obj;        
        } catch(Exception e) {
            e.printStackTrace();
            return null;
        }
     }
}
//客户端
public class Client {
    public static void main(String a[]) {
        Color color;
        Pen pen;
        
        color=(Color)XMLUtilPen.getBean("color");
        pen=(Pen)XMLUtilPen.getBean("pen");
        
        pen.setColor(color);
        pen.draw("鲜花");
    }
}
    
13.6 模式优缺点
优点
- 分离抽象接口及其实现部分。
- 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法。
- 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
- 实现细节对客户透明,可以对用户隐藏实现细节。
 
缺点
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
  
模式适用环境
在以下情况下可以使用桥接模式:
- 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
- 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
- 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
- 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
  
模式应用
1. Java 语言通过 Java 虚拟机实现了平台的无关性。

  
2. 一个 Java 桌面软件总是带有所在操作系统的视感(LookAndFeel),如果一个 Java 软件是在 Unix 系统上开发的,那么开发人员看到的是 Motif 用户界面的视感;在 Windows 上面使用这个系统的用户看到的是 Windows 用户界面的视感;而一个在 Macintosh 上面使用的用户看到的则是 Macintosh 用户界面的视感,Java 语言是通过所谓的 Peer 架构做到这一点的。Java 为 AWT 中的每一个 GUI 构件都提供了一个 Peer 构件,在 AWT 中的 Peer 架构就使用了桥接模式。
3. JDBC 驱动程序也是桥接模式的应用之一。使用 JDBC 驱动程序的应用系统就是抽象角色,而所使用的数据库是实现角色。一个 JDBC 驱动程序可以动态地将一个特定类型的数据库与一个 Java 应用程序绑定在一起,从而实现抽象角色与实现角色的动态耦合。
  

十四、委派模式

14.1 什么是委派模式
现实生活中也常有委派的场景发生,例如:老板(Boss)给项目经理(Leader)下达任务,项目经理会根据实际情况给每个员工派发工作任务,待员工把工作任务完成之后,再由项目经理汇报工作进度和结果给老板。
  
14.2 委派模式定义
委派模式( DelegatePattern )又叫委托模式,是一种面向对象的设计模式,允许对象组合实现与继承相同的代码重用。他的基本作用就是负责任务的调用和分配任务,是一种特殊的静态代理,可以理解为全权代理,但是代理模式注重过程,而委派模式注重结果。委派模式属于行为型模式,不属于 GOF23 种设计模式中。
  
14.3 源码实现
创建 IEmployee 接口:
public interface IEmployee {
   void doing(String task);
}
  
创建员工 EmployeeA 类:
public class EmployeeA implements IEmployee {
 
    protected String name;
 
    public EmployeeA(String name){
        this.name = name;
    }
 
    @Override
    public void doing(String task) {
        System.out.println("我是员工A,我擅长"+name+",现在开始做"+task+"工作");
    }
}
 
创建员工 EmployeeB 类:
public class EmployeeB implements IEmployee {
 
    protected String name;
 
    public EmployeeB(String name){
        this.name = name;
    }
    @Override
    public void doing(String task) {
        System.out.println("我是员工B,我擅长"+name+",现在开始做"+task+"工作");
    }
}
  
创建领导 Leader 类:
import java.util.HashMap;
public class Leader implements IEmployee {
 
    HashMap<String,IEmployee> map = new HashMap<String,IEmployee>();
 
    public Leader(){
        map.put("登录",new EmployeeA("开发"));
        map.put("登录页面",new EmployeeB("UI"));
    }
 
    @Override
    public void doing(String task) {
       map.get(task).doing(task);
    }
}
    
创建老板 Boss 类:
public class Boss {
 
    public static void main(String[] args) {
        new Leader().doing("登录");
        new Leader().doing("登录页面");
    }
 
}
    
运行结果:
我是员工 A,我擅长开发,现在开始做登录工作。
我是员工 B,我擅长 UI,现在开始做登录页面工作。
  

十五、模板方法模式

模板方法模式(Template Method Pattern)是一种类行为型模式。
定义:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
  
15.1 模式结构
模板方法模式包含以下主要角色:
1. 抽象类/抽象模板(Abstract Class)
抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。
- 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
- 基本方法:是整个算法中的一个步骤,包含以下几种类型。
- 抽象方法:在抽象类中声明,由具体子类实现。
- 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
- 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
2. 具体子类/具体实现(Concrete Class)
具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
  
模板方法模式结构图

  
优点:
- 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
- 它在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
  
缺点:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
- 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。
  
应用场景:
- 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
- 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
- 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。
  
15.2 实例数据库操作
对数据库的操作一般包括连接、打开、使用、关闭等步骤,在数据库中模板类中我们定义了 connDB、openDB、useDB、closeDB 四个方法分别对应这四个步骤。对于不同类型的数据库(如 SQLServer 和 Oracle)其操作步骤都一致,只是连接数据库 connDB 方法有所区别,现使用模板方法模式对其进行设计。

  
DBOperator 充当抽象父类角色,SQLServerDBOperator 和 OracleDBOperator 充当具体子类角色。
//抽象数据库操作类:抽象类
abstract class DBOperator{
    public abstract void connDB();
    public void openDB(){
        System.out.println("打开数据库");
    }
    public void useDB(){
        System.out.println("使用数据库");
    }
    public void closeDB(){
        System.out.println("关闭数据库");
    }
    public void process(){
        connDB();
        openDB();
        useDB();
        closeDB();
    }
}
//SQL Server 数据库操作类:具体子类
class SQLServerDBOperator extends DBOperator{

    @Override
    public void connDB() {
        System.out.println("连接SQL Server数据库");
    }
}
//Oracle数据库操作类:具体子类
class OracleDBOperator extends DBOperator{
    @Override
    public void connDB() {
        System.out.println("连接Oracle数据库");
    }
}
  
客户端测试:
public class Client {
    public static void main(String[] args) {
        DBOperator operator;
        operator = new SQLServerDBOperator();
        operator.process();
        System.out.println("------------------");
        operator = new OracleDBOperator();
        operator.process();
    }
}
  
运行结果:
连接 SQL Server 数据库
打开数据库
使用数据库
关闭数据库
------------------
连接 Oracle 数据库
打开数据库
使用数据库
关闭数据库
    

十六、策略模式

16.1 介绍
为什么需要使用到设计模式?
使用设计模式可以重构整体架构代码,可以使代码提高复用性,扩展性,减少代码冗余问题。
  

什么是策略模式?
策略模式是对算法的包装,是把使用算法的责任和算法本身分隔开来,委派给不同的对象管理,最终可以实现解决多重 if 判断问题。
  
为什么叫策略模式?
每个 if 判断都可以理解为就是一个策略。
 public String toPay(String payCode){
     if(payCode.equals("ali_pay")){
         return "调用支付宝接口支付";
     }
     if(payCode.equals("weixin_pay")){
         return "调用微信支付接口支付";
     }
     if(payCode.equals("yinlian_pay")){
         return "调用银联支付接口支付";
     }
     return "未找到该支付接口";
 }
  
策略,指的是可以实现目标的方案集合,在某些特定情况下,策略之间是可以相互替换的。
  
16.2 实现
我们来看上面一段代码,我们传不同的payCode,得到对应的支付平台,调用接口行为是相同的,调用接口代码是不同的。
  

我们来用策略模式实现下:
- 环境(Context)角色:持有一个 Strategy 的引用。
- 抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或者抽象类实现,此角色给出所有的具体策略类所需的接口。
- 具体策略(ConcreStrategy)角色:包装了相关的算法或行为。
  
定义策略接口 --> 实现不同的策略类 --> 利用多态或其他方式调用策略。

  
我们来创建共同的行为:
public interface PayStrategy {
    /**
     * 共同方法行为
     * @return
     */
    public String toPay();
}
  
具体策略实现:
public class AliPayStrategy implements PayStrategy {
    @Override
    public String toPay() 
        return "调用支付宝接口。。。。。";
    }
}
public class WeXinPayStrategy implements PayStrategy {
    @Override
    public String toPay() {
        return "调用微信支付接口。。。。。";
    }
}
  
策略枚举类 所有策略的具体实现:
public enum PayEnumStrategy {
    /**
     * 支付宝支付
     */
    ALI_PAY("com.zigao.com.strategy.impl.AliPayStrategy"),
 
    /**
     * 微信支付
     */
    WEICHAT_PAY("com.zigao.com.strategy.impl.WeXinPayStrategy"),
 
    /**
     * 小米支付
     */
    XIAOMI_PAY("com.zigao.com.strategy.impl.XiaoMiPayStrategy");
 
    /**
     * 完整地址
     */
	private String className;
 
    PayEnumStrategy(String className) {
        this.className = className;
    }
 
	public String getClassName() {
        return className;
    }
 
    public void setClassName(String className) {
        this.className = className;
    }
}
  
使用策略工厂获取具体策略实现:
public class StrateFactory {
 
    public static PayStrategy  getPayStrategy(String strategyType){
       try {
            //1:获取枚举类中classname
            String classname = PayEnumStrategy.valueOf(strategyType).getClassName();
             //2:使用java反射技术初始化类
            return (PayStrategy)Class.forName(classname).newInstance();
       }catch (Exception e){
           return null;
       }
    }
 
}
    
使用 Context 上下文获取具体策略实现:
@Component
public class PayContextStrategy {
 
    /**
     * 获取具体策略实现
     */
    public String toPayHtml(String payCode){
        //判断是否为空
        if(StringUtils.isEmpty(payCode)){
            return "payCode======>>>>>不能为空";
        }
        //使用策略工厂获取具体策略实现
        PayStrategy payStrategy = StrateFactory.getPayStrategy(payCode);
        if(payStrategy == null){
              return "没有找到具体策略实现。。。。。";
        }
        return payStrategy.toPay();
    }
}
  
测试:
@RestController
@RequestMapping("/pay")
public class PayController{
 
    //打印日志
    public Logger logger = LoggerFactory.getLogger(this.getClass());
 
    @Autowired
    private PayContextStrategy payContextStrategy;
 
    @PostMapping("/toPay")
    public ResponseMessage upload(@RequestBody Map map) {
        ResponseMessage responseMessage = new ResponseMessage(0);
        try {
            String payCode = (String) map.get("payCode");
            String payHtml = payContextStrategy.toPayHtml(payCode);
            responseMessage.setMessage(payHtml);
        } catch (Exception e) {
            logger.error("调用支付异常,", e);
            throw new RuntimeException(e.getMessage());
        }
        return responseMessage;
    }
 
}
  

  
上面虽然实现了,但是还是不完美,我们用另一种方式实现。
  
通过 bean 对象注入的方式实现:
<bean id = "weXinPayStrategy" class = “com.zigao.com.strategy.impl.WeXinPayStrategy">
使用类名小写名称 从 Spring 容器中获取具体策略对象;
 
这些可以放到数据库中

  
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
 
-- ----------------------------
-- Table structure for payment
-- ----------------------------
DROP TABLE IF EXISTS `payment`;
CREATE TABLE `payment`  (
  `id` int(10) NOT NULL,
  `channel_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `channel_id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `strategy_bean_id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
 
-- ----------------------------
-- Records of payment
-- ----------------------------
INSERT INTO `payment` VALUES (1, '支付宝支付实现', 'ALI_PAY', 'aliPayStrategy');
INSERT INTO `payment` VALUES (2, '微信宝支付实现', 'WEICHAT_PAY', 'weXinPayStrategy');
INSERT INTO `payment` VALUES (3, '小米宝支付实现', 'XIAOMI_PAY', 'xiaoMiPayStrategy');
 
SET FOREIGN_KEY_CHECKS = 1;
  

  
16.3 总结
策略模式可以充分的体现面向对象设计原则中的封装变化、多用组合,少用继承、针对接口编程,不针对实现编程等原则。
  
策略模式具有以下特点:
1. 策略模式的关注点不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。
2. 策略模式中各个策略算法是平等的。对于一系列具体的策略算法,地位是完全一样的。正因为这个平等性,才能实现算法之间可以相互替换。所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的。所以可以这样描述这一系列策略算法:策略算法是相同行为的不同实现。
3. 运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同的策略实现中切换,但是同时只能使用一个。
  

如果所有的具体策略类都有一些公有的行为。这时候,就应当把这些公有的行为放到共同的抽象策略角色 Strategy 类里面。当然这时候抽象策略角色必须要用 Java 抽象类实现,而不能使用接口。但是,编程中没有银弹,策略模式也不例外,也有一些缺点,先来回顾总结下优点:
1. 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
2. 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。
3. 使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。
  
但同时,也有如下缺点:
1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。这种策略类的创建及选择其实也可以通过工厂模式来辅助进行。
2. 由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。可以通过使用享元模式在一定程度上减少对象的数量。
  

十七、责任链模式

责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
  
17.1 介绍
意图:
避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
  
主要解决:
职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
  
何时使用:
在处理消息的时候以过滤很多道。
  
如何解决:
拦截的类都实现统一接口。
  
关键代码:
Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。
  
优点:
1. 降低耦合度。它将请求的发送者和接收者解耦。
2. 简化了对象。使得对象不需要知道链的结构。
3. 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
4. 增加新的请求处理类很方便。
  
缺点:
1. 不能保证请求一定被接收。
2. 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
3. 可能不容易观察运行时的特征,有碍于除错。
  
使用场景:
1. 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
2. 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
3. 可动态指定一组对象处理请求。
 
注意事项:
在 JAVA WEB 中遇到很多应用。
  
17.2 实现
我们创建抽象类 AbstractLogger,带有详细的日志记录级别。然后我们创建三种类型的记录器,都扩展了 AbstractLogger。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器。

     
17.3 代码实现
创建抽象的记录器类
//创建抽象的记录器类
public abstract class AbstractLogger {
    public static int INFO = 1;
    public static int DEBUG = 2;
    public static int ERROR = 3;
 
    protected int level;
 
    //责任链中的下一个元素
    protected AbstractLogger nextLogger;
 
    public void setNextLogger(AbstractLogger nextLogger){
        this.nextLogger = nextLogger;
    }
 
    public void logMessage(int level, String message){
        if(this.level <= level){
            write(message);
        }
        if(nextLogger !=null){
            nextLogger.logMessage(level, message);
        }
    }
 
    abstract protected void write(String message);
}
  
创建抽象记录器实体类
//创建扩展了该记录器类的实体类
public class ConsoleLogger extends AbstractLogger{
    public ConsoleLogger(int level){
        this.level = level;
    }
 
    @Override
    protected void write(String message) {
        System.out.println("Standard Console::Logger: " + message);
    }
}
public class ErrorLogger extends AbstractLogger{
 
    public ErrorLogger(int level){
        this.level = level;
    }
 
    @Override
    protected void write(String message) {
        System.out.println("Error Console::Logger: " + message);
    }
}
public class FileLogger extends AbstractLogger{
 
    public FileLogger(int level){
        this.level = level;
    }
 
    @Override
    protected void write(String message) {
        System.out.println("File::Logger: " + message);
    }
}
  
创建不同类型的记录器。赋予它们不同的错误级别,并在每个记录器中设置下一个记录器。每个记录器中的下一个记录器代表的是链的一部分。
public class ChainPatternDemo {
    private static AbstractLogger getChainOfLoggers(){
 
        AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
        AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
        AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
 
        errorLogger.setNextLogger(fileLogger);
        fileLogger.setNextLogger(consoleLogger);
 
        return errorLogger;
    }
 
    public static void main(String[] args) {
        AbstractLogger loggerChain = getChainOfLoggers();
 
        loggerChain.logMessage(AbstractLogger.INFO, "This is an information.");
 
        loggerChain.logMessage(AbstractLogger.DEBUG,
                "This is a debug level information.");
 
        loggerChain.logMessage(AbstractLogger.ERROR,
                "This is an error information.");
    }
}
  
运行结果:
Standard Console::Logger: This is an information.
File::Logger: This is a debug level information.
Standard Console::Logger: This is a debug level information.
Error Console::Logger: This is an error information.
File::Logger: This is an error information.
Standard Console::Logger: This is an error information.  
  

十八、迭代器模式

18.1 定义
迭代器(Iterator)模式又叫作游标(Cursor)模式,是一种对象的行为模式。提供一种方法顺序访问一个聚合(指一组对象的组合结构,如:Java 中的集合、数组等)对象中各个元素,而又不需暴露该对象的内部表示。迭代器模式的本质:控制访问聚合对象中的元素。其设计意图:无须暴露聚合对象的内部实现,就能够访问到聚合对象中的各个元素。
  

18.2 角色
抽象迭代器(Iterator)角色: 一般定义为接口,用来定义访问和遍历元素的接口。
具体迭代器(ConcreteIterator)角色: 实现对聚合对象的遍历,并跟踪遍历时的当前位置。
抽象聚合(Aggregate)角色: 定义创建相应迭代器对象的接口。
具体聚合(ConcreteAggregate)角色: 实现创建相应的迭代器对象。
  
18.3 示例
Iterator 接口
public interface Iterator {
    /**
     * 移动到聚合对象中下一个元素
     */
    void next();

    /**
     * 判断是否已经移动到聚合对象的最后一个元素
     */
    boolean isDone();

    /**
     * 获取迭代的当前元素
     */
    Object currentItem();
}
     
ConcreteIterator 实现类
public class ConcreteIterator implements Iterator {

    private ConcreteAggregate concreteAggregate;

    private int index = 0;

    ConcreteIterator(ConcreteAggregate concreteAggregate) {
        this.concreteAggregate = concreteAggregate;
    }

    @Override
    public void next() {
        if (index < this.concreteAggregate.size()) {
            index++;
        }
    }

    @Override
    public boolean isDone() {
        if (index == this.concreteAggregate.size()) {
            return true;
        }
        return false;
    }

    @Override
    public Object currentItem() {
        return this.concreteAggregate.get(index);
    }
}
  
Aggregate 抽象类
public abstract class Aggregate {
    /**
     * 工厂方法,创建相应的迭代器对象的接口
     */
    public abstract Iterator createIterator();
}
  
ConcreteAggregate 实现类
public class ConcreteAggregate extends Aggregate {

    private String[] value;

    ConcreteAggregate(String[] value) {
        this.value = value;
    }

    @Override
    public Iterator createIterator() {
        return new ConcreteIterator(this);
    }

    public int size() {
        return this.value.length;
    }

    public Object get(int index) {
        Object retObj = null;
        if (index < value.length) {
            retObj = value[index];
        }
        return retObj;
    }

}
  
Test测试类
public class Test {
    public static void main(String[] args) {
        String[] names = { "张三", "李四", "王五" };
        // 创建聚合对象
        ConcreteAggregate aggregate = new ConcreteAggregate(names);
        Iterator iterator = aggregate.createIterator();
        while (!iterator.isDone()){
            System.out.println(iterator.currentItem());
            iterator.next();
        }
    }
}
  
从以上示例可以看出,迭代器模式为客户端提供了一个统一访问聚合对象的接口,通过这个接口就可以顺序地访问聚合对象的元素。对于客户端而言,只是面向这个接口在访问,根本不知道聚合对象内部的实现细节(聚合对象可以是集合,也可以是数组,客户端无从得知)
  
使用迭代器模式,还可以实现很多更加丰富的功能。比如:
- 以不同的方式遍历聚合对象,比如向前、向后等;
- 对同一个聚合同时进行多个遍历;
- 以不同的遍历策略来遍历聚合,比如是否需要过滤等;
- 多态迭代,含义是:为不同的聚合结构提供统一的迭代接口,也就是说通过一个迭代接口可以访问不同的聚合结构。(多态迭代可能会带来类型安全的问题,可以考虑使用泛型)。
  
18.4 迭代器模式的适用性
在以下条件下可以考虑使用迭代器模式:
- 如果你希望为遍历不同的聚合对象提供一个统一的接口,可以使用迭代器模式;
- 如果你希望有多种遍历方式可以访问聚合对象,可以使用迭代器模式;
- 如果你希望提供访问一个聚合对象的内容,但是又不想暴露它的内部表示的时候,可以使用迭代器模式来提供迭代器接口,从而让客户端只是通过迭代器的接口来访问聚合对象,而无须关心聚合对象的内部实现。
  
18.5 迭代器模式的优缺点
优点:
- 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计;
- 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足“开闭原则”的要求;
- 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
  
缺点:
- 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性;
- 抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展,例如 JDK 内置迭代器 Iterator 就无法实现逆向遍历,如果需要实现逆向遍历,只能通过其子类 ListIterator 等来实现,而 ListIterator 迭代器无法用于操作Set 类型的聚合对象。在自定义迭代器时,创建一个考虑全面的抽象迭代器并不是件很容易的事情。
    

十九、命令模式

19.1 定义
将来自客户端的请求传入一个对象,从而使你可用不同的请求对客户进行参数化。用于“行为请求者”与“行为实现者”解耦,可实现二者之间的松耦合,以便适应变化。分离变化与不变的因素。
  
19.2 角色
- Command:定义命令的接口,声明执行的方法。
- ConcreteCommand:命令接口实现对象,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
- Receiver:接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。

- Invoker:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。

- Client:创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个 Client 称为装配者会更好理解,因为真正使用命令的客户端是从 Invoker 来触发执行。
  
19.3 优点
- 降低对象之间的耦合度。
- 新的命令可以很容易地加入到系统中。
- 可以比较容易地设计一个组合命令。
- 调用同一方法实现不同的功能。
  
19.4 缺点
使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
  
19.5 适用情况
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
- 系统需要在不同的时间指定请求、将请求排队和执行请求。
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
- 系统需要将一组操作组合在一起,即支持宏命令。
  
19.6 应用
模拟对电视机的操作有开机、关机、换台命令。代码如下
//执行命令的接口
public interface Command {
  void execute();
}
//命令接收者Receiver
public class Tv {
  public int currentChannel = 0;
 
  public void turnOn() {
     System.out.println("The televisino is on.");
  }
 
  public void turnOff() {
     System.out.println("The television is off.");
  }
 
  public void changeChannel(int channel) {
     this.currentChannel = channel;
     System.out.println("Now TV channel is " + channel);
  }
}
//开机命令ConcreteCommand
public class CommandOn implements Command {
  private Tv myTv;
 
  public CommandOn(Tv tv) {
     myTv = tv;
  }
 
  public void execute() {
     myTv.turnOn();
  }
}
//关机命令ConcreteCommand
public class CommandOff implements Command {
  private Tv myTv;
 
  public CommandOff(Tv tv) {
     myTv = tv;
  }
 
  public void execute() {
     myTv.turnOff();
  }
}

//频道切换命令ConcreteCommand
public class CommandChange implements Command {
  private Tv myTv;
 
  private int channel;
 
  public CommandChange(Tv tv, int channel) {
     myTv = tv;
     this.channel = channel;
  }
 
  public void execute() {
     myTv.changeChannel(channel);
  }
}
//可以看作是遥控器Invoker
public class Control {
  private Command onCommand, offCommand, changeChannel;
 
  public Control(Command on, Command off, Command channel) {
     onCommand = on;
     offCommand = off;
     changeChannel = channel;
  }
 
  public void turnOn() {
     onCommand.execute();
  }
 
  public void turnOff() {
     offCommand.execute();
  }
 
  public void changeChannel() {
     changeChannel.execute();
  }
}
//测试类Client
public class Client {
  public static void main(String[] args) {
     // 命令接收者Receiver
     Tv myTv = new Tv();
     // 开机命令ConcreteCommond
     CommandOn on = new CommandOn(myTv);
     // 关机命令ConcreteCommond
     CommandOff off = new CommandOff(myTv);
     // 频道切换命令ConcreteCommond
     CommandChange channel = new CommandChange(myTv, 2);
     // 命令控制对象Invoker
     Control control = new Control(on, off, channel);
 
     // 开机
     control.turnOn();
     // 切换频道
     control.changeChannel();
     // 关机
     control.turnOff();
  }
}
  
运行结果
The televisino is on.
Now TV channel is 2
The television is off.
  

19.7 总结
- 命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。
- 每一个命令都是一个操作:请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作。
- 命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
- 命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
- 命令模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。
  

二十、状态模式

20.1 介绍
我们在实现某一类逻辑,譬如,`WIFI`的打开,关闭,连接各种状态的转换时,如果我们用正常的逻辑,在代码中我们会发现很多像下面这样的条件语句:
if (WIFIState.ON) {
    // WIFI已打开处理逻辑
} else if (WIFIState.OFF) {
    // WIFI关闭的处理逻辑
} else if (WIFIState.CONNECTED) {
    // WIFI已连接的处理逻辑
}
  
这种冗余的判断逻辑导致代码的可维护性特别差,扩展性也特别的差,如果我们加一个 CONNECTING 中的状态,代码的改动逻辑会非常的大。
为了解决上述的代码中复杂对象的状态转换以及不同状态下行为的封装问题。当系统中某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式。状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化,对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致处理。
  
定义: 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。
  
状态模式中包含以下三个角色:
- 抽象状态类(state):接口或者抽象类,声明了各种状态对应的方法,而在子类中实现这种方法。由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。
- 具体状态类(ConcreteState):是抽象状态类的子类,每一个子类处理这一类状态所对应的相关行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。
- 环境类(Context):又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类 State 的实例,这个实例定义当前状态,在具体实现时,它是一个 State 子类的对象。

  
20.2 代码实现
第一步,先创建一个抽象状态类 WIFIState。
//抽象状态类
public abstract class WIFIState {

    //声明抽象业务方法,不同的具体状态类可以不同的实现
    public abstract void handle();
}
   
第二步,创建两个具体状态类 WIFIOnState 和 WIFIOffState。
// 具体状态类,打开WIFI,开始播放音乐
public class WIFIOnState extends WIFIState {
    @Override
    public void handle() {
        System.out.println("start to play music");
    }
}
//具体状态类,wifi关闭,停止正在播放的音乐
public class WIFIOffState extends WIFIState {
    @Override
    public void handle() {
        System.out.println("stop playing music");
    }
}
  
第三步,创建一个环境类 Context。包含了切换设置状态方法和调用状态对象的业务方法。
//环境类
public class Context {
    //维持一个对抽象状态对象的引用
    private WIFIState wifiState;

    public void setState(WIFIState wifiState) {
        this.wifiState = wifiState;
    }

    public void request() {
        //调用状态对象的业务方法
        wifiState.handle();
    }
}
 
第四步,测试类。
public class StateTest {

    public static void main(String[] args) {
        Context context = new Context();
        WIFIState wifiOnState = new WIFIOnState();
        context.setState(wifiOnState);
        context.request();

        System.out.println("start to change wifi state");
        WIFIState wifiOffState = new WIFIOffState();
        context.setState(wifiOffState);
        context.request();
    }
}
  
第五步,输出。由 WIFION 切换到了 WIFIOFF
start to play music
start to change wifi state
stop playing music
  
20.3 总结
状态模式将一个对象在不同状态下的不同行为封装在一个个状态类中,通过设置不同的状态对象可以让环境对象拥有不同的行为,而状态转换的细节对于客户端而言是透明的,方便了客户端的使用。在实际开发中,状态模式具有较高的使用频率,在工作流和游戏开发中状态模式都得到了广泛的应用,例如公文状态的转换、游戏中角色的升级等。
   
优点
- 封装了状态的转换规则,在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
- 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可以让我们避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起。
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
  
缺点
- 状态模式的使用必然会增加系统中类和对象的个数,导致系统运行开销增大。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的难度。
- 状态模式对“开闭原则”的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。
  
使用场景
- 对象的行为依赖于它的状态(如某些属性值) ,状态的改变将导致行为的变化。
- 在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强。
  

二十一、备忘录模式

备忘录模式(Memento Design Pattern),也叫快照(Snapshot)模式。指在不违背封装原则前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。
备忘录模式在日常中很常见,比如 Word 中的回退,MySQL 中的 undo log 日志,Git 版本管理等等,我们都可以从当前状态退回之前保存的状态。比如 Git 中的 checkout 命令就可以从 main 版本切换到之前的 bugFix 版本:

  
21.1 介绍
备忘录是一种对象行为型模式,它提供了一种可以恢复状态的机制,并实现了内部状态的封装。下面就来看看备忘录模式的结构及其对应的实现:
  
21.1.1 备忘录模式的结构
备忘录的核心是备忘录类(Memento)和管理备忘录的管理者类(Caretaker)的设计,其结构如下图所示:

  
- Originator:组织者类,记录当前业务的状态信息,提供备忘录创建和恢复的功能。
- Memento:备忘录类,存储组织者类的内部状态,在需要时候提供这些内部状态给组织者类。
- Caretaker:管理者类,对备忘录进行管理,提供存储于获取备忘录的功能,无法对备忘录对象进行修改和访问。
 
21.1.2 备忘录模式的实现
在利用备忘录模式时,首先应该设计一个组织者类(Originator),它是一个具体的业务类,存储当前状态。它包含备忘录对象的创建方法 createMemeto()和备忘录对象恢复方法 restoreMemeto()。
 
Originator 类的具体代码如下:
public class Originator {
    private String state;

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
	
    //创建一个备忘录对象
    public Memento createMemento() {
        return new Memento(this);
    }
	
    //根据备忘录对象,恢复之前组织者的状态
    public void restoreMemento(Memento m) {
        state = m.getState();
    }
}
  
对于备忘录类(Memento)而言,它存储组织者类(Originator)的状态,其具体代码如下:
public class Memento {

    private String state;

    public Memento(Originator o) {
        this.state = state;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}
  
在这里需要考虑备忘录的封装性,除了 Originator 类外,其他类不能调用备忘录的内部的相关方法。因为外界类的调用可能会引起备忘录内的状态发生变化,这样备忘录的设置就没有了意义。在实际操作中,可以将 Memento 和 Originator 类定义在同一个包中来实现封装;也可以将 Memento 类作为 Originator 的内部类。
  
下面再了看看管理者类(Caretaker)的具体代码:
public class Caretaker {

    private Memento memento;


    public Memento getMemento() {
        return memento;
    }

    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}
  
它的作用仅仅是存储备忘录对象,而且其内部中也不应该有直接调用 Memento 中的状态改变方法。只有当用户需要对 Originator 类进行恢复时,再将存储在其中的备忘录对象取出。
  
下面是对整个流程的测试:
public class Client {
    public static void main(String[] args) {
        Originator originator = new Originator();
        Caretaker caretaker = new Caretaker();
        //在originator和caretaker中保存memento对象
        originator.setState("1");
        System.out.println("当前的状态是:" + originator.getState());
        caretaker.setMemento(originator.createMemento());

        originator.setState("2");
        System.out.println("当前的状态是:" + originator.getState());
        //从Caretaker取出Memento对象
        originator.restoreMemento(caretaker.getMemento());
        System.out.println("执行状态恢复,当前的状态是:" + originator.getState());

    }
}
  
运行结果:
当前的状态是:1
当前的状态是:2
执行状态恢复,当前的状态是:1
  
21.2 备忘录模式的应用场景
正如开头提到的,备忘录模式可以用在诸如Word文字编辑器,PhotoShop等软件的状态保存,还有数据库的备份等等场景。下面引用一个文本编辑的代码实现,来自于《设计模式》
  
21.2.1 实现文本编辑器恢复功能
/**
 * @description: 输入text的当前状态
 */
public class InputText {
    
    private StringBuilder text = new StringBuilder();

    public StringBuilder getText() {
        return text;
    }

    public void setText(StringBuilder text) {
        this.text = text;
    }
    
    //创建SnapMemento对象
    public SnapMemento createSnapMemento() {
        return new SnapMemento(this);
    }
    
    //恢复SnapMemento对象
    public void restoreSnapMemento(SnapMemento sm) {
        text = sm.getText(); 
    }
}
/**
 * @description: 快照备忘录
 */
public class SnapMemento {

    private StringBuilder text;

    public SnapMemento(InputText it) {
        text = it.getText();
    }

    public StringBuilder getText() {
        return text;
    }

    public void setText(StringBuilder text) {
        this.text = text;
    }
}

/**
 * @description: 负责SnapMemento对象的获取和存储
 */
public class SnapMementoHolder {
    private Stack<SnapMemento> snapMementos = new Stack<>();

    //获取snapMemento对象
    public SnapMemento popSnapMemento() {
        return snapMementos.pop();
    }

    //存储snapMemento对象
    public void pushSnapMemento(SnapMemento sm) {
        snapMementos.push(sm);
    }
}
/**
 * @description: 客户端
 */
public class test_memento {
    public static void main(String[] args) {
        InputText inputText = new InputText();
        StringBuilder first_stringBuilder = new StringBuilder("First StringBuilder");
        inputText.setText(first_stringBuilder);
        SnapMementoHolder snapMementoHolder = new SnapMementoHolder();
        snapMementoHolder.pushSnapMemento(inputText.createSnapMemento());

        System.out.println("当前的状态是:" + inputText.getText().toString());
        StringBuilder second_stringBuilder = new StringBuilder("Second StringBuilder");
        inputText.setText(second_stringBuilder);
        System.out.println("修改过后的状态是:" + inputText.getText().toString());
        inputText.restoreSnapMemento(snapMementoHolder.popSnapMemento());
        System.out.println("利用备忘录恢复的状态:" + inputText.getText().toString());

    }
}
  
运行结果:
当前的状态是:First StringBuilder
修改过后的状态是:Second StringBuilder
利用备忘录恢复的状态:First StringBuilder
 
21.3 备忘录模式实战
在本案例中模拟系统在发布上线的过程中记录线上配置文件用于紧急回滚(案例来源于《重学 Java 设计模式》)

 
其中配置文件中包含版本、时间、MD5、内容信息和操作人。如果一旦遇到紧急问题,系统可以通过回滚操作将配置文件回退到上一个版本中。那么备忘录存储的信息就是配置文件的内容,根据备忘录模式设计该结构:

  
- ConfigMemento:备忘录类,是对原有配置类的扩展。
- ConfigOriginator:记录者类,相当于之前的管理者(Caretaker),获取和返回备忘录对象。
- Admin:管理员类,操作修改备忘信息,相当于之前的组织者(Originator)
  

具体代码实现
ConfigFile 配置信息类
public class ConfigFile {

    private String versionNo;
    private String content;
    private Date dateTime;
    private String operator;
    
    //getset,constructor
}
  
ConfigMemento 备忘录类
public class ConfigMemento {

    private ConfigFile configFile;

    public ConfigMemento(ConfigFile configFile) {
        this.configFile = configFile;
    }

    public ConfigFile getConfigFile() {
        return configFile;
    }

    public void setConfigFile(ConfigFile configFile) {
        this.configFile = configFile;
    }
}
  
ConfigOriginator 配置文件组织者类
public class ConfigOriginator {

    private ConfigFile configFile;

    public ConfigFile getConfigFile() {
        return configFile;
    }

    public void setConfigFile(ConfigFile configFile) {
        this.configFile = configFile;
    }

    public ConfigMemento saveMemento() {
        return new ConfigMemento(configFile);
    }

    public void getMemento(ConfigMemento memento) {
        this.configFile = memento.getConfigFile();
    }
}
  
Admin 配置文件管理者类
public class Admin {

    //版本信息
    private int cursorIdx = 0;
    private List<ConfigMemento> mementoList = new ArrayList<>();
    private Map<String, ConfigMemento> mementoMap = new ConcurrentHashMap<String, ConfigMemento>();

    //新增版本信息
    public void append(ConfigMemento memento) {
        mementoList.add(memento);
        mementoMap.put(memento.getConfigFile().getVersionNo(), memento);
        cursorIdx++;
    }

    //回滚历史配置
    public ConfigMemento undo() {
        if (--cursorIdx <= 0) {
            return mementoList.get(0);
        }
        return mementoList.get(cursorIdx);
    }

    //前进历史配置
    public ConfigMemento redo() {
        if(++cursorIdx > mementoList.size()) {
            return mementoList.get(mementoList.size() - 1);
        }
        return mementoList.get(cursorIdx);
    }

    public ConfigMemento get(String versionNo) {
        return mementoMap.get(versionNo);
    }

}
  
测试类及结果
public class ApiTest {

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    @Test
    public void test_memento() {
        Admin admin = new Admin();
        ConfigOriginator configOriginator = new ConfigOriginator();

        configOriginator.setConfigFile(new ConfigFile("1000001", "配置内容1", new Date(), "ethan"));
        admin.append(configOriginator.saveMemento());

        configOriginator.setConfigFile(new ConfigFile("1000002", "配置内容2", new Date(), "ethan"));
        admin.append(configOriginator.saveMemento());

        configOriginator.setConfigFile(new ConfigFile("1000003", "配置内容3", new Date(), "ethan"));
        admin.append(configOriginator.saveMemento());

        configOriginator.setConfigFile(new ConfigFile("1000004", "配置内容4", new Date(), "ethan"));
        admin.append(configOriginator.saveMemento());

        //(第一次回滚)
        configOriginator.getMemento(admin.undo());
        logger.info("回滚undo: {}", JSON.toJSONString(configOriginator.getConfigFile()));

        //(第二次回滚)
        configOriginator.getMemento(admin.undo());
        logger.info("回滚undo: {}", JSON.toJSONString(configOriginator.getConfigFile()));

        // (前进)
        configOriginator.getMemento(admin.redo());
        logger.info("前进redo:{}", JSON.toJSONString(configOriginator.getConfigFile()));

        // (获取)
        configOriginator.getMemento(admin.get("1000002"));
        logger.info("获取get:{}", JSON.toJSONString(configOriginator.getConfigFile()));

    }
}
  
运行结果:
22:44:39.773 [main] INFO ApiTest - 回滚 undo: {"content":"配置内容 4","dateTime":1649429079642,"operator":"ethan","versionNo":"1000004"}
22:44:39.777 [main] INFO ApiTest - 回滚 undo: {"content":"配置内容 3","dateTime":1649429079642,"operator":"ethan","versionNo":"1000003"}
22:44:39.777 [main] INFO ApiTest - 前进 redo:{"content":"配置内容 4","dateTime":1649429079642,"operator":"ethan","versionNo":"1000004"}
22:44:39.777 [main] INFO ApiTest - 获取 get:{"content":"配置内容 2","dateTime":1649429079642,"operator":"ethan","versionNo":"1000002"}
  

二十二、中介者模式

22.1 定义
中介者模式用一个中介对象封装一系列对象(同事)的交互,中介者使各对象不需要显式地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
 
中介者模式的角色划分:
- 抽象中介者角色(`Mediator`):该角色定义出同事对象到中介者对象的统一接口,用于各同事角色之间的通信。
- 具体中介者角色(`ConcreteMediator`):该角色实现抽象中介者,它依赖于各同事角色,并通过协调各同事角色实现协作行为。
- 抽象同事角色(`Colleague`):该角色定义出中介者到同事对象的接口,同事对象只知道中介者而不知道其余的同事对象。
- 具体同事角色(`ConcreteColleague`):该角色实现抽象同事类,每一个具体同事类都清楚自己在小范围内的行为,而不知道大范围的目的。
  
中介者模式的类图如下:

 
22.2 使用场景
- 系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。
- 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。
  
22.3 优缺点
优点
- 减少类间的依赖,将原有的一对多的依赖变成一对一的依赖,是的对象之间的关系更易维护和理解。
- 避免同事之间过度耦合,同事类只依赖于中介者,使同事类更易被复用,中介类和同事类可以相对独立地演化。
- 中介者模式将对象的行为和协作抽象化,将对象在小尺度的行为上与其他对象的相互作用分开处理。
  
缺点
- 中介者模式降低了同事对象的复杂性,但增加了中介者类的复杂性。
- 中介者类经常充满了各个具体同事类的关系协调代码,这种代码是不能复用的。
  
22.4 实现
场景描述:过年回家,你又被悲催地催婚了,父母带你去了婚姻中介所……
  
类图如下:

  
实现如下:
/**
 * 婚姻中介所:抽象中介者角色
 */
public interface MarriageAgency {
    /**
     * 为Person配对
     * @param person
     */
    void pair(Person person);
 
    /**
     * 注册为会员
     * @param person
     */
    void register(Person person);
 
}
/**
 * 具体中介者角色
 */
public class MarriageAgencyImp implements MarriageAgency{
    //男会员列表
    List<Man> men = new ArrayList<>();
    //女会员列表
    List<Woman> women = new ArrayList<>();
 
    /**
     * 根据条件找对象
     * @param person
     */
    @Override
    public void pair(Person person) {
        //如果是男的,在女会员列表中找合适的,反之则在男会员列表中找
        if("男".equals(person.sex)){
            for(Woman w : women){
                if(w.age == person.requeireAge){
                    System.out.println(person.name + "和" +  w.name + "终成眷属");
                    return;
                }
            }
        }else if("女".equals(person.sex)){
            for(Man m : men){
                if(m.age == person.requeireAge){
                    System.out.println(person.name + "和" +  m.name + "终成眷属");
                    return;
                }
            }
        }
        System.out.println("目前找不到合适的对象");
    }
 
    /**
     * 注册会员
     * @param person
     */
    @Override
    public void register(Person person) {
        if("男".equals(person.sex)){
            men.add((Man) person);
        }else if("女".equals(person.sex)){
            women.add((Woman) person);
        }
    }
 
}
/**
 * 抽象同事角色
 */
public abstract class Person {
    //姓名
    String name;
    //年龄
    int age;
    //性别
    String sex;//要求的年龄
    int requeireAge;
    MarriageAgency marriageAgency;
 
    public Person(String name, int age, String sex, int requeireAge, MarriageAgency marriageAgency) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.requeireAge = requeireAge;
        this.marriageAgency = marriageAgency;
        marriageAgency.register(this);
    }
 
    /**
     * 找对象
     */
    public void findPartner(){
        marriageAgency.pair(this);
    }
 
}
/**
 * 具体同事角色
 */
public class Man extends Person{
    public Man(String name, int age, String sex, int requeireAge, MarriageAgency marriageAgency) {
        super(name, age, sex, requeireAge, marriageAgency);
    }
}
/**
 * 具体同事角色
 */
public class Woman extends Person{
    public Woman(String name, int age, String sex, int requeireAge, MarriageAgency marriageAgency) {
        super(name, age, sex, requeireAge, marriageAgency);
    }
}
public class Client {
    public static void main(String[] args) {
        MarriageAgency marriageAgency = new MarriageAgencyImp();
        Person man1 = new Man("张强", 24, "男", 22, marriageAgency);
        Person man2 = new Man("康欣", 27, "男", 20, marriageAgency);
        Person woman1 = new Woman("王慧", 22, "女", 24, marriageAgency);
        Person woman2 = new Woman("李静", 23, "女", 26, marriageAgency);
        Person woman3 = new Woman("赵蕊", 24, "女", 24, marriageAgency);
        man1.findPartner();
        man2.findPartner();
    }
}
  
运行结果:
张强和王慧终成眷属
目前找不到合适的对象
  

二十三、解释器模式

23.1 定义
解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器。
  
下图是解释器模式的 UML 结构图。

  
解释器模式主要包含如下几个角色:
- AbstractExpression:抽象表达式。声明一个抽象的解释操作,该接口为抽象语法树中所有的节点共享。

- TerminalExpression:终结符表达式。实现与文法中的终结符相关的解释操作。实现抽象表达式中所要求的方法。文法中每一个终结符都有一个具体的终结表达式与之相对应。

- NonterminalExpression:非终结符表达式。为文法中的非终结符相关的解释操作。
- Context:环境类。包含解释器之外的一些全局信息。
- Client:·客户类。
  
抽象语法树描述了如何构成一个复杂的句子,通过对抽象语法树的分析,可以识别出语言中的终结符和非终结符类。 在解释器模式中由于每一种终结符表达式、非终结符表达式都会有一个具体的实例与之相对应,所以系统的扩展性比较好。
  
现在我们用解释器模式来实现一个基本的递增递减操作。
public class Context {
    private String input;
    private int output;
 
    public Context(String input) {
        this.input = input;
    }
 
    public String getInput() {
        return input;
    }
 
    public void setInput(String input) {
        this.input = input;
    }
 
    public int getOutput() {
        return output;
    }
 
    public void setOutput(int output) {
        this.output = output;
    }
}
public abstract class Expression {
    public abstract void interpret(Context context);
}
public class PlusExpression extends Expression {
 
    public void interpret(Context context){
        System.out.println("自动递增");
        //获得上下文环境
        String input=context.getInput();
        //进行类转换
        int intput=Integer.parseInt(input);
        ++intput;
        //对上下文环境重新进行复制
        context.setInput(String.valueOf(intput));
        context.setOutput(intput);
    }
}
public class MinusExpression extends Expression {
    @Override
    public void interpret(Context context) {
        System.out.println("自动递减");
        //获得上下文环境
        String input=context.getInput();
        //进行类转换
        int intput=Integer.parseInt(input);
        --intput;
 
        context.setInput(String.valueOf(intput));
        context.setOutput(intput);
    }
}

public class Client {
    public static void main(String[] args) {
        String number="10";
        Context context=new Context(number);
        //递增
        Expression expression=new PlusExpression();
        expression.interpret(context);
        System.out.println(context.getOutput());
        //递减
        Expression expression1=new MinusExpression();
        expression1.interpret(context);
        System.out.println(context.getOutput());
    }
}
  
运行结果:
自动递增
11
自动递减
10
 
23.2 优点
1. 可扩展性比较好,灵活。
2. 增加了新的解释表达式的方式。
3. 易于实现文法。
  
23.3 缺点
1. 执行效率比较低,可利用场景比较少。
2. 对于复杂的文法比较难维护。
  
23.4 模式适用场景
1. 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
2. 一些重复出现的问题可以用一种简单的语言来进行表达。
3. 文法较为简单。
  
23.5 模式总结
1. 在解释器模式中由于语法是由很多类表示的,所以可扩展性强。
2. 虽然解释器的可扩展性强,但是如果语法规则的数目太大的时候,该模式可能就会变得异常复杂。所以解释器模式适用于文法较为简单的。
3. 解释器模式可以处理脚本语言和编程语言。常用于解决某一特定类型的问题频繁发生情况。
  

二十四、观察者模式

观察者模式是使用频率最高的设计模式之一,它用于建立一种对象与对象之间的依赖关系, 一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。在观察者模式中,发 生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察 者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。
  
24.1 定义与特点
观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
  
观察者模式是一种对象行为型模式,其主要优点如下。

- 观察者模式可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色。

- 观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象 观察者的集合,无须了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因 此它们可以属于不同的抽象化层次。
- 观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对 多系统设计的难度。
- 观察者模式满足“开闭原则”的要求,增加新的具体观察者无须修改原有系统代码,在具体 观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。
  
它的主要缺点如下。
- 如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可 能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只 是知道观察目标发生了变化。
  
24.2 结构
实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。
  
观察者模式的主要角色如下。
- 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
- 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被用。
- 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
  

24.3 实现
观察者模式的实现代码如下:
import java.util.*;
public class ObserverPattern {
    public static void main(String[] args) {
        Subject subject=new ConcreteSubject();
        Observer obs1=new ConcreteObserver1();
        Observer obs2=new ConcreteObserver2();
        subject.add(obs1);
        subject.add(obs2);
        subject.notifyObserver();
    }
}
//抽象目标
abstract class Subject {
    protected List<Observer> observers=new ArrayList<Observer>();   
    //增加观察者方法
    public void add(Observer observer) {
        observers.add(observer);
    }    
    //删除观察者方法
    public void remove(Observer observer) {
        observers.remove(observer);
    }   
    public abstract void notifyObserver(); //通知观察者方法
}
//具体目标
class ConcreteSubject extends Subject {
    public void notifyObserver() {
        System.out.println("具体目标发生改变...");
        System.out.println("--------------");       
       
        for(Object obs:observers) {
            ((Observer)obs).response();
        }
       
    }          
}
//抽象观察者
interface Observer {
    void response(); //反应
}
//具体观察者1
class ConcreteObserver1 implements Observer {
    public void response() {
        System.out.println("具体观察者1作出反应!");
    }
}
//具体观察者1
class ConcreteObserver2 implements Observer {
    public void response() {
        System.out.println("具体观察者2作出反应!");
    }
}
  
运行结果:
具体目标发生改变...
--------------
具体观察者 1 作出反应!
具体观察者 2 作出反应!
 
24.4 应用实例
【例 1】利用观察者模式设计一个程序,分析“人民币汇率”的升值或贬值对进口公司的进口产品成本或出口公司的出口产品收入以及公司的利润率的影响。
  
分析:当“人民币汇率”升值时,进口公司的进口产品成本降低且利润率提升,出口公司的出口产品收入降低且利润率降低;当“人民币汇率”贬值时,进口公司的进口产品成本提升且利润率降低,出口公司的出口产品收入提升且利润率提升。
  
这里的汇率(Rate)类是抽象目标类,它包含了保存观察者(Company)的 List 和增加/删除观察者的方法,以及有关汇率改变的抽象方法 change(int number);而人民币汇率(RMBrate)类是具体目标, 它实现了父类的 change(int number)方法,即当人民币汇率发生改变时通过相关公司;公司(Company)类是抽象观察者,它定义了一个有关汇率反应的抽象方法 response(int number);进口公司(ImportCompany)类和出口公司(ExportCompany)类是具体观察者类,它们实现了父类的 response(int number) 方法,即当它们接收到汇率发生改变的通知时作为相应的反应。
  
程序代码如下:
import java.util.*;
public class RMBrateTest {
    public static void main(String[] args) {
        Rate rate=new RMBrate();         
        Company watcher1=new ImportCompany(); 
        Company watcher2=new ExportCompany();           
        rate.add(watcher1); 
        rate.add(watcher2);           
        rate.change(10);
        rate.change(-9);
    }
}
//抽象目标:汇率
abstract class Rate {
    protected List<Company> companys=new ArrayList<Company>();   
    //增加观察者方法
    public void add(Company company) {
        companys.add(company);
    }    
    //删除观察者方法
    public void remove(Company company) {
        companys.remove(company);
    }   
    public abstract void change(int number);
}
//具体目标:人民币汇率
class RMBrate extends Rate {
    public void change(int number) {       
        for(Company obs:companys) {
            ((Company)obs).response(number);
        }       
    }
 
}
//抽象观察者:公司
interface Company {
    void response(int number);
}

//具体观察者1:进口公司 
class ImportCompany implements Company {
    public void response(int number) {
        if(number>0) {
            System.out.println("人民币汇率升值"+number+"个基点,降低了进口产品成本,提升了进口公司利润率。"); 
        } else if(number<0) {
              System.out.println("人民币汇率贬值"+(-number)+"个基点,提升了进口产品成本,降低了进口公司利润率。"); 
        }
    } 
} 
//具体观察者2:出口公司
class ExportCompany implements Company {
    public void response(int number) {
        if(number>0) {
            System.out.println("人民币汇率升值"+number+"个基点,降低了出口产品收入,降低了出口公司的销售利润率。"); 
        }
        else if(number<0) {
              System.out.println("人民币汇率贬值"+(-number)+"个基点,提升了出口产品收入,提升了出口公司的销售利润率。"); 
        }
    } 
}
  
运行结果:
人民币汇率升值 10 个基点,降低了进口产品成本,提升了进口公司利润率。
人民币汇率升值 10 个基点,降低了出口产品收入,降低了出口公司的销售利润率。
人民币汇率贬值 9 个基点,提升了进口产品成本,降低了进口公司利润率。
人民币汇率贬值 9 个基点,提升了出口产品收入,提升了出口公司的销售利润率。
  
观察者模式在软件幵发中用得最多的是窗体程序设计中的事件处理,窗体中的所有组件都是“事件源”,也就是目标对象,而事件处理程序类的对象是具体观察者对象。下面以一个学校铃声的事件处理程序为例,介绍 Windows 中的“事件处理模型”的工作原理。
  
【例 2】利用观察者模式设计一个学校铃声的事件处理程序。
  
分析:在本实例中,学校的“铃”是事件源和目标,“老师”和“学生”是事件监听器和具体观察者,“铃声”是事件类。学生和老师来到学校的教学区,都会注意学校的铃,这叫事件绑定;当上课时间或下课时间到,会触发铃发声,这时会生成“铃声”事件;学生和老师听到铃声会开始上课或下课,这叫事件处理。这个实例非常适合用观察者模式实现。
  
现在用“观察者模式”来实现该事件处理模型。首先,定义一个铃声事件(RingEvent)类,它记录了铃声的类型(上课铃声/下课铃声);再定义一个学校的铃(BellEventSource)类,它是事件源,是观察者目标类,该类里面包含了监听器容器 listener,可以绑定监听者(学生或老师),并且有产生铃声事件和通知所有监听者的方法;然后,定义一声事件监听者(BellEventListener)类,它是抽象观察者,它包含了铃声事件处理方法 heardBell(RingEvent e);最后,定义老师类(TeachEventListener)和学生类(StuEventListener),它们是事件监听器,是具体观察者,听到铃声会去上课或下课。
  
程序代码如下:
import java.util.*;
public class BellEventTest {
    public static void main(String[] args) {
        BellEventSource bell=new BellEventSource();    //铃(事件源)    
        bell.addPersonListener(new TeachEventListener()); //注册监听器(老师)
        bell.addPersonListener(new StuEventListener());    //注册监听器(学生)
        bell.ring(true);   //打上课铃声
        System.out.println("------------");   
        bell.ring(false);  //打下课铃声
    }
}
//铃声事件类:用于封装事件源及一些与事件相关的参数
class RingEvent extends EventObject {   
    private static final long serialVersionUID=1L;
    private boolean sound;    //true表示上课铃声,false表示下课铃声
    public RingEvent(Object source,boolean sound)
    {
        super(source);
        this.sound=sound;
    }   
    public void setSound(boolean sound)
    {
        this.sound=sound;
    }
    public boolean getSound()
    {
        return this.sound;
    }
}

//目标类:事件源,铃
class BellEventSource {    
    private List<BellEventListener> listener; //监听器容器
    public BellEventSource() { 
        listener=new ArrayList<BellEventListener>();        
    }
    //给事件源绑定监听器 
    public void addPersonListener(BellEventListener ren) { 
        listener.add(ren); 
    }
    //事件触发器:敲钟,当铃声sound的值发生变化时,触发事件。
    public void ring(boolean sound) {
        String type=sound?"上课铃":"下课铃";
        System.out.println(type+"响!");
        RingEvent event=new RingEvent(this, sound);     
        notifies(event);    //通知注册在该事件源上的所有监听器                
    }   
    //当事件发生时,通知绑定在该事件源上的所有监听器做出反应(调用事件处理方法)
    protected void notifies(RingEvent e) { 
        BellEventListener ren=null; 
        Iterator<BellEventListener> iterator=listener.iterator(); 
        while(iterator.hasNext()) { 
            ren=iterator.next(); 
            ren.heardBell(e); 
        } 
    }
}
//抽象观察者类:铃声事件监听器
interface  BellEventListener extends EventListener {
    //事件处理方法,听到铃声
    public void heardBell(RingEvent e);
}
//具体观察者类:老师事件监听器
class TeachEventListener implements BellEventListener {
    public void heardBell(RingEvent e) {        
        if(e.getSound()) {
            System.out.println("老师上课了...");           
        } else {
            System.out.println("老师下课了...");   
        }          
    }
}
//具体观察者类:学生事件监听器
class StuEventListener implements BellEventListener {
    public void heardBell(RingEvent e) {        
        if(e.getSound()) {
            System.out.println("同学们,上课了...");           
        } else {
            System.out.println("同学们,下课了...");   
        }          
    }
}
  
运行结果:
上课铃响!
老师上课了...
同学们,上课了...
------------
下课铃响!
老师下课了...
同学们,下课了...
  
24.5 应用场景
通过前面的分析与应用实例可知观察者模式适合以下几种情形。
1. 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
2. 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
  
24.6 模式的扩展
在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。

 

1. Observable 类

Observable 类是抽象目标类,它有一个 Vector 向量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。

- void addObserver(Observer o) 方法:用于将新的观察者对象添加到向量中。
- void notifyObservers(Object arg) 方法:调用向量中的所有观察者对象的
update。方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。
- void setChange() 方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真时,notifyObservers() 才会通知观察者。
  
2. Observer 接口
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 void update(Observable o,Object arg) 方法,进行相应的工作。
  
【例 3】利用 Observable 类和 Observer 接口实现原油期货的观察者模式实例。
  
分析:当原油价格上涨时,空方伤心,多方局兴;当油价下跌时,空方局兴,多方伤心。本实例中的抽象目标(Observable)类在 Java 中已经定义,可以直接定义其子类,即原油期货(OilFutures)类,它是具体目标类,该类中定义一个 SetPriCe(float price) 方 法 , 当 原 油 数 据 发 生 变 化 时 调 用 其 父 类 的 notifyObservers(Object arg) 方法来通知所有观察者;另外,本实例中的抽象观察者接口(Observer)在 Java 中已经定义,只要定义其子类,即具体观察者类(包括多方类 Bull 和空方类 Bear),并实现 update(Observable o,Object arg) 方法即可。
  
程序代码如下:
import java.util.Observer;
import java.util.Observable;
public class CrudeOilFutures {
    public static void main(String[] args) {
        OilFutures oil=new OilFutures();
        Observer bull=new Bull(); //多方
        Observer bear=new Bear(); //空方
        oil.addObserver(bull);
        oil.addObserver(bear);
        oil.setPrice(10);
        oil.setPrice(-8);
    }
}
//具体目标类:原油期货
class OilFutures extends Observable {
    private float price;   
    public float getPrice() {
        return this.price; 
    }
    public void setPrice(float price) {
        super.setChanged() ;  //设置内部标志位,注明数据发生变化 
        super.notifyObservers(price);    //通知观察者价格改变了 
        this.price=price ; 
    }
}
//具体观察者类:多方
class Bull implements Observer {   
    public void update(Observable o,Object arg) {
        Float price=((Float)arg).floatValue();
        if(price>0) {
            System.out.println("油价上涨"+price+"元,多方高兴了!");
        } else {
            System.out.println("油价下跌"+(-price)+"元,多方伤心了!");
        }
    }
}
//具体观察者类:空方
class Bear implements Observer {   
    public void update(Observable o,Object arg) {
        Float price=((Float)arg).floatValue();
        if(price>0) {
            System.out.println("油价上涨"+price+"元,伤心了!");
        } else {
            System.out.println("油价下跌"+(-price)+"元,高兴了!");
        }
    }
}
  
运行结果:
油价上涨 10.0 元,空方伤心了!
油价上涨 10.0 元,多方高兴了!
油价下跌 8.0 元,空方高兴了!
油价下跌 8.0 元,多方伤心了!
  

二十五、访问者模式

25.1 定义
访问者模式封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用与这些元素的新的操作。
  
25.2 角色
访问者模式的角色划分:
- 抽象访问者角色(Visitor):该角色声明一个或多个访问操作,定义访问者可以访问哪些元素。
- 具体访问者角色(Concrete Visitor):该角色实现抽象访问者角色中的各个访问操作。
- 抽象元素角色(Element):该角色声明一个接受操作,接受一个访问者对象。
- 具体元素角色(Concrete Element):该角色实现抽象元素中的接受操作。
- 结构对象角色(Object Structure):该角色有以下责任:可以遍历结构中的所有元素,如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素,也可以设计一个复合对象或者一个集合,如 List 或者 Set。
  
访问者模式的类图如下:

  
25.3 使用场景
- 数据结构稳定,作用于数据结构的操作经常变化的时候。
- 当一个数据结构中,一些元素类需要负责与其不相关的操作的时候,为了将这些操作分离出去,以减少这些元素类的职责时,可以使用访问者模式。
- 有时在对数据结构上的元素进行操作的时候,需要区分具体的类型,这时使用访问者模式可以针对不同的类型,在访问者类中定义不同的操作,从而去除掉类型判断。
  
25.4 优缺点
优点
- 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
- 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
- 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
  
缺点
- 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每
一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
- 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
- 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
  
25.5 访问者模式的实现
场景描述:使用访问者模式完成对计算机中各种硬件资源得访问。类图如下:

  
实现过程如下:
HardWare 抽象类代表计算机的各种硬件,对应访问模式中的抽象元素角色,代码如下:
 /**
 * 抽象元素角色
 */
public abstract class HardWare {
    //类型
    String type;
 
    public HardWare(String type) {
        this.type = type;
    }
 
    public String getType() {
        return type;
    }
 
    /**
     * 运行
     */
    public abstract void run();
 
    /**
     * 接收计算机访问者
     * @param computerVisitor   指定的计算机访问者
     */
    public abstract void accept(ComputerVisitor computerVisitor);
}
   
具体元素角色:CPU 和 HardDisk,继承自抽象元素角色。
public class CPU extends HardWare{
    public CPU(String type) {
        super(type);
    }
 
    @Override
    public void run() {
        System.out.println("型号为:" + type + "的CPU在运转");
    }
 
    @Override
    public void accept(ComputerVisitor computerVisitor) {
        computerVisitor.visitCPU(this);
    }
}
public class HardDisk extends HardWare{
    public HardDisk(String type) {
        super(type);
    }
 
    @Override
    public void run() {
        System.out.println("型号为:" + type + "的硬盘在运转");
    }
 
    @Override
    public void accept(ComputerVisitor computerVisitor) {
        computerVisitor.visitHardDisk(this);
    }
}
  
抽象访问者角色,代表对计算机硬件的访问者:
/**
 * 抽象访问者角色
 */
public interface ComputerVisitor {
    /**
     * 访问CPU
     * @param cpu
     */
    void visitCPU(CPU cpu);
 
    /**
     * 访问硬盘
     * @param hardDisk
     */
    void visitHardDisk(HardDisk hardDisk);
}
  
具体访问者角色:TypeVisitor 代表对计算机硬件型号的访问者,RunVisitor 代表对计算机硬件运转的访问者
public class TypeVisitor implements ComputerVisitor{
    @Override
    public void visitCPU(CPU cpu) {
        System.out.println("CPU的型号为:" + cpu.getType());
    }
 
    @Override
    public void visitHardDisk(HardDisk hardDisk) {
        System.out.println("硬盘的型号为:" + hardDisk.getType());
    }
}
public class RunVisitor implements ComputerVisitor{
    @Override
    public void visitCPU(CPU cpu) {
        cpu.run();
    }
 
    @Override
    public void visitHardDisk(HardDisk hardDisk) {
        hardDisk.run();
    }
}
   
结构对象角色:Computer 代表计算机,由 CPU 和硬盘组成。
public class Computer {
    private HardWare cpu;
    private HardWare hardDisk;
 
    public Computer() {
        this.cpu = new CPU("Intel Core i7-620");
        this.hardDisk = new HardDisk("Seagate 500G 7200转");
    }
 
    public void accept(ComputerVisitor computerVisitor){
        cpu.accept(computerVisitor);
        hardDisk.accept(computerVisitor);
    }
}
 
测试类如下:
public class Client {
    public static void main(String[] args) {
        Computer computer = new Computer();
        ComputerVisitor typeVisitor = new TypeVisitor();
        ComputerVisitor runVisitor = new RunVisitor();
 
        computer.accept(typeVisitor);
        System.out.println("==========");
        computer.accept(runVisitor);
    }
}
    

二十六、总结

设计模式有两种分类方法,即根据模式的目的来分和根据模式的作用的范围来分。
  
根据模式是用来完成什么工作来划分,这种方式可分为创建型模式结构型模式行为型模式 3 种。
创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。
GoF 中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。
结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,GoF 中提供了代理、适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。
行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。
GoF 中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。
  
根据模式是主要用于类上还是主要用于对象上来分,这种方式可分为类模式对象模式两种。
类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时刻便确定下来了。GoF 中的工厂方法、(类)适配器、模板方法、解释器属于该模式。
对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。GoF 中除了以上 4 种,其他的都是对象模式。
  
下表介绍了这 23 种设计模式的分类:

| 范围\目的  | 创建型模式                           | 结构型模式                                                   | 行为型模式                                                   |
| --------- | ----------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 类模式     | 工厂方法                             | (类)适配器                                                 | 模板方法、解释器                                             |
| 对象模式   | 单例<br>原型<br>抽象工厂<br/>建造者   | 代理<br/>(对象)适配器<br/>桥接<br/>装饰<br/>外观<br/>享元<br/>组合 | 策略<br/>命令<br/>职责链<br/>状态<br/>观察者<br/>中介者<br/>迭代器<br/>访问者<br/>备忘录 |
|           |                                      |                                                              |                                                              |
  
1、工厂方法
追 MM 少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是 MM 爱吃的东西,虽然口味有所不同,但不管你带 MM 去麦当劳或肯德基,只管向服务员说「来四个鸡翅」就行了。
麦当劳和肯德基就是生产鸡翅的 Factory 工厂模式:客户类和工厂类分开。
消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。
缺点:是当产品修改时,工厂类也要做相应的修改。如:如何创建及如何向客户端提供。
  
2、建造者模式
MM 最爱听的就是「我爱你」这句话了,见到不同地方的 MM,要能够用她们的方言跟她说这句话哦。
我有一个多种语言翻译机,上面每种语言都有一个按键,见到 MM 我只要按对应的键,它就能够用相应的语言说出「我爱你」这句话了,国外的 MM 也可以轻松搞定,这就是我的「我爱你」builder。
建造模式:将产品的内部表象和产品的生成过程分割开来,从而使一个建造过程生成具有不同的内部表象的产品对象。
建造模式使得产品内部表象可以独立的变化,客户不必知道产品内部组成的细节。
建造模式可以强制实行一种分步骤进行的建造过程。
  
3、抽象工厂
请 MM 去麦当劳吃汉堡,不同的 MM 有不同的口味,要每个都记住是一件烦人的事情,我一般采用 Factory Method 模式。
带着 MM 到服务员那儿,说「要一个汉堡」,具体要什么样的汉堡呢,让 MM 直接跟服务员说就行了。
工厂方法模式:核心工厂类不再负责所有产品的创建,而是将具体创建的工作交给子类去做,成为一个抽象工厂角色,仅负责给出具体工厂类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。
  
4、原型模式

跟 MM 用 微信 聊天,一定要说些深情的话语了,我搜集了好多肉麻的情话,需要时只要 copy 出来放到 微信 里面就行了,这就是我的情话 prototype 了。(100 块钱一份,你要不要)

原始模型模式:通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的方法创建出更多同类型的对象。
原始模型模式允许动态的增加或减少产品类,产品类不需要非得有任何事先确定的等级结构,原始模型模式适用于任何的等级结构。缺点是每一个类都必须配备一个克隆方法。
  

5、单态模式
有 7 个漂亮的老婆,她们的老公都是你,你就是你们家里的老公 Singleton,她们只要说道「老公」,都是指的同一个人,那就是你。(刚才做了个梦啦,哪有这么好的事)
单例模式:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例模式。单例模式只应在有真正的 “单一实例” 的需求时才可使用。
  

6、适配器模式
在朋友聚会上碰到了一个美女,从香港来的,可我不会说粤语,她不会说普通话,只好求助于我的朋友了。
朋友作为我和美女之间的 Adapter,让我和美女以相互交谈了!(也不知道他会不会耍我)
适配器(变压器)模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口原因不匹配而无法一起工作的两个类能够一起工作。适配类可以根据参数返还一个合适的实例给客户端。
  
7、桥接模式
早上碰到 MM,要说早上好,晚上碰到 MM,要说晚上好;碰到 MM 穿了件新衣服,要说你的衣服好漂亮哦,碰到 MM 新做的发型,要说你的头发好漂亮哦。
不要问我 “早上碰到 MM 新做了个发型怎么说” 这种问题,自己用 BRIDGE 组合一下不就行了。
桥接模式:将抽象化与实现化脱耦,使得二者可以独立的变化,也就是说将他们之间的强关联变成弱关联,也就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以独立的变化。
  
8、合成模式
你女朋友今天过生日。“我过生日,你要送我一件礼物。”“嗯,好吧,去商店,你自己挑。”
“这件 T 恤挺漂亮,买,这条裙子好看,买,这个包也不错,买。”“喂,买了三件了呀,我只答应送一件礼物的哦。”
“什么呀,T 恤加裙子加包包,正好配成一套呀,小姐,麻烦你包起来。”“……”,MM 都会用 Composite 模式了,你会了没有?
合成模式:合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。
合成模式就是一个处理对象的树结构的模式。合成模式把部分与整体的关系用树结构表示出来。
合成模式使得客户端把一个个单独的成分对象和由他们复合而成的合成对象同等看待。
  

9、装饰模式
女朋友过完轮到妹妹过生日,还是不要叫她自己挑了,不然这个月伙食费肯定玩完。
拿出我去年在华山顶上照的照片,在背面写上 “最好的的礼物,就是爱你的哥哥”,再到街上礼品店买了个像框(卖礼品的 MM 也很漂亮哦),再找隔壁搞美术设计的 Mike 设计了一个漂亮的盒子装起来……
我们都是 Decorator,最终都在修饰我这个人呀,怎么样,看懂了吗?
装饰模式:装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案,提供比继承更多的灵活性。
动态给一个对象增加功能,这些功能可以再动态的撤消。增加由一些基本功能的排列组合而产生的非常大量的功能。

  

10、门面模式
我有一个专业的 Nikon 相机,我就喜欢自己手动调光圈、快门,这样照出来的照片才专业,但 MM 可不懂这些,教了半天也不会。
幸好相机有 Facade 设计模式,把相机调整到自动档,只要对准目标按快门就行了,一切由相机自动调整,这样 MM 也可以用这个相机给我拍张照片了。
门面模式:外部与一个子系统的通信必须通过一个统一的门面对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。
每一个子系统只有一个门面类,而且此门面类只有一个实例,也就是说它是一个单例模式。但整个系统可以有多个门面类。
  

11、享元模式
每天跟 MM 发短信,手指都累死了,最近买了个新手机,可以把一些常用的句子存在手机里,要用的时候,直接拿出来,在前面加上 MM 的名字就可以发送了,再不用一个字一个字敲了。
共享的句子就是 Flyweight,MM 的名字就是提取出来的外部特征,根据上下文情况使用。享元模式:FLYWEIGHT 在拳击比赛中指最轻量级。
享元模式以共享的方式高效的支持大量的细粒度对象。享元模式能做到共享的关键是区分内蕴状态和外蕴状态。
内蕴状态存储在享元内部,不会随环境的改变而有所不同。外蕴状态是随环境的改变而改变的。外蕴状态不能影响内蕴状态,它们是相互独立的。
将可以共享的状态和不可以共享的状态从常规类中区分开来,将不可以共享的状态从类里剔除出去。
客户端不可以直接创建被共享的对象,而应当使用一个工厂对象负责创建被共享的对象。享元模式大幅度的降低内存中对象的数量。
  
12、代理模式
跟 MM 在网上聊天,一开头总是 “hi,你好”,“你从哪儿来呀”,“你多大了”,“身高多少呀” 这些话。

真烦人,写个程序做为我的 Proxy 吧,凡是接收到这些话都设置好了自己的回答,接收到其他的话时再通知我回答,怎么样,酷吧。

代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。
代理就是一个人或一个机构代表另一个人或者一个机构采取行动。某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。
客户端分辨不出代理主题对象与真实主题对象。代理模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入。
  

13、责任链模式
晚上去上英语课,为了好开溜坐到了最后一排,哇,前面坐了好几个漂亮的 MM 哎,找张纸条,写上 “Hi,可以做我的女朋友吗?如果不愿意请向前传”。
纸条就一个接一个的传上去了,糟糕,传到第一排的 MM 把纸条传给老师了,呀,快跑!
责任链模式:在责任链模式中,很多对象由每一个对象对其下家的引用而接起来形成一条链。
请求在这个链上传递,直到链上的某一个对象决定处理此请求。客户并不知道链上的哪一个对象最终处理这个请求,系统可以在不影响客户端的情况下动态的重新组织链和分配责任。
处理者有两个选择:承担责任或者把责任推给下家。一个请求可以最终不被任何接收端对象所接受。
  

14、命令模式
俺有一个 MM 家里管得特别严,没法见面,只好借助于她弟弟在我们俩之间传送信息,她对我有什么指示,就写一张纸条让她弟弟带给我。
这不,她弟弟又传送过来一个 COMMAND,为了感谢他,我请他吃了碗杂酱面。
哪知道他说:“我同时给我姐姐三个男朋友送 COMMAND,就数你最小气,才请我吃面。”
命令模式:命令模式把一个请求或者操作封装到一个对象中。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。
命令模式允许请求的一方和发送的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否执行,何时被执行以及是怎么被执行的。系统支持命令的撤消。
  

15、解释器模式
有一个《泡 MM 真经》,上面有各种泡 MM 的攻略,比如说去吃西餐的步骤、去看电影的方法等等,跟 MM 约会时,只要做一个 Interpreter,照着上面的脚本执行就可以了。
解释器模式:给定一个语言后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。
解释器模式将描述怎样在有了一个简单的文法后,使用模式设计解释这些语句。
在解释器模式里面提到的语言是指任何解释器对象能够解释的任何组合。在解释器模式中需要定义一个代表文法的命令类的等级结构,也就是一系列的组合规则。

每一个命令对象都有一个解释方法,代表对命令对象的解释。命令对象的等级结构中的对象的任何排列组合都是一个语言。
  
16、迭代模式
你爱上了 Ann,不顾一切的向她求婚。Ann:“想要我跟你结婚,得答应我的条件”。
你:“什么条件我都答应,你说吧” 。
Ann:“我看上了那个一克拉的钻石”。
你:“我买,我买,还有吗?” 。
Ann:“我看上了湖边的那栋别墅” 。
你:“我买,我买,还有吗?”
Ann:“我看上那辆法拉利跑车” 。
你脑袋嗡的一声,坐在椅子上,一咬牙:“我买,我买,还有吗?”
迭代模式:迭代模式可以顺序访问一个聚集中的元素而不必暴露聚集的内部表象。多个对象聚在一起形成的总体称之为聚集,聚集对象是能够包容一组对象的容器对象。迭代子模式将迭代逻辑封装到一个独立的子对象中,从而与聚集本身隔开。迭代模式简化了聚集的界面。每一个聚集对象都可以有一个或一个以上的迭代子对象,每一个迭代子的迭代状态可以是彼此独立的。迭代算法可以独立于聚集角色变化。
  

17、调停者模式
四个 MM 打麻将,相互之间谁应该给谁多少钱算不清楚了,幸亏当时我在旁边,按照各自的筹码数算钱,赚了钱的从我这里拿,赔了钱的也付给我,一切就 OK 啦,俺得到了四个 MM 的电话。
调停者模式:调停者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用。从而使他们可以松散偶合。
当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用。保证这些作用可以彼此独立的变化。
调停者模式将多对多的相互作用转化为一对多的相互作用。调停者模式将对象的行为和协作抽象化,把对象在小尺度的行为上与其他对象的相互作用分开处理。
  

18、备忘录模式
同时跟几个 MM 聊天时,一定要记清楚刚才跟 MM 说了些什么话,不然 MM 发现了会不高兴的哦,幸亏我有个备忘录,刚才与哪个 MM 说了什么话我都拷贝一份放到备忘录里面保存,这样可以随时察看以前的记录啦。
备忘录模式:备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。
备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捉住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。
  

19、观察者模式
想知道咱们公司最新 MM 情报吗?加入公司的 MM 情报邮件组就行了,jack 负责搜集情报,他发现的新情报不用一个一个通知我们,直接发布给邮件组,我们作为订阅者(观察者)就可以及时收到情报啦。
观察者模式:观察者模式定义了一种一队多的依赖关系,让多个观察者对象同时监听某一个主题对象。
这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己。
  
20、状态模式
跟 MM 交往时,一定要注意她的状态哦,在不同的状态时她的行为会有不同。比如你约她今天晚上去看电影,对你没兴趣的 MM 就会说:“有事情啦”,对你不讨厌但还没喜欢上的 MM 就会说:“好啊,不过可以带上我同事么?”,已经喜欢上你的 MM 就会说:“几点钟?看完电影再去泡吧怎么样?”。
当然你看电影过程中表现良好的话,也可以把 MM 的状态从不讨厌不喜欢变成喜欢哦。
状态模式:状态模式允许一个对象在其内部状态改变的时候改变行为。这个对象看上去象是改变了它的类一样。
状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。
状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。状态模式需要对每一个系统可能取得的状态创立一个状态类的子类。当系统的状态变化时,系统便改变所选的子类。
  
21、策略模式
跟不同类型的 MM 约会,要用不同的策略,有的请电影比较好,有的则去吃小吃效果不错,有的去海边浪漫最合适,单目的都是为了得到 MM 的芳心,我的追 MM 锦囊中有好多 Strategy 哦。
策略模式:策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。
策略模式使得算法可以在不影响到客户端的情况下发生变化。策略模把行为和环境分开。
环境类负责维持和查询行为类,各种算法在具体的策略类中提供。由于算法和环境独立开来,算法的增减,修改都不会影响到环境和客户端。
  

22、模板方法模式
追女孩子的步骤分为制造巧遇、打破僵局、展开追求、牵手成功 (Template method)。
但每个步骤针对不同的情况,都有不一样的做法,这就要看你随机应变啦 (具体实现)。
模板方法模式:模板方法模式准备一个抽象类,将部分逻辑以具体方法以及具体构造子类的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。
不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。先制定一个顶级逻辑框架,而将逻辑的细节留给具体的子类去实现。
  

23、访问者模式
情人节到了,要给每个 MM 送一束鲜花和一张卡片,可是每个 MM 送的花都要针对她个人的特点,每张卡片也要根据个人的特点来挑,我一个人哪搞得清楚。
还是找花店老板和礼品店老板做一下 Visitor,让花店老板根据 MM 的特点选一束花,让礼品店老板也根据每个人特点选一张卡,这样就轻松多了。
访问者模式:访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。
一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。
访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由的演化。
访问者模式使得增加新的操作变的很容易,就是增加一个新的访问者类。访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。
当使用访问者模式时,要将尽可能多的对象浏览逻辑放在访问者类中,而不是放到它的子类中。
访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方寸之间不太闲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值