设计模式 之 行为型模式

news2024/11/14 2:45:26

设计模式 之 行为型模式

模式 & 描述包括
行为型模式 这些设计模式特别关注对象之间的通信。责任链模式(Chain of Responsibility Pattern)
命令模式(Command Pattern)
解释器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
备忘录模式(Memento Pattern)
观察者模式(Observer Pattern)
状态模式(State Pattern)
空对象模式(Null Object Pattern)
策略模式(Strategy Pattern)
模板模式(Template Pattern)
访问者模式(Visitor Pattern)

1. 责任链模式(Chain of Responsibility Pattern)

顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

1.1 概述

  • 意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
  • 主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
  • 何时使用:在处理消息的时候以过滤很多道。
  • 如何解决:拦截的类都实现统一接口。
  • 关键代码Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。
  • 应用实例
    1. 红楼梦中的"击鼓传花"。
    2. JS 中的事件冒泡。
    3. JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。
  • 优点
    1. 降低耦合度。它将请求的发送者和接收者解耦。
    2. 简化了对象。使得对象不需要知道链的结构。
    3. 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
    4. 增加新的请求处理类很方便。
  • 缺点
    1. 不能保证请求一定被接收。
    2. 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
    3. 可能不容易观察运行时的特征,有碍于除错。
  • 使用场景
    1. 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
    2. 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
    3. 可动态指定一组对象处理请求。
  • 注意事项:在 JAVA WEB 中遇到很多应用。

1.2 例子

我们创建抽象类 AbstractLogger,带有详细的日志记录级别。然后我们创建三种类型的记录器,都扩展了 AbstractLogger。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器。

在这里插入图片描述

1.2.1 步骤 1 . 创建抽象的记录器类

AbstractLogger.java

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);
   
}

1.2.2 步骤 2 . 创建扩展了该记录器类的实体类

ConsoleLogger.java

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);
   }
}

ErrorLogger.java

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);
   }
}

FileLogger.java

public class FileLogger extends AbstractLogger {
 
   public FileLogger(int level){
      this.level = level;
   }
 
   @Override
   protected void write(String message) {    
      System.out.println("File::Logger: " + message);
   }
}

1.2.3 步骤 3 . 创建不同类型的记录器

创建不同类型的记录器。赋予它们不同的错误级别,并在每个记录器中设置下一个记录器。每个记录器中的下一个记录器代表的是链的一部分。

ChainPatternDemo.java

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.");
   }
}

1.2.4 步骤 4 . 执行程序,输出结果

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.

2. 命令模式(Command Pattern)

命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

2.1 概述

  • 意图:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。
  • 主要解决:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。
  • 何时使用:在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者"与"行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。
  • 如何解决:通过调用者调用接受者执行命令,顺序:调用者→命令→接受者。
  • 关键代码:定义三个角色
    1. received 真正的命令执行对象
    2. Command
    3. invoker 使用命令对象的入口
  • 应用实例:struts 1 中的 action 核心控制器 ActionServlet 只有一个,相当于 Invoker,而模型层的类会随着不同的应用有不同的模型类,相当于具体的 Command。
  • 优点
    1. 降低了系统耦合度。
    2. 新的命令可以很容易添加到系统中去。
  • 缺点:使用命令模式可能会导致某些系统有过多的具体命令类。
  • 使用场景:认为是命令的地方都可以使用命令模式,比如
    1. GUI 中每一个按钮都是一条命令。
    2. 模拟 CMD。
  • 注意事项:系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作,也可以考虑使用命令模式,见命令模式的扩展。

命令模式结构示意图:

在这里插入图片描述

2.2 例子

我们首先创建作为命令的接口 Order,然后创建作为请求的 Stock 类。实体命令类 BuyStock 和 SellStock,实现了 Order 接口,将执行实际的命令处理。创建作为调用对象的类 Broker,它接受订单并能下订单。

Broker 对象使用命令模式,基于命令的类型确定哪个对象执行哪个命令。CommandPatternDemo 类使用 Broker 类来演示命令模式。

在这里插入图片描述

2.2.1 步骤 1 . 创建一个命令接口

Order.java

public interface Order {
   void execute();
}

2.2.2 步骤 2 . 创建一个请求类

Stock.java

public class Stock {
   
   private String name = "ABC";
   private int quantity = 10;
 
   public void buy(){
      System.out.println("Stock [ Name: "+name+", Quantity: " + quantity +" ] bought");
   }
   public void sell(){
      System.out.println("Stock [ Name: "+name+", Quantity: " + quantity +" ] sold");
   }
}

2.2.3 步骤 3 . 创建实现了 Order 接口的实体类

BuyStock.java

public class BuyStock implements Order {
   private Stock abcStock;
 
   public BuyStock(Stock abcStock){
      this.abcStock = abcStock;
   }
 
   public void execute() {
      abcStock.buy();
   }
}

SellStock.java

public class SellStock implements Order {
   private Stock abcStock;
 
   public SellStock(Stock abcStock){
      this.abcStock = abcStock;
   }
 
   public void execute() {
      abcStock.sell();
   }
}

2.2.4 步骤 4 . 创建命令调用类

Broker.java

import java.util.ArrayList;
import java.util.List;
 
public class Broker {
   private List<Order> orderList = new ArrayList<Order>(); 
 
   public void takeOrder(Order order){
      orderList.add(order);      
   }
 
   public void placeOrders(){
      for (Order order : orderList) {
         order.execute();
      }
      orderList.clear();
   }
}

2.2.5 步骤 5 . 使用 Broker 类来接受并执行命令

CommandPatternDemo.java

public class CommandPatternDemo {
   public static void main(String[] args) {
      Stock abcStock = new Stock();
 
      BuyStock buyStockOrder = new BuyStock(abcStock);
      SellStock sellStockOrder = new SellStock(abcStock);
 
      Broker broker = new Broker();
      broker.takeOrder(buyStockOrder);
      broker.takeOrder(sellStockOrder);
 
      broker.placeOrders();
   }
}

2.2.6 步骤 6 . 执行程序,输出结果

Stock [ Name: ABC, Quantity: 10 ] bought
Stock [ Name: ABC, Quantity: 10 ] sold

3. 解释器模式(Interpreter Pattern)

解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。

3.1 概述

  • 意图:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
  • 主要解决:对于一些固定文法构建一个解释句子的解释器。
  • 何时使用:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
  • 如何解决:构建语法树,定义终结符与非终结符。
  • 关键代码:构建环境类,包含解释器之外的一些全局信息,一般是 HashMap。
  • 应用实例:编译器、运算表达式计算。
  • 优点
    1. 可扩展性比较好,灵活。
    2. 增加了新的解释表达式的方式。
    3. 易于实现简单文法。
  • 缺点
    1. 可利用场景比较少。
    2. 对于复杂的文法比较难维护。
    3. 解释器模式会引起类膨胀。
    4. 解释器模式采用递归调用方法。
  • 使用场景
    1. 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
    2. 一些重复出现的问题可以用一种简单的语言来进行表达。
    3. 一个简单语法需要解释的场景。
  • 注意事项:可利用场景比较少,JAVA 中如果碰到可以用 expression4J 代替。

3.2 例子

我们将创建一个接口 Expression 和实现了 Expression 接口的实体类。定义作为上下文中主要解释器的 TerminalExpression 类。其他的类 OrExpression、AndExpression 用于创建组合式表达式。

InterpreterPatternDemo,我们的演示类使用 Expression 类创建规则和演示表达式的解析。

在这里插入图片描述

3.2.1 步骤 1 . 创建一个表达式接口

Expression.java

public interface Expression {
   public boolean interpret(String context);
}

3.2.2 步骤 2 . 创建实现了上述接口的实体类

TerminalExpression.java

public class TerminalExpression implements Expression {
   
   private String data;
 
   public TerminalExpression(String data){
      this.data = data; 
   }
 
   @Override
   public boolean interpret(String context) {
      if(context.contains(data)) {
         return true;
      }
      return false;
   }
}

OrExpression.java

public class OrExpression implements Expression {
    
   private Expression expr1 = null;
   private Expression expr2 = null;
 
   public OrExpression(Expression expr1, Expression expr2) { 
      this.expr1 = expr1;
      this.expr2 = expr2;
   }
 
   @Override
   public boolean interpret(String context) {      
      return expr1.interpret(context) || expr2.interpret(context);
   }
}

AndExpression.java

public class AndExpression implements Expression {
    
   private Expression expr1 = null;
   private Expression expr2 = null;
 
   public AndExpression(Expression expr1, Expression expr2) { 
      this.expr1 = expr1;
      this.expr2 = expr2;
   }
 
   @Override
   public boolean interpret(String context) {      
      return expr1.interpret(context) && expr2.interpret(context);
   }
}

3.2.3 步骤 3 . InterpreterPatternDemo 使用 Expression 类来创建规则,并解析它们

InterpreterPatternDemo.java

public class InterpreterPatternDemo {
 
   //规则:Robert 和 John 是男性
   public static Expression getMaleExpression(){
      Expression robert = new TerminalExpression("Robert");
      Expression john = new TerminalExpression("John");
      return new OrExpression(robert, john);    
   }
 
   //规则:Julie 是一个已婚的女性
   public static Expression getMarriedWomanExpression(){
      Expression julie = new TerminalExpression("Julie");
      Expression married = new TerminalExpression("Married");
      return new AndExpression(julie, married);    
   }
 
   public static void main(String[] args) {
      Expression isMale = getMaleExpression();
      Expression isMarriedWoman = getMarriedWomanExpression();
 
      System.out.println("John is male? " + isMale.interpret("John"));
      System.out.println("Julie is a married women? " + isMarriedWoman.interpret("Married Julie"));
   }
}

3.2.4 步骤 4 . 执行程序,输出结果

John is male? true
Julie is a married women? true

4. 迭代器模式(Iterator Pattern)

迭代器模式(Iterator Pattern)是 Java 和 .Net 编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。

迭代器模式属于行为型模式。

4.1 概述

  • 意图:提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。
  • 主要解决:不同的方式来遍历整个整合对象。
  • 何时使用:遍历一个聚合对象。
  • 如何解决:把在元素之间游走的责任交给迭代器,而不是聚合对象。
  • 关键代码:定义接口:hasNext, next。
  • 应用实例:JAVA 中的 iterator。
  • 优点
    1. 它支持以不同的方式遍历一个聚合对象。
    2. 迭代器简化了聚合类。
    3. 在同一个聚合上可以有多个遍历。
    4. 在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
  • 缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
  • 使用场景
    1. 访问一个聚合对象的内容而无须暴露它的内部表示。
    2. 需要为聚合对象提供多种遍历方式。
    3. 为遍历不同的聚合结构提供一个统一的接口。
  • 注意事项:迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。

4.2 例子

我们将创建一个叙述导航方法的 Iterator 接口和一个返回迭代器的 Container 接口。实现了 Container 接口的实体类将负责实现 Iterator 接口。

IteratorPatternDemo,我们的演示类使用实体类 NamesRepository 来打印 NamesRepository 中存储为集合的 Names

在这里插入图片描述

4.2.1 步骤 1 . 创建接口

Iterator.java

public interface Iterator {
   public boolean hasNext();
   public Object next();
}

Container.java

public interface Container {
   public Iterator getIterator();
}

4.2.2 步骤 2 . 创建实现了 Container 接口的实体类。该类有实现了 Iterator 接口的内部类 NameIterator

NameRepository.java

public class NameRepository implements Container {
   public String[] names = {"Robert" , "John" ,"Julie" , "Lora"};
 
   @Override
   public Iterator getIterator() {
      return new NameIterator();
   }
 
   private class NameIterator implements Iterator {
 
      int index;
 
      @Override
      public boolean hasNext() {
         if(index < names.length){
            return true;
         }
         return false;
      }
 
      @Override
      public Object next() {
         if(this.hasNext()){
            return names[index++];
         }
         return null;
      }     
   }
}

4.2.3 步骤 3 . 使用 NameRepository 来获取迭代器,并打印名字

IteratorPatternDemo.java

public class IteratorPatternDemo {
   
   public static void main(String[] args) {
      NameRepository namesRepository = new NameRepository();
 
      for(Iterator iter = namesRepository.getIterator(); iter.hasNext(); ){
         String name = (String)iter.next();
         System.out.println("Name : " + name);
      }  
   }
}

4.2.4 步骤 4 . 执行程序,输出结果

Name : Robert
Name : John
Name : Julie
Name : Lora

5. 中介者模式(Mediator Pattern)

中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。

5.1 概述

  • 意图:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
  • 主要解决:对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。
  • 何时使用:多个类相互耦合,形成了网状结构。
  • 如何解决:将上述网状结构分离为星型结构。
  • 关键代码:对象 Colleague 之间的通信封装到一个类中单独处理。
  • 应用实例
    1. 中国加入 WTO 之前是各个国家相互贸易,结构复杂,现在是各个国家通过 WTO 来互相贸易。
    2. 机场调度系统。
    3. MVC 框架,其中C(控制器)就是 M(模型)和 V(视图)的中介者。
  • 优点
    1. 降低了类的复杂度,将一对多转化成了一对一。
    2. 各个类之间的解耦。
    3. 符合迪米特原则。
  • 缺点:中介者会庞大,变得复杂难以维护。
  • 使用场景
    1. 系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。
    2. 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。
  • 注意事项:不应当在职责混乱的时候使用。

5.2 例子

我们通过聊天室实例来演示中介者模式。实例中,多个用户可以向聊天室发送消息,聊天室向所有的用户显示消息。我们将创建两个类 ChatRoom 和 User。User 对象使用 ChatRoom 方法来分享他们的消息。

MediatorPatternDemo,我们的演示类使用 User 对象来显示他们之间的通信。

在这里插入图片描述

5.2.1 步骤 1 . 创建中介类

ChatRoom.java

import java.util.Date;
 
public class ChatRoom {
   public static void showMessage(User user, String message){
      System.out.println(new Date().toString() + " [" + user.getName() +"] : " + message);
   }
}

5.2.2 步骤 2 . 创建 user 类

User.java

public class User {
   private String name;
 
   public String getName() {
      return name;
   }
 
   public void setName(String name) {
      this.name = name;
   }
 
   public User(String name){
      this.name  = name;
   }
 
   public void sendMessage(String message){
      ChatRoom.showMessage(this, message);
   }
}

5.2.3 步骤 3 . 使用 User 对象来显示他们之间的通信

MediatorPatternDemo.java

public class MediatorPatternDemo {
   public static void main(String[] args) {
      User robert = new User("Robert");
      User john = new User("John");
 
      robert.sendMessage("Hi! John!");
      john.sendMessage("Hello! Robert!");
   }
}

5.2.4 步骤 4 . 执行程序,输出结果

Thu Jan 31 16:05:46 IST 2013 [Robert] : Hi! John!
Thu Jan 31 16:05:46 IST 2013 [John] : Hello! Robert!

6. 备忘录模式(Memento Pattern)

备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。

6.1 概述

  • 意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
  • 主要解决:所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
  • 何时使用:很多时候我们总是需要记录一个对象的内部状态,这样做的目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先的状态,使得他有"后悔药"可吃。
  • 如何解决:通过一个备忘录类专门存储对象状态。
  • 关键代码:客户不与备忘录类耦合,与备忘录管理类耦合。
  • 应用实例
    1. 后悔药。
    2. 打游戏时的存档。
    3. Windows 里的 ctrl + z。
    4. IE 中的后退。
    5. 数据库的事务管理。
  • 优点
    1. 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
    2. 实现了信息的封装,使得用户不需要关心状态的保存细节。
  • 缺点:消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
  • 使用场景
    1. 需要保存/恢复数据的相关状态场景。
    2. 提供一个可回滚的操作。
  • 注意事项
    1. 为了符合迪米特原则,还要增加一个管理备忘录的类。
    2. 为了节约内存,可使用原型模式+备忘录模式。

6.2 例子

备忘录模式使用三个类 Memento、Originator 和 CareTaker。Memento 包含了要被恢复的对象的状态。Originator 创建并在 Memento 对象中存储状态。Caretaker 对象负责从 Memento 中恢复对象的状态。

MementoPatternDemo,我们的演示类使用 CareTaker 和 Originator 对象来显示对象的状态恢复。

在这里插入图片描述

6.2.1 步骤 1 . 创建 Memento 类

Memento.java

public class Memento {
   private String state;
 
   public Memento(String state){
      this.state = state;
   }
 
   public String getState(){
      return state;
   }  
}

6.2.2 步骤 2 . 创建 Originator 类

Originator.java

public class Originator {
   private String state;
 
   public void setState(String state){
      this.state = state;
   }
 
   public String getState(){
      return state;
   }
 
   public Memento saveStateToMemento(){
      return new Memento(state);
   }
 
   public void getStateFromMemento(Memento Memento){
      state = Memento.getState();
   }
}

6.2.3 步骤 3 . 创建 CareTaker 类

CareTaker.java

import java.util.ArrayList;
import java.util.List;
 
public class CareTaker {
   private List<Memento> mementoList = new ArrayList<Memento>();
 
   public void add(Memento state){
      mementoList.add(state);
   }
 
   public Memento get(int index){
      return mementoList.get(index);
   }
}

6.2.4 步骤 4 . 使用 CareTaker 和 Originator 对象

MementoPatternDemo.java

public class MementoPatternDemo {
   public static void main(String[] args) {
      Originator originator = new Originator();
      CareTaker careTaker = new CareTaker();
      originator.setState("State #1");
      originator.setState("State #2");
      careTaker.add(originator.saveStateToMemento());
      originator.setState("State #3");
      careTaker.add(originator.saveStateToMemento());
      originator.setState("State #4");
 
      System.out.println("Current State: " + originator.getState());    
      originator.getStateFromMemento(careTaker.get(0));
      System.out.println("First saved State: " + originator.getState());
      originator.getStateFromMemento(careTaker.get(1));
      System.out.println("Second saved State: " + originator.getState());
   }
}

6.2.5 步骤 5 . 验证输出

Current State: State #4
First saved State: State #2
Second saved State: State #3

7. 观察者模式(Observer Pattern)

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。

7.1 概述

  • 意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
  • 主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
  • 何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
  • 如何解决:使用面向对象技术,可以将这种依赖关系弱化。
  • 关键代码:在抽象类里有一个 ArrayList 存放观察者们。
  • 应用实例
    1. 拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。
    2. 西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。
  • 优点
    1. 观察者和被观察者是抽象耦合的。
    2. 建立一套触发机制。
  • 缺点
    1. 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
    2. 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
    3. 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
  • 使用场景
    • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
    • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
    • 一个对象必须通知其他对象,而并不知道这些对象是谁。
    • 需要在系统中创建一个触发链,A对象 的行为将影响B对象,B对象 的行为将影响 C对象 ……,可以使用观察者模式创建一种链式触发机制。
  • 注意事项
    1. JAVA 中已经有了对观察者模式的支持类。
    2. 避免循环引用。
    3. 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

7.2 例子

观察者模式使用三个类 Subject、Observer 和 Client。Subject 对象带有绑定观察者到 Client 对象和从 Client 对象解绑观察者的方法。我们创建 Subject 类、Observer 抽象类和扩展了抽象类 Observer 的实体类。

ObserverPatternDemo,我们的演示类使用 Subject 和实体类对象来演示观察者模式。

在这里插入图片描述

7.2.1 步骤 1 . 创建 Subject 类

Subject.java

import java.util.ArrayList;
import java.util.List;
 
public class Subject {
   
   private List<Observer> observers = new ArrayList<Observer>();
   private int state;
 
   public int getState() {
      return state;
   }
 
   public void setState(int state) {
      this.state = state;
      notifyAllObservers();
   }
 
   public void attach(Observer observer){
      observers.add(observer);      
   }
 
   public void notifyAllObservers(){
      for (Observer observer : observers) {
         observer.update();
      }
   }  
}

7.2.2 步骤 2 . 创建 Observer 类

Observer.java

public abstract class Observer {
   protected Subject subject;
   public abstract void update();
}

7.2.3 步骤 3 . 创建实体观察者类

BinaryObserver.java

public class BinaryObserver extends Observer{
 
   public BinaryObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
      System.out.println( "Binary String: " + Integer.toBinaryString( subject.getState() ) ); 
   }
}

OctalObserver.java

public class OctalObserver extends Observer{
 
   public OctalObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
     System.out.println( "Octal String: " + Integer.toOctalString( subject.getState() ) ); 
   }
}

HexaObserver.java

public class HexaObserver extends Observer{
 
   public HexaObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
      System.out.println( "Hex String: " + Integer.toHexString( subject.getState() ).toUpperCase() ); 
   }
}

7.2.4 步骤 4 . 使用 Subject 和实体观察者对象

ObserverPatternDemo.java

public class ObserverPatternDemo {
   public static void main(String[] args) {
      Subject subject = new Subject();
 
      new HexaObserver(subject);
      new OctalObserver(subject);
      new BinaryObserver(subject);
 
      System.out.println("First state change: 15");   
      subject.setState(15);
      System.out.println("Second state change: 10");  
      subject.setState(10);
   }
}

7.2.5 步骤 5 . 执行程序,输出结果

First state change: 15
Hex String: F
Octal String: 17
Binary String: 1111
Second state change: 10
Hex String: A
Octal String: 12
Binary String: 1010

8. 状态模式(State Pattern)

在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。

在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。

8.1 概述

  • 意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
  • 主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
  • 何时使用:代码中包含大量与对象状态有关的条件语句。
  • 如何解决:将各种具体的状态类抽象出来。
  • 关键代码:通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if…else 等条件选择语句。
  • 应用实例
    1. 打篮球的时候运动员可以有正常状态、不正常状态和超常状态。
    2. 曾侯乙编钟中,‘钟是抽象接口’,‘钟A’ 等是具体状态,‘曾侯乙编钟’ 是具体环境(Context)。
  • 优点
    1. 封装了转换规则。
    2. 枚举可能的状态,在枚举状态之前需要确定状态种类。
    3. 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
    4. 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
    5. 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
  • 缺点
    1. 状态模式的使用必然会增加系统类和对象的个数。
    2. 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
    3. 状态模式对 “开闭原则” 的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
  • 使用场景
    1. 行为随状态改变而改变的场景。
    2. 条件、分支语句的代替者。
  • 注意事项:在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。

8.2 例子

我们将创建一个 State 接口和实现了 State 接口的实体状态类。Context 是一个带有某个状态的类。

StatePatternDemo,我们的演示类使用 Context 和状态对象来演示 Context 在状态改变时的行为变化。

在这里插入图片描述

8.2.1 步骤 1 . 创建一个接口

State.java

public interface State {
   public void doAction(Context context);
}

8.2.2 步骤 2 . 创建实现接口的实体类

StartState.java

public class StartState implements State {
 
   public void doAction(Context context) {
      System.out.println("Player is in start state");
      context.setState(this); 
   }
 
   public String toString(){
      return "Start State";
   }
}

StopState.java

public class StopState implements State {
 
   public void doAction(Context context) {
      System.out.println("Player is in stop state");
      context.setState(this); 
   }
 
   public String toString(){
      return "Stop State";
   }
}

8.2.3 步骤 3 . 创建 Context 类

Context.java

public class Context {
   private State state;
 
   public Context() {
      state = null;
   }
 
   public void setState(State state) {
      this.state = state;     
   }
 
   public State getState() {
      return state;
   }
}

8.2.4 步骤 4 . 使用 Context 来查看当状态 State 改变时的行为变化

StatePatternDemo.java

public class StatePatternDemo {
   public static void main(String[] args) {
      Context context = new Context();
 
      StartState startState = new StartState();
      startState.doAction(context);
 
      System.out.println(context.getState().toString());
 
      StopState stopState = new StopState();
      stopState.doAction(context);
 
      System.out.println(context.getState().toString());
   }
}

8.2.5 步骤 5 . 执行程序,输出结果

Player is in start state
Start State
Player is in stop state
Stop State

9. 空对象模式(Null Object Pattern)

在空对象模式(Null Object Pattern)中,一个空对象取代 NULL 对象实例的检查。Null 对象不是检查空值,而是反应一个不做任何动作的关系。这样的 Null 对象也可以在数据不可用的时候提供默认的行为。

在空对象模式中,我们创建一个指定各种要执行的操作的抽象类和扩展该类的实体类,还创建一个未对该类做任何实现的空对象类,该空对象类将无缝地使用在需要检查空值的地方。

9.1 例子

我们将创建一个定义操作(在这里,是客户的名称)的 AbstractCustomer 抽象类,和扩展了 AbstractCustomer 类的实体类。工厂类 CustomerFactory 基于客户传递的名字来返回 RealCustomer 或 NullCustomer 对象。

NullPatternDemo,我们的演示类使用 CustomerFactory 来演示空对象模式的用法。

在这里插入图片描述

9.1.1 步骤 1 . 创建一个抽象类

AbstractCustomer.java

public abstract class AbstractCustomer {
   protected String name;
   public abstract boolean isNil();
   public abstract String getName();
}

9.1.2 步骤 2 . 创建扩展了上述类的实体类

RealCustomer.java

public class RealCustomer extends AbstractCustomer {
 
   public RealCustomer(String name) {
      this.name = name;    
   }
   
   @Override
   public String getName() {
      return name;
   }
   
   @Override
   public boolean isNil() {
      return false;
   }
}

NullCustomer.java

public class NullCustomer extends AbstractCustomer {
 
   @Override
   public String getName() {
      return "Not Available in Customer Database";
   }
 
   @Override
   public boolean isNil() {
      return true;
   }
}

9.1.3 步骤 3 . 创建 CustomerFactory 类

CustomerFactory.java

public class CustomerFactory {
   
   public static final String[] names = {"Rob", "Joe", "Julie"};
 
   public static AbstractCustomer getCustomer(String name){
      for (int i = 0; i < names.length; i++) {
         if (names[i].equalsIgnoreCase(name)){
            return new RealCustomer(name);
         }
      }
      return new NullCustomer();
   }
}

9.1.4 步骤 4 . 使用 CustomerFactory,基于客户传递的名字,来获取 RealCustomer 或 NullCustomer 对象

NullPatternDemo.java

public class NullPatternDemo {
   public static void main(String[] args) {
 
      AbstractCustomer customer1 = CustomerFactory.getCustomer("Rob");
      AbstractCustomer customer2 = CustomerFactory.getCustomer("Bob");
      AbstractCustomer customer3 = CustomerFactory.getCustomer("Julie");
      AbstractCustomer customer4 = CustomerFactory.getCustomer("Laura");
 
      System.out.println("Customers");
      System.out.println(customer1.getName());
      System.out.println(customer2.getName());
      System.out.println(customer3.getName());
      System.out.println(customer4.getName());
   }
}

9.1.5 步骤 5 . 执行程序,输出结果

Customers
Rob
Not Available in Customer Database
Julie
Not Available in Customer Database

10. 策略模式(Strategy Pattern)

在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

10.1 概述

  • 意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
  • 主要解决:在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。
  • 何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
  • 如何解决:将这些算法封装成一个一个的类,任意地替换。
  • 关键代码:实现同一个接口。
  • 应用实例
    1. 诸葛亮的锦囊妙计,每一个锦囊就是一个策略。
    2. 旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。
    3. JAVA AWT 中的 LayoutManager。
  • 优点
    1. 算法可以自由切换。
    2. 避免使用多重条件判断。
    3. 扩展性良好。
  • 缺点
    1. 策略类会增多。
    2. 所有策略类都需要对外暴露。
  • 使用场景
    1. 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
    2. 一个系统需要动态地在几种算法中选择一种。
    3. 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
  • 注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。

10.2 例子

我们将创建一个定义活动的 Strategy 接口和实现了 Strategy 接口的实体策略类。Context 是一个使用了某种策略的类。

StrategyPatternDemo,我们的演示类使用 Context 和策略对象来演示 Context 在它所配置或使用的策略改变时的行为变化。

在这里插入图片描述

10.2.1 步骤 1 . 创建一个接口

Strategy.java

public interface Strategy {
   public int doOperation(int num1, int num2);
}

10.2.2 步骤 2 . 创建实现接口的实体类

OperationAdd.java

public class OperationAdd implements Strategy {
   @Override
   public int doOperation(int num1, int num2) {
      return num1 + num2;
   }
}

OperationSubtract.java

public class OperationSubtract implements Strategy {
   @Override
   public int doOperation(int num1, int num2) {
      return num1 - num2;
   }
}

OperationMultiply.java

public class OperationMultiply implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2;
   }
}

10.2.3 步骤 3 . 创建 Context 类

Context.java

public class Context {
   private Strategy strategy;
 
   public Context(Strategy strategy){
      this.strategy = strategy;
   }
 
   public int executeStrategy(int num1, int num2){
      return strategy.doOperation(num1, num2);
   }
}

10.2.4 步骤 4 . 使用 Context 来查看当它改变策略 Strategy 时的行为变化

StrategyPatternDemo.java

public class StrategyPatternDemo {
   public static void main(String[] args) {
      Context context = new Context(new OperationAdd());    
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationSubtract());      
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationMultiply());    
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
   }
}

10.2.5 步骤 5 . 执行程序,输出结果

10 + 5 = 15
10 - 5 = 5
10 * 5 = 50

11. 模板模式

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。

11.1 概述

  • 意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
  • 主要解决:一些方法通用,却在每一个子类都重新写了这一方法。
  • 何时使用:有一些通用的方法。
  • 如何解决:将这些通用算法抽象出来。
  • 关键代码:在抽象类实现,其他步骤在子类实现。
  • 应用实例
    1. 在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。
    2. 西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。
    3. spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。
  • 优点
    1. 封装不变部分,扩展可变部分。
    2. 提取公共代码,便于维护。
    3. 行为由父类控制,子类实现。
  • 缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
  • 使用场景
    1. 有多个子类共有的方法,且逻辑相同。
    2. 重要的、复杂的方法,可以考虑作为模板方法。
  • 注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。

11.2 例子

我们将创建一个定义操作的 Game 抽象类,其中,模板方法设置为 final,这样它就不会被重写。Cricket 和 Football 是扩展了 Game 的实体类,它们重写了抽象类的方法。

TemplatePatternDemo,我们的演示类使用 Game 来演示模板模式的用法。

在这里插入图片描述

11.2.1 步骤 1 . 创建一个抽象类,它的模板方法被设置为 final

Game.java

public abstract class Game {
   abstract void initialize();
   abstract void startPlay();
   abstract void endPlay();
 
   //模板
   public final void play(){
 
      //初始化游戏
      initialize();
 
      //开始游戏
      startPlay();
 
      //结束游戏
      endPlay();
   }
}

11.2.2 步骤 2 . 创建扩展了上述类的实体类

Cricket.java

public class Cricket extends Game {
 
   @Override
   void endPlay() {
      System.out.println("Cricket Game Finished!");
   }
 
   @Override
   void initialize() {
      System.out.println("Cricket Game Initialized! Start playing.");
   }
 
   @Override
   void startPlay() {
      System.out.println("Cricket Game Started. Enjoy the game!");
   }
}

Football.java

public class Football extends Game {
 
   @Override
   void endPlay() {
      System.out.println("Football Game Finished!");
   }
 
   @Override
   void initialize() {
      System.out.println("Football Game Initialized! Start playing.");
   }
 
   @Override
   void startPlay() {
      System.out.println("Football Game Started. Enjoy the game!");
   }
}

11.2.3 步骤 3 . 使用 Game 的模板方法 play() 来演示游戏的定义方式

TemplatePatternDemo.java

public class TemplatePatternDemo {
   public static void main(String[] args) {
 
      Game game = new Cricket();
      game.play();
      System.out.println();
      game = new Football();
      game.play();      
   }
}

11.2.4 步骤 4 . 执行程序,输出结果

Cricket Game Initialized! Start playing.
Cricket Game Started. Enjoy the game!
Cricket Game Finished!

Football Game Initialized! Start playing.
Football Game Started. Enjoy the game!
Football Game Finished!

12. 访问者模式(Visitor Pattern)

在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。

12.1 概述

  • 意图:主要将数据结构与数据操作分离。
  • 主要解决:稳定的数据结构和易变的操作耦合问题。
  • 何时使用:需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,使用访问者模式将这些封装到类中。
  • 如何解决:在被访问的类里面加一个对外提供接待访问者的接口。
  • 关键代码:在数据基础类里面有一个方法接受访问者,将自身引用传入访问者。
  • 应用实例:您在朋友家做客,您是访问者,朋友接受您的访问,您通过朋友的描述,然后对朋友的描述做出一个判断,这就是访问者模式。
  • 优点
    1. 符合单一职责原则。
    2. 优秀的扩展性。
    3. 灵活性。
  • 缺点
    1. 具体元素对访问者公布细节,违反了迪米特原则。
    2. 具体元素变更比较困难。
    3. 违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
  • 使用场景
    1. 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
    2. 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。
  • 注意事项:访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。

12.2 例子

我们将创建一个定义接受操作的 ComputerPart 接口。Keyboard、Mouse、Monitor 和 Computer 是实现了 ComputerPart 接口的实体类。我们将定义另一个接口 ComputerPartVisitor,它定义了访问者类的操作。Computer 使用实体访问者来执行相应的动作。

VisitorPatternDemo,我们的演示类使用 Computer、ComputerPartVisitor 类来演示访问者模式的用法。

在这里插入图片描述

12.2.1 步骤 1 . 定义一个表示元素的接口

ComputerPart.java

public interface ComputerPart {
   public void accept(ComputerPartVisitor computerPartVisitor);
}

12.2.2 步骤 2 . 创建扩展了上述类的实体类

Keyboard.java

public class Keyboard  implements ComputerPart {
 
   @Override
   public void accept(ComputerPartVisitor computerPartVisitor) {
      computerPartVisitor.visit(this);
   }
}

Monitor.java

public class Monitor  implements ComputerPart {
 
   @Override
   public void accept(ComputerPartVisitor computerPartVisitor) {
      computerPartVisitor.visit(this);
   }
}

Mouse.java

public class Mouse  implements ComputerPart {
 
   @Override
   public void accept(ComputerPartVisitor computerPartVisitor) {
      computerPartVisitor.visit(this);
   }
}

Computer.java

public class Computer implements ComputerPart {
   
   ComputerPart[] parts;
 
   public Computer(){
      parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};      
   } 
 
 
   @Override
   public void accept(ComputerPartVisitor computerPartVisitor) {
      for (int i = 0; i < parts.length; i++) {
         parts[i].accept(computerPartVisitor);
      }
      computerPartVisitor.visit(this);
   }
}

12.2.3 步骤 3 . 定义一个表示访问者的接口

ComputerPartVisitor.java

public interface ComputerPartVisitor {
   public void visit(Computer computer);
   public void visit(Mouse mouse);
   public void visit(Keyboard keyboard);
   public void visit(Monitor monitor);
}

12.2.4 步骤 4 . 创建实现了上述类的实体访问者

ComputerPartDisplayVisitor.java

public class ComputerPartDisplayVisitor implements ComputerPartVisitor {
 
   @Override
   public void visit(Computer computer) {
      System.out.println("Displaying Computer.");
   }
 
   @Override
   public void visit(Mouse mouse) {
      System.out.println("Displaying Mouse.");
   }
 
   @Override
   public void visit(Keyboard keyboard) {
      System.out.println("Displaying Keyboard.");
   }
 
   @Override
   public void visit(Monitor monitor) {
      System.out.println("Displaying Monitor.");
   }
}

12.2.5 步骤 5 . 使用 ComputerPartDisplayVisitor 来显示 Computer 的组成部分

VisitorPatternDemo.java

public class VisitorPatternDemo {
   public static void main(String[] args) {
 
      ComputerPart computer = new Computer();
      computer.accept(new ComputerPartDisplayVisitor());
   }
}

12.2.6 步骤 6 . 执行程序,输出结果

Displaying Mouse.
Displaying Keyboard.
Displaying Monitor.
Displaying Computer.

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

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

相关文章

Web 性能指标

Web 性能指标 对于 Web 开发人员来说&#xff0c;如何衡量一个 Web 页面的性能一直是一个难题。 最初&#xff0c;我们使用 Time to First Byte、DomContentLoaded 和 Load 这些衡量文档加载进度的指标&#xff0c;但它们不能直接反应用户视觉体验。 为了能衡量用户视觉体验…

[附源码]计算机毕业设计springboot志愿者服务平台

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[数据结构]八大排序算法总结

作者&#xff1a; 华丞臧专栏&#xff1a;【数据结构】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。推荐一款刷题网站 &#x1f449; LeetCode刷题网站 目录 一、排序的概念及其运用 1.1排…

【目的:windows下VS2017/2022配置使用opengl - 初探-创建一个空窗口】

目的&#xff1a;windows下VS2017/2022配置使用opengl - 初探-创建一个空窗口 环境&#xff1a; 系统&#xff1a;Win10 环境&#xff1a;VS2017 64bit步骤&#xff1a; windows下visualstudio下使用opengl&#xff0c;搭建配置环境并测试窗口 1、opengl库&#xff0c;vs下自…

Crack:Open Inventor 10.12.1 Fixed Bugs List 10.12

10.12.0 - 10.12.1 Open Inventor 10.12.1 Core #OIV-4245 Shapes not rendered with MultipleInstancing  #OIV-4258 Transparency issue with SoPackedColor – CAS-41256-F0S4 OivSuite.Java #OIV-4273 Memory leak with RemoteViz Java and JVM VolumeViz #OI…

CMake中add_library的使用

CMake中的add_library命令用于使用指定的源文件向项目(project)中添加库&#xff0c;其格式如下&#xff1a; add_library(<name> [STATIC | SHARED | MODULE][EXCLUDE_FROM_ALL][<source>...]) # Normal Libraries add_library(<name> OBJECT [<source&…

【Java 快速复习】垃圾回收算法 垃圾回收器

快速理解 Java 垃圾回收算法 & 垃圾回收器 先说个关系概念&#xff0c;垃圾回收的算法是逻辑概念的定义&#xff0c;用于规范垃圾回收器实现方的一些行为&#xff0c;而垃圾回收器就是实现这些算法的工具&#xff0c;这些工具大概是一系列的 C 的类以及其实现的一些对应回…

Linux服务器上跑深度学习实验

原文地址&#xff1a;Linux上跑深度学习实验 目录远程连接环境搭建与服务器断开连接后代码停止之前一直使用Google Colab跑实验&#xff0c;因为实验的规模不大&#xff0c;配合Google Drive用起来就很舒服&#xff0c;但是最近要系统地进行实验&#xff0c;规模一下子上来了&a…

【Spring】一文带你搞懂Spring容器配置

前言 本文为大家介绍的是Spring容器配置相关知识&#xff0c;包含Bean和Configuration的使用&#xff0c;使用 AnnotationConfigApplicationContext实例化Spring容器&#xff0c;Bean注解的使用&#xff0c;Configuration的使用&#xff0c;Import 注解的使用&#xff0c;结合J…

C++中STL-set详解

目录 set/ multiset容器 1. set基本概念 2.set构造和赋值 3.set大小和交换 4.set插入和删除 5.set容器-查找和统计 6.set和multiset的区别 7.pair对组创建 8.set容器排序 9.set存放自定义数据类型 set/ multiset容器 1. set基本概念 简介: 所有元素都会在插入时自动…

使用Apisix打造家庭NAS网关,免公网IP访问

使用Apisix打造家庭NAS网关 本文使用apisix打造家庭NAS网关&#xff0c;并通过cloudflare进行穿透&#xff0c;可免公网IP访问。首先你的NAS支持Docker&#xff0c;没有NAS也没有关系&#xff0c;只要你的电脑支持Docker同样可以参照该教程。 1 依赖资源准备 准备域名: 免费…

HTML+CSS+JS做一个好看的个人网页—web网页设计作业

个人网页设计个人网页&#xff08;htmlcssjs&#xff09;——网页设计作业带背景音乐&#xff08;The way I still Love you&#xff09;、樱花飘落效果、粒子飘落效果页面美观&#xff0c;样式精美涉及&#xff08;htmlcssjs&#xff09;&#xff0c;下载后可以根据自己需求进…

8086,8088CPU管脚,奇偶地址体, 时钟信号发生器8284 ,ready信号,reset复位信号。规则字和非规则字

8086/8088均为40条引线&#xff0c;双列直插式封装&#xff0c;某些引线有多重功能&#xff0c;其功能转换有两种情况&#xff1a;一种是分时复用&#xff0c;一种是按组态定义。 用8088微处理器构成系统时&#xff0c;有两种不同的组态&#xff1a; 最小组态&#xff1a;808…

模型效果不好?推荐你8种机器学习调参技巧

大家好&#xff0c;今天给大家一篇关于机器学习调参技巧的文章 超参数调优是机器学习例程中的基本步骤之一。该方法也称为超参数优化&#xff0c;需要搜索超参数的最佳配置以实现最佳性能。 机器学习算法需要用户定义的输入来实现准确性和通用性之间的平衡。这个过程称为超参…

嵌入式开发学习之--RCC(上)

提示&#xff1a;本篇主要介绍一下不同时钟的特性和作用&#xff0c;了解为主。 文章目录前言一、RCC简介二、系统时钟简介2.1HSE 高速外部时钟信号2.2锁相环 PLL2.3系统时钟 SYSCLK2.4AHB 总线时钟 HCLK2.5 APB2 总线时钟 HCLK22.6 APB1 总线时钟 HCLK1三、其他时钟3.1RTC 时钟…

HTTP 请求是什么?

文章目录HTTP请求一&#xff0c;请求行二&#xff0c;请求头三&#xff0c;空行四&#xff0c;请求体五&#xff0c;HTTP请求示例HTTP请求 请求是由客户端向服务器发送的&#xff0c;一般可以分为请求行、请求头、空行和请求体四个部分&#xff0c;如下图所示&#xff1a; 一…

【数据集NO.2】工业检测数据集汇总(缺陷、纹理等检测)

文章目录前言一、东北大学钢材表面缺陷数据集二、天池铝型材表面缺陷数据集三、Severstal 带钢缺陷数据集四、UCI 带钢缺陷数据集五、磁瓦缺陷数据集六、RSDDs铁轨表面缺陷数据集七、印刷电路板&#xff08;PCB&#xff09;瑕疵数据集八、坑洼检测数据集九、Kylberg纹理检测十、…

C语言:文件操作(1)

1、什么是文件&#xff1f; 磁盘上的文件是文件。 但是在程序设计中&#xff0c;我们一般谈的文件有两种: 程序文件、数据文件 程序文件&#xff1a; 包括源程序文件(后缀为.c)&#xff0c;目标文件(windows环境后缀为.obj)&#xff0c;可执行程序(wndows环境后缀为.exe) 数…

初级测试到中级测试就差这几个找bug小技巧

前言 测试的过程就是一个寻找影响产品功能和用户体验bug的过程&#xff0c;测试人员发现的bug类型五花八门。 当你在上班期间&#xff0c;听到不远处传来这样的声音“你会不会提BUG&#xff0c;责任人都指派错了&#xff0c;还能好好提吗&#xff1f;” 如果哪天开发对着你说…

Babel自动生成Attribute文档实现详解

这篇文章主要为大家介绍了Babel自动生成Attribute文档实现示例详解&#xff0c;有需要的朋友可以借鉴参考下&#xff0c;希望能够有所帮助&#xff01; 1. 前言 利用Babel自动解析源码属性上的注释生成对应Markdown文档&#xff0c;这个场景的应用主要包括在组件库文档对组件…