当前位置: 首页 > news >正文

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

1.访问者模式

访问者模式在实际开发中使用的非常少,因为它比较难以实现并且应用该模式肯能会导致代码的可读性变差,可维护性变差,在没有特别必要的情况下,不建议使用访问者模式.

访问者模式(Visitor Pattern) 的原始定义是:

允许在运行时将一个或多个操作应用于一组对象,将操作与对象结构分离。

这个定义会比较抽象,但是我们依然能看出两个关键点:

一个是: 运行时使用一组对象的一个或多个操作,比如,对不同类型的文件(.pdf、.xml、.properties)进行扫描;
另一个是: 分离对象的操作和对象本身的结构,比如,扫描多个文件夹下的多个文件,对于文件来说,扫描是额外的业务操作,如果在每个文件对象上都加一个扫描操作,太过于冗余,而扫描操作具有统一性,非常适合访问者模式。

访问者模式主要解决的是数据与算法的耦合问题, 尤其是在数据结构比较稳定,而算法多变的情况下.为了不污染数据本身,访问者会将多种算法独立归档,并在访问数据时根据数据类型自动切换到对应的算法,实现数据的自动响应机制,并确保算法的自由扩展.

1.1访问者模式原理

1.1.1访问者模式包含以下主要角色:

抽象访问者(Visitor)角色:

可以是接口或者抽象类,定义了一系列操作方法,用来处理所有数据元素,通常为同名的访问方法,并以数据元素类作为入参来确定那个重载方法被调用.
具体访问者(ConcreteVisitor)角色:

访问者接口的实现类,可以有多个实现,每个访问者都需要实现所有数据元素类型的访问重载方法.
抽象元素(Element)角色:

被访问的数据元素接口,定义了一个接受访问者的方法(`accept`),其意义是指,每一个元素都要可以被访问者访问。
具体元素(ConcreteElement)角色:

具体数据元素实现类,提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法,其accept实现方法中调用访问者并将自己 "this" 传回。
对象结构(Object Structure)角色:

包含所有可能被访问的数据对象的容器,可以提供数据对象的迭代功能,可以是任意类型的数据结构.
客户端 ( Client ) :

使用容器并初始化其中各类数据元素,并选择合适的访问者处理容器中的所有数据对象.

 1.2访问者模式实现

我们以超市购物为例,假设超市中的三类商品: 水果,糖果,酒水进行售卖. 我们可以忽略每种商品的计价方法,因为最终结账时由收银员统一集中处理,在商品类中添加计价方法是不合理的设计.我们先来定义糖果类和酒类、水果类.

/*** 抽象商品父类* @author spikeCong* @date 2022/10/18**/
public abstract class Product {private String name;  //商品名private LocalDate producedDate;  // 生产日期private double price;  //单品价格public Product(String name, LocalDate producedDate, double price) {this.name = name;this.producedDate = producedDate;this.price = price;}public String getName() {return name;}public void setName(String name) {this.name = name;}public LocalDate getProducedDate() {return producedDate;}public void setProducedDate(LocalDate producedDate) {this.producedDate = producedDate;}public double getPrice() {return price;}public void setPrice(double price) {this.price = price;}
}/*** 糖果类* @author spikeCong* @date 2022/10/18**/
public class Candy extends Product{public Candy(String name, LocalDate producedDate, double price) {super(name, producedDate, price);}
}/*** 酒水类* @author spikeCong* @date 2022/10/18**/
public class Wine extends Product{public Wine(String name, LocalDate producedDate, double price) {super(name, producedDate, price);}
}/*** 水果类* @author spikeCong* @date 2022/10/18**/
public class Fruit extends Product{//重量private float weight;public Fruit(String name, LocalDate producedDate, double price, float weight) {super(name, producedDate, price);this.weight = weight;}public float getWeight() {return weight;}public void setWeight(float weight) {this.weight = weight;}
}

访问者接口

收银员就类似于访问者,访问用户选择的商品,我们假设根据生产日期进行打折,过期商品不能够出售. 注意这种计价策略不适用于酒类,作为收银员要对不同商品应用不同的计价方法.

/*** 访问者接口-根据入参不同调用对应的重载方法* @author spikeCong* @date 2022/10/18**/
public interface Visitor {public void visit(Candy candy);  //糖果重载方法public void visit(Wine wine);  //酒类重载方法public void visit(Fruit fruit);  //水果重载方法
}

具体访问者

创建计价业务类,对三类商品进行折扣计价,折扣计价访问者的三个重载方法分别实现了3类商品的计价方法,体现了visit() 方法的多态性.

/*** 折扣计价访问者类* @author spikeCong* @date 2022/10/18**/
public class DiscountVisitor implements Visitor {private LocalDate billDate;public DiscountVisitor(LocalDate billDate) {this.billDate = billDate;System.out.println("结算日期: " + billDate);}@Overridepublic void visit(Candy candy) {System.out.println("糖果: " + candy.getName());//获取产品生产天数long days = billDate.toEpochDay() - candy.getProducedDate().toEpochDay();if(days > 180){System.out.println("超过半年的糖果,请勿食用!");}else{double rate = 0.9;double discountPrice = candy.getPrice() * rate;System.out.println("糖果打折后的价格"+NumberFormat.getCurrencyInstance().format(discountPrice));}}@Overridepublic void visit(Wine wine) {System.out.println("酒类: " + wine.getName()+",无折扣价格!");System.out.println("原价: "+NumberFormat.getCurrencyInstance().format(wine.getPrice()));}@Overridepublic void visit(Fruit fruit) {System.out.println("水果: " + fruit.getName());//获取产品生产天数long days = billDate.toEpochDay() - fruit.getProducedDate().toEpochDay();double rate = 0;if(days > 7){System.out.println("超过七天的水果,请勿食用!");}else if(days > 3){rate = 0.5;}else{rate = 1;}double discountPrice = fruit.getPrice() * fruit.getWeight() * rate;System.out.println("水果价格: "+NumberFormat.getCurrencyInstance().format(discountPrice));}public static void main(String[] args) {LocalDate billDate = LocalDate.now();Candy candy = new Candy("徐福记",LocalDate.of(2022,10,1),10.0);System.out.println("糖果: " + candy.getName());double rate = 0.0;long days = billDate.toEpochDay() - candy.getProducedDate().toEpochDay();System.out.println(days);if(days > 180){System.out.println("超过半年的糖果,请勿食用!");}else{rate = 0.9;double discountPrice = candy.getPrice() * rate;System.out.println("打折后的价格"+NumberFormat.getCurrencyInstance().format(discountPrice));}}
}

客户端

public class Client {public static void main(String[] args) {//德芙巧克力,生产日期2002-5-1 ,原价 10元Candy candy = new Candy("德芙巧克力",LocalDate.of(2022,5,1),10.0);Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,11));visitor.visit(candy);}
}

上面的代码虽然可以完成当前的需求,但是设想一下这样一个场景: 由于访问者的重载方法只能对当个的具体商品进行计价,如果顾客选择了多件商品来结账时,就可能会引起重载方法的派发问题(到底该由谁来计算的问题).

首先我们定义一个接待访问者的类 Acceptable,其中定义了一个accept(Visitor visitor)方法, 只要是visitor的子类都可以接收.

/*** 接待者接口(抽象元素角色)* @author spikeCong* @date 2022/10/18**/
public interface Acceptable {//接收所有的Visitor访问者的子类实现类public void accept(Visitor visitor);
}/*** 糖果类* @author spikeCong* @date 2022/10/18**/
public class Candy extends Product implements Acceptable{public Candy(String name, LocalDate producedDate, double price) {super(name, producedDate, price);}@Overridepublic void accept(Visitor visitor) {//accept实现方法中调用访问者并将自己 "this" 传回。this是一个明确的身份,不存在任何泛型visitor.visit(this);}
}//酒水与水果类同样实现Acceptable接口,重写accept方法

测试

public class Client {public static void main(String[] args) {//        //德芙巧克力,生产日期2002-5-1 ,原价 10元Candy candy = new Candy("德芙巧克力",LocalDate.of(2022,5,1),10.0);Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,11));visitor.visit(candy);//模拟添加多个商品的操作List<Acceptable> products = Arrays.asList(new Candy("金丝猴奶糖",LocalDate.of(2022,6,10),10.00),new Wine("衡水老白干",LocalDate.of(2020,6,10),100.00),new Fruit("草莓",LocalDate.of(2022,10,12),50.00,1));Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,17));for (Acceptable product : products) {product.accept(visitor);}}
}

代码编写到此出,就可以应对计价方式或者业务逻辑的变化了,访问者模式成功地将数据资源(需实现接待者接口)与数据算法 (需实现访问者接口)分离开来。重载方法的使用让多样化的算法自成体系,多态化的访问者接口保证了系统算法的可扩展性,数据则保持相对固定,最终形成⼀个算法类对应⼀套数据。

1.3访问者模式总结

1.3.1访问者模式优点:

扩展性好

在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
复用性好

通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
分离无关行为

通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

1.3.2访问者模式缺点:

对象结构变化很困难

在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
违反了依赖倒置原则

访问者模式依赖了具体类,而没有依赖抽象类。

1.3.3使用场景

当对象的数据结构相对稳定,而操作却经常变化的时候。 比如,上面例子中路由器本身的内部构造(也就是数据结构)不会怎么变化,但是在不同操作系统下的操作可能会经常变化,比如,发送数据、接收数据等。
需要将数据结构与不常用的操作进行分离的时候。 比如,扫描文件内容这个动作通常不是文件常用的操作,但是对于文件夹和文件来说,和数据结构本身没有太大关系(树形结构的遍历操作),扫描是一个额外的动作,如果给每个文件都添加一个扫描操作会太过于重复,这时采用访问者模式是非常合适的,能够很好分离文件自身的遍历操作和外部的扫描操作。
需要在运行时动态决定使用哪些对象和方法的时候。 比如,对于监控系统来说,很多时候需要监控运行时的程序状态,但大多数时候又无法预知对象编译时的状态和参数,这时使用访问者模式就可以动态增加监控行为。

2.备忘录模式

备忘录模式提供了一种对象状态的撤销实现机制,当系统中某一个对象需要恢复到某一历史状态时可以使用备忘录模式进行设计.

很多软件都提供了撤销(Undo)操作,如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 浏览器 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。

备忘录模式(memento pattern)定义: 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态.

2.1备忘录模式原理

2.1.1备忘录模式的主要角色:

发起人(Originator)角色:

状态需要被记录的元对象类, 记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
备忘录(Memento)角色:

负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
看护人(Caretaker)角色:

对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

2.2.备忘录模式实现

下面我们再来看看 UML 对应的代码实现。首先,我们创建原始对象 Originator,对象中有四个属性,分别是 state 用于显示当前对象状态,id、name、phone 用来模拟业务属性,并添加 get、set 方法、create() 方法用于创建备份对象,restore(memento) 用于恢复对象状态。

/*** 发起人类* @author spikeCong* @date 2022/10/19**/
public class Originator {private String state = "原始对象";private String id;private String name;private String phone;public Originator() {}//创建备忘录对象public Memento create(){return new Memento(id,name,phone);}//恢复对象状态public void restore(Memento m){this.state = m.getState();this.id = m.getId();this.name = m.getName();this.phone = m.getPhone();}public String getState() {return state;}public void setState(String state) {this.state = state;}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}@Overridepublic String toString() {return "Originator{" +"state='" + state + '\'' +", id='" + id + '\'' +", name='" + name + '\'' +", phone='" + phone + '\'' +'}';}
}/*** 备忘录对象*     访问权限为: 默认,也就是同包下可见(保证只有发起者类可以访问备忘录类)* @author spikeCong* @date 2022/10/19**/
class Memento {private String state = "从备份对象恢复为原始对象";private String id;private String name;private String phone;public Memento() {}public Memento(String id, String name, String phone) {this.id = id;this.name = name;this.phone = phone;}//get、set、toString......
}/*** 负责人类-保存备忘录对象* @author spikeCong* @date 2022/10/19**/
public class Caretaker {private Memento memento;public Memento getMemento() {return memento;}public void setMemento(Memento memento) {this.memento = memento;}
}public class Client {public static void main(String[] args) {//创建发起人对象Originator originator = new Originator();originator.setId("1");originator.setName("spike");originator.setPhone("13512341234");System.out.println("=============" + originator);//创建负责人对象,并保存备忘录对象Caretaker caretaker = new Caretaker();caretaker.setMemento(originator.create());//修改originator.setName("update");System.out.println("=============" + originator);//从负责人对象中获取备忘录对象,实现撤销originator.restore(caretaker.getMemento());System.out.println("=============" + originator);}
}

 2.3 备忘录模式应用实例

设计一个收集水果和获取金钱数的掷骰子游戏,游戏规则如下

1. 游戏玩家通过扔骰子来决定下一个状态
2. 当点数为1,玩家金钱增加
3. 当点数为2,玩家金钱减少
4. 当点数为6,玩家会得到水果
5. 当钱消耗到一定程度,就恢复到初始状态

Memento类: 表示玩家的状态

/*** Memento 表示状态* @author spikeCong* @date 2022/10/19**/
public class Memento {int money;    //所持金钱ArrayList fruits; //获得的水果//构造函数Memento(int money) {this.money = money;this.fruits = new ArrayList();}//获取当前玩家所有的金钱int getMoney() {return money;}//获取当前玩家所有的水果List getFruits() {return (List)fruits.clone();}//添加水果void addFruit(String fruit){fruits.add(fruit);}
}

Player玩家类,只要玩家的金币还够,就会一直进行游戏,在该类中会设置一个createMemento方法,其作用是保存当前玩家状态.还会包含一个restore撤销方法,相当于复活操作.

package com.mashibing.memento.example02;import java.util.ArrayList;
import java.util.List;
import java.util.Random;/*** @author spikeCong* @date 2022/10/19**/
public class Player {private int money;      //所持金钱private List<String> fruits = new ArrayList();  //获得的水果private Random random = new Random();   //随机数对象private static String[] fruitsName={    //表示水果种类的数组"苹果","葡萄","香蕉","橘子"};//构造方法public Player(int money) {this.money = money;}//获取当前所持有的金钱public int getMoney() {return money;}//获取一个水果public String getFruit() {String prefix = "";if (random.nextBoolean()) {prefix = "好吃的";}//从数组中获取水果String f = fruitsName[random.nextInt(fruitsName.length)];return prefix + f;}//掷骰子游戏public void yacht(){int dice = random.nextInt(6) + 1;   //掷骰子if(dice == 1){money += 100;System.out.println("所持有的金钱增加了..");}else if(dice == 2){money /= 2;System.out.println("所持有的金钱减半..");}else if(dice == 6){   //获取水果String fruit = getFruit();System.out.println("获得了水果: " + fruit);fruits.add(fruit);}else{//骰子结果为3、4、5System.out.println("无效数字,继续投掷");}}//拍摄快照public Memento createMemento(){Memento memento = new Memento(money);for (String fruit : fruits) {if(fruit.startsWith("好吃的")){memento.addFruit(fruit);}}return memento;}//撤销方法public void restore(Memento memento){this.money = memento.money;this.fruits = memento.getFruits();}@Overridepublic String toString() {return "Player{" +"money=" + money +", fruits=" + fruits +'}';}
}

测试: 由于引入了备忘录模式,可以保存某个时间点的玩家状态,这样就可以对玩家进行复活操作.

public class MainApp {public static void main(String[] args) throws InterruptedException {Player player = new Player(100);        //最初所持的金钱数Memento memento = player.createMemento();       //保存最初状态for (int i = 0; i < 100; i++) {//显示扔骰子的次数System.out.println("=====" + i);//显示当前状态System.out.println("当前状态: " + player);//开启游戏player.yacht();System.out.println("所持有的金钱为: " + player.getMoney() + " 元");//决定如何操作Mementoif(player.getMoney() > memento.getMoney()){System.out.println("赚到金币,保存当前状态,继续游戏!");memento = player.createMemento();}else if(player.getMoney() < memento.getMoney() / 2){System.out.println("所持金币不多了,将游戏恢复到初始状态!");player.restore(memento);}Thread.sleep(1000);System.out.println("");}}
}

2.4备忘录模式总结

2.4.1备忘录模式的优点

1. 提供了一种状态恢复的实现机制,使得用户可以方便的回到一个特定的历史步骤,当新的状态无效或者存在问题的时候,可以使用暂时存储起来的备忘录将状态复原.
2. 备忘录实现了对信息的封装,一个备忘录对象是一种发起者对象状态的表示,不会被其他代码所改动.备忘录保存了发起者的状态,采用集合来存储备忘录可以实现多次撤销的操作

2.4.2备忘录模式的缺点

资源消耗过大,如果需要保存的发起者类的成员变量比较多, 就不可避免的需要占用大量的存储空间,每保存一次对象的状态,都需要消耗一定系统资源

2.4.3备忘录模式使用场景

1. 需要保存一个对象在某一时刻的状态时,可以使用备忘录模式.
2. 不希望外界直接访问对象内部状态时.

3.命令模式

命令模式(command pattern)的定义: 命令模式将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其他对象(将不 同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等 (附加控制)功能。

命令模式的核心是将指令信息封装成一个对象,并将此对象作为参数发送给接收方去执行,达到使命令的请求与执行方解耦,双方只通过传递各种命令对象来完成任务.

在实际的开发中,如果你用到的编程语言并不支持用函数作为参数来传递,那么就可以借助命令模式将函数封装为对象来使用。

我们知道,C 语 言支持函数指针,我们可以把函数当作变量传递来传递去。但是,在大部分编程语言中,函 数没法儿作为参数传递给其他函数,也没法儿赋值给变量。借助命令模式,我们可以将函数 封装成对象。具体来说就是,设计一个包含这个函数的类,实例化一个对象传来传去,这样 就可以实现把函数像对象一样使用。

3.1命令模式原理

命令模式包含以下主要角色:

抽象命令类(Command)角色:

定义命令的接口,声明执行的方法。
具体命令(Concrete  Command)角色:

具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
实现者/接收者(Receiver)角色:

接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
调用者/请求者(Invoker)角色:

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

 3.2命令模式实现

模拟酒店后厨的出餐流程,来对命令模式进行一个演示,命令模式角色的角色与案例中角色的对应关系如下:

服务员: 即调用者角色,由她来发起命令.
厨师: 接收者,真正执行命令的对象.
订单: 命令中包含订单

/*** 订单类* @author spikeCong* @date 2022/10/19**/
public class Order {private int diningTable;  //餐桌号码//存储菜名与份数private Map<String,Integer> foodMenu = new HashMap<>();public int getDiningTable() {return diningTable;}public void setDiningTable(int diningTable) {this.diningTable = diningTable;}public Map<String, Integer> getFoodMenu() {return foodMenu;}public void setFoodDic(Map<String, Integer> foodMenu) {this.foodMenu = foodMenu;}
}/*** 厨师类 -> Receiver角色* @author spikeCong* @date 2022/10/19**/
public class Chef {public void makeFood(int num,String foodName){System.out.println(num + "份," + foodName);}
}/*** 抽象命令接口* @author spikeCong* @date 2022/10/19**/
public interface Command {void execute(); //只需要定义一个统一的执行方法
}/*** 具体命令* @author spikeCong* @date 2022/10/19**/
public class OrderCommand implements Command {//持有接收者对象private Chef receiver;private Order order;public OrderCommand(Chef receiver, Order order) {this.receiver = receiver;this.order = order;}@Overridepublic void execute() {System.out.println(order.getDiningTable() + "桌的订单: ");Set<String> keys = order.getFoodMenu().keySet();for (String key : keys) {receiver.makeFood(order.getFoodMenu().get(key),key);}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(order.getDiningTable() + "桌的菜已上齐.");}
}/*** 服务员-> Invoker调用者* @author spikeCong* @date 2022/10/19**/
public class Waiter {//可以持有很多的命令对象private ArrayList<Command> commands;public Waiter() {commands = new ArrayList();}public Waiter(ArrayList<Command> commands) {this.commands = commands;}public void setCommands(Command command) {commands.add(command);}//发出命令 ,指挥厨师工作public void orderUp(){System.out.println("服务员: 叮咚,有新的订单,请厨师开始制作......");for (Command cmd : commands) {if(cmd != null){cmd.execute();}}}
}public class Client {public static void main(String[] args) {Order order1 = new Order();order1.setDiningTable(1);order1.getFoodMenu().put("鲍鱼炒饭",1);order1.getFoodMenu().put("茅台迎宾",1);Order order2 = new Order();order2.setDiningTable(3);order2.getFoodMenu().put("海参炒面",1);order2.getFoodMenu().put("五粮液",1);//创建接收者Chef receiver = new Chef();//将订单和接收者封装成命令对象OrderCommand cmd1 = new OrderCommand(receiver,order1);OrderCommand cmd2 = new OrderCommand(receiver,order2);//创建调用者Waiter invoke = new Waiter();invoke.setCommands(cmd1);invoke.setCommands(cmd2);//将订单发送到后厨invoke.orderUp();}
}

3.3命令模式总结

3.3.1命令模式优点:

降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。

3.3.2命令模式缺点:

使用命令模式可能会导致某些系统有过多的具体命令类。
系统结构更加复杂。

3.3.3使用场景

系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
系统需要在不同的时间指定请求、将请求排队和执行请求。
系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。

4.解释器模式

解释器模式使用频率不算高,通常用来描述如何构建一个简单“语言”的语法解释器。它只在一些非常特定的领域被用到,比如编译器、规则引擎、正则表达式、SQL 解析等。不过,了解它的实现原理同样很重要,能帮助你思考如何通过更简洁的规则来表示复杂的逻辑。

解释器模式(Interpreter pattern)的原始定义是:用于定义语言的语法规则表示,并提供解释器来处理句子中的语法。

我们通过一个例子给大家解释一下解释器模式

假设我们设计一个软件用来进行加减计算。我们第一想法就是使用工具类,提供对应的加法和减法的工具方法。

//用于两个整数相加的方法
public static int add(int a , int  b){return a + b;
}//用于三个整数相加的方法
public static int add(int a , int  b,int c){return a + b + c;
}public static int add(Integer ... arr){int sum = 0;for(Integer num : arr){sum += num;}return sum;
}+ - 

上面的形式比较单一、有限,如果形式变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无限种组合方式。比如: 5-3+2-1, 10-5+20....

文法规则和抽象语法树

解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子.

在上面提到的加法/减法解释器中,每一个输入表达式(比如:2+3+4-5) 都包含了3个语言单位,可以使用下面的文法规则定义:

文法是用于描述语言的语法结构的形式规则。

expression ::= value | plus | minus 
plus ::= expression ‘+’ expression   
minus ::= expression ‘-’ expression  
value ::= integer

注意: 这里的符号“::=”表示“定义为”的意思,竖线 | 表示或,左右的其中一个,引号内为字符本身,引号外为语法。 

上面规则描述为 :

表达式可以是一个值,也可以是plus或者minus运算,而plus和minus又是由表达式结合运算符构成,值的类型为整型数。

抽象语法树:

在解释器模式中还可以通过一种称为抽象语法树的图形方式来直观的表示语言的构成,每一棵抽象语法树对应一个语言实例,例如加法/减法表达式语言中的语句 " 1+ 2 + 3 - 4 + 1" 可以通过下面的抽象语法树表示

4.1解释器模式原理

 4.1.1解释器模式包含以下主要角色。

抽象表达式(Abstract Expression)角色:

定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
终结符表达式(Terminal  Expression)角色:

是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。上例中的value 是终结符表达式.
非终结符表达式(Nonterminal Expression)角色:

也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。上例中的 plus , minus 都是非终结符表达式
环境(Context)角色:

通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
客户端(Client):

主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。

4.2解释器模式实现

为了更好的给大家解释一下解释器模式, 我们来定义了一个进行加减乘除计算的“语言”,语法规则如下:

运算符只包含加、减、乘、除,并且没有优先级的概念;
表达式中,先书写数字,后书写运算符,空格隔开;

我们举个例子来解释一下上面的语法规则:

比如 `“ 9 5 7 3 - + * ”`这样一个表达式,我们按照上面的语法规则来处理,取出数字 `“9、5”` 和 `“-”` 运算符,计算得到 4,于是表达式就变成了 `“ 4 7 3 + * ”`。然后,我们再取出 `“4 7”`和“ + ”运算符,计算得到 11,表达式就变成了“ 11 3 * ”。最后,我们取出“ 11 3”和“ * ”运算符,最终得到的结果就是 33。

代码示例:

用户按照上 面的规则书写表达式,传递给 interpret() 函数,就可以得到最终的计算结果。

/*** 表达式解释器类* @author spikeCong* @date 2022/10/20**/
public class ExpressionInterpreter {//Deque双向队列,可以从队列的两端增加或者删除元素private Deque<Long>  numbers = new LinkedList<>();//接收表达式进行解析public long interpret(String expression){String[] elements = expression.split(" ");int length = elements.length;//获取表达式中的数字for (int i = 0; i < (length+1)/2; ++i) {//在 Deque的尾部添加元素numbers.addLast(Long.parseLong(elements[i]));}//获取表达式中的符号for (int i = (length+1)/2; i < length; ++i) {String operator = elements[i];//符号必须是 + - * / 否则抛出异常boolean isValid = "+".equals(operator) || "-".equals(operator)|| "*".equals(operator) || "/".equals(operator);if (!isValid) {throw new RuntimeException("Expression is invalid: " + expression);}//pollFirst()方法, 移除Deque中的第一个元素,并返回被移除的值long number1 = numbers.pollFirst(); //数字long number2 = numbers.pollFirst();long result = 0;  //运算结果//对number1和number2进行运算if (operator.equals("+")) {result = number1 + number2;} else if (operator.equals("-")) {result = number1 - number2;} else if (operator.equals("*")) {result = number1 * number2;} else if (operator.equals("/")) {result = number1 / number2;}//将运算结果添加到集合头部numbers.addFirst(result);}//运算完成numbers中应该保存着运算结果,否则是无效表达式if (numbers.size() != 1) {throw new RuntimeException("Expression is invalid: " + expression);}//移除Deque的第一个元素,并返回return numbers.pop();}
}

 4.3代码重构

代码的所有的解析逻辑都耦合在一个函数中,这样显然是不合适的。这 个时候,我们就要考虑拆分代码,将解析逻辑拆分到独立的小类中, 前面定义的语法规则有两类表达式,一类是数字,一类是运算符,运算符又包括加减乘除。 利用解释器模式,我们把解析的工作拆分到以下五个类:plu,sub,mul,div

NumExpression
PluExpression
SubExpression
MulExpression
DivExpression

/*** 表达式接口* @author spikeCong* @date 2022/10/20**/
public interface Expression {long interpret();
}/*** 数字表达式* @author spikeCong* @date 2022/10/20**/
public class NumExpression implements Expression {private long number;public NumExpression(long number) {this.number = number;}public NumExpression(String number) {this.number = Long.parseLong(number);}@Overridepublic long interpret() {return this.number;}
}/*** 加法运算* @author spikeCong* @date 2022/10/20**/
public class PluExpression implements Expression{private Expression exp1;private Expression exp2;public PluExpression(Expression exp1, Expression exp2) {this.exp1 = exp1;this.exp2 = exp2;}@Overridepublic long interpret() {return exp1.interpret() + exp2.interpret();}
}/*** 减法运算* @author spikeCong* @date 2022/10/20**/
public class SubExpression implements Expression {private Expression exp1;private Expression exp2;public SubExpression(Expression exp1, Expression exp2) {this.exp1 = exp1;this.exp2 = exp2;}@Overridepublic long interpret() {return exp1.interpret() - exp2.interpret();}
}/*** 乘法运算* @author spikeCong* @date 2022/10/20**/
public class MulExpression implements Expression {private Expression exp1;private Expression exp2;public MulExpression(Expression exp1, Expression exp2) {this.exp1 = exp1;this.exp2 = exp2;}@Overridepublic long interpret() {return exp1.interpret() * exp2.interpret();}
}/*** 除法* @author spikeCong* @date 2022/10/20**/
public class DivExpression implements Expression {private Expression exp1;private Expression exp2;public DivExpression(Expression exp1, Expression exp2) {this.exp1 = exp1;this.exp2 = exp2;}@Overridepublic long interpret() {return exp1.interpret() / exp2.interpret();}
}//测试
public class Test01 {public static void main(String[] args) {ExpressionInterpreter e = new ExpressionInterpreter();long result = e.interpret("6 2 3 2 4 / - + *");System.out.println(result);}
}

4.4解释器模式总结

4.4.1解释器优点

易于改变和扩展文法

  因为在解释器模式中使用类来表示语言的文法规则的,因此就可以通过继承等机制改变或者扩展文法.每一个文法规则都可以表示为一个类,因此我们可以快速的实现一个迷你的语言
实现文法比较容易

  在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂
增加新的解释表达式比较方便

  如果用户需要增加新的解释表达式,只需要对应增加一个新的表达式类就可以了.原有的表达式类不需要修改,符合开闭原则

4.4.2解释器缺点

对于复杂文法难以维护

  在解释器中一条规则至少要定义一个类,因此一个语言中如果有太多的文法规则,就会使类的个数急剧增加,当值系统的维护难以管理.
执行效率低

  在解释器模式中大量的使用了循环和递归调用,所有复杂的句子执行起来,整个过程也是非常的繁琐

4.4.3使用场景

当语言的文法比较简单,并且执行效率不是关键问题.
当问题重复出现,且可以用一种简单的语言来进行表达
当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象的语法树的时候

5.中介者模式

提到中介模式,有一个比较经典的例子就是航空管制。 为了让飞机在飞行的时候互不干扰,每架飞机都需要知道其他飞机每时每刻的位置,这就需要时刻跟其他飞机通信。飞机通信形成的通信网络就会无比复杂。这个时候,我们通过引 入“塔台”这样一个中介,让每架飞机只跟塔台来通信,发送自己的位置给塔台,由塔台来 负责每架飞机的航线调度。这样就大大简化了通信网络。

中介模式(mediator pattern)的定义:

定义一个单独的(中介)对象,来封装一组对象之间的交互,将这组对象之间的交互委派给予中介对象交互,来避免对象之间的交互.

中介者对象就是用于处理对象与对象之间的直接交互,封装了多个对象之间的交互细节。中介模式的设计跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系从多对多的网状关系转换为一对多的星状关系.原来一个对象要跟N个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低代码的复杂度,提高代码的可读性和可维护性.

5.1中介者模式原理

5.1.1中介者模式包含以下主要角色:

抽象中介者(Mediator)角色:

它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
具体中介者(ConcreteMediator)角色:

实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
抽象同事类(Colleague)角色:

定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
具体同事类(Concrete Colleague)角色:

是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

 5.2中介者模式实现

/*** 抽象中介者* @author spikeCong* @date 2022/10/20**/
public interface Mediator {void apply(String key);
}/*** 具体中介者* @author spikeCong* @date 2022/10/20**/
public class MediatorImpl implements Mediator {@Overridepublic void apply(String key) {System.out.println("最终中介者执行操作,key为: " + key);}
}/*** 抽象同事类* @author spikeCong* @date 2022/10/20**/
public abstract class Colleague {private Mediator mediator;public Colleague(Mediator mediator) {this.mediator = mediator;}public Mediator getMediator() {return mediator;}public abstract void exec(String key);
}/*** 具体同事类* @author spikeCong* @date 2022/10/20**/
public class ConcreteColleagueA extends Colleague {public ConcreteColleagueA(Mediator mediator) {super(mediator);}@Overridepublic void exec(String key) {System.out.println("====在组件A中,通过中介者执行!");getMediator().apply(key);}
}public class ConcreteColleagueB extends Colleague {public ConcreteColleagueB(Mediator mediator) {super(mediator);}@Overridepublic void exec(String key) {System.out.println("====在组件B中,通过中介者执行!");getMediator().apply(key);}
}public class Client {public static void main(String[] args) {//创建中介者MediatorImpl mediator = new MediatorImpl();//创建同事对象Colleague c1 = new ConcreteColleagueA(mediator);c1.exec("key-A");Colleague c2 = new ConcreteColleagueB(mediator);c2.exec("key-B");}
}====在组件A中,通过中介者执行!
最终中介者执行操作,key为: key-A
====在组件B中,通过中介者执行!
最终中介者执行操作,key为: key-B

5.3中介者模式应用实例

【例】租房

现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。

/*** 抽象中介者* @author spikeCong* @date 2022/10/20**/
public abstract class Mediator {//申明一个联络方法public abstract void contact(String message,Person person);
}/*** 抽象同事类* @author spikeCong* @date 2022/10/20**/
public abstract class Person {protected String name;protected Mediator mediator;public Person(String name, Mediator mediator) {this.name = name;this.mediator = mediator;}
}/*** 中介机构* @author spikeCong* @date 2022/10/20**/
public class MediatorStructure extends Mediator {//中介要知晓房主和租房者的信息private HouseOwner houseOwner;private Tenant tenant;public HouseOwner getHouseOwner() {return houseOwner;}public void setHouseOwner(HouseOwner houseOwner) {this.houseOwner = houseOwner;}public Tenant getTenant() {return tenant;}public void setTenant(Tenant tenant) {this.tenant = tenant;}@Overridepublic void contact(String message, Person person) {if(person == houseOwner){  //如果是房主,则租房者获得信息tenant.getMessage(message);}else { //如果是租房者则获取房主信息houseOwner.getMessage(message);}}
}/*** 具体同事类-房屋拥有者* @author spikeCong* @date 2022/10/20**/
public class HouseOwner extends Person{public HouseOwner(String name, Mediator mediator) {super(name, mediator);}//与中介者联系public void contact(String message){mediator.contact(message,this);}//获取信息public void getMessage(String message){System.out.println("房主" + name + "获取到的信息: " + message);}
}/*** 具体同事类-承租人* @author spikeCong* @date 2022/10/20**/
public class Tenant extends Person{public Tenant(String name, Mediator mediator) {super(name, mediator);}//与中介者联系public void contact(String message){mediator.contact(message,this);}//获取信息public void getMessage(String message){System.out.println("租房者" + name + "获取到的信息: " + message);}
}public class Client {public static void main(String[] args) {//一个房主 一个租房者 一个中介机构MediatorStructure mediator = new MediatorStructure();//房主和租房者只需要知道中介机构即可HouseOwner houseOwner = new HouseOwner("路飞", mediator);Tenant tenant = new Tenant("娜美", mediator);//中介收集房租和租房者信息mediator.setHouseOwner(houseOwner);mediator.setTenant(tenant);tenant.contact("需要一个两室一厅的房子,一家人住");houseOwner.contact("出租一套两室一厅带电梯,月租5000");}
}

5.4中介者模式总结

5.4.1中介者模式的优点

中介者模式简化了对象之间的交互,他用中介者和同事的一对多代替了原来的同事之间的多对多的交互,一对多关系更好理解 易于维护和扩展,将原本难以理解的网状结构转换成习相对简单的星型结构.
可以将各个同事就对象进行解耦.中介者有利于各个同事之间的松耦合,可以独立的改变或者复用每一个同事或者中介者,增加新的中介者类和新的同事类都比较方便,更符合开闭原则
可以减少子类生成,中介者将原本分布与多个对象的行为集中在了一起,改变这些行为只需要生成新的中介者的子类即可,使得同事类可以被重用,无需直接对同事类进行扩展.

5.4.2中介者模式的缺点

在具体中介者类中包含了大量同事之间的交互细节,可能会导致中介者类变得非常的复杂,使得系统不好维护.

5.4.3中介者模式使用场景

系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解.
一个对象由于引用了其他的很多对象并且直接和这些对象进行通信,导致难以复用该对象.
想要通过一个中间类来封装多个类中的行为,而又不想生成太多的子类,此时可以通过引用中介者类来实现,在中介者类中定义对象的交互的公共行为,如果需要改变行为则可以在增加新的中介类.

相关文章:

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

1.访问者模式 访问者模式在实际开发中使用的非常少,因为它比较难以实现并且应用该模式肯能会导致代码的可读性变差,可维护性变差,在没有特别必要的情况下,不建议使用访问者模式. 访问者模式(Visitor Pattern) 的原始定义是&#xff1a; 允许在运行时将一个或多个操作应用于一…...

华为交换机常用命令

一、查看命令 1、查看交换机信息 display version 查看交换机软件版本display clock 查看交换机时钟2、查看交换机配置 display saved-configuration 显示系统保存配置display current-configuration 显示系统当前配置 3、查看当前对象信息 display this …...

【Linux】信号-上

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;【LeetCode】winter vacation training 目录 &#x1f449;&#x1f3fb;信号的概念与产生jobs命令普通信号和实…...

uniapp 开发App 权限授权 js-sdk

从官网的插件市场下载的&#xff1a; 直接上代码&#xff1a; /*** 本模块封装了Android、iOS的应用权限判断、打开应用权限设置界面、以及位置系统服务是否开启*/var isIos // #ifdef APP-PLUS isIos (plus.os.name "iOS") // #endif// 判断推送权限是否开启 fu…...

【01】判断素数/质数(C语言)

目录 &#xff08;1&#xff09;素数特点&#xff1a;只能被1和本身整除 &#xff08;2&#xff09;代码如下&#xff1a; &#xff08;3&#xff09;运行结果如下 ​编辑 &#xff08;4&#xff09;函数引申 &#xff08;1&#xff09;素数特点&#xff1a;只能被1和本身…...

特征工程:特征提取和降维-上

目录 一、前言 二、正文 Ⅰ.主成分分析 Ⅱ.核主成分分析 三、结语 一、前言 前面介绍的特征选择方法获得的特征&#xff0c;是从原始数据中抽取出来的&#xff0c;并没有对数据进行变换。而特征提取和降维&#xff0c;则是对原始数据的特征进行相应的数据变换&#xff0c;并…...

前端JavaScript篇之强类型语言和弱类型语言的区别和对比

目录 强类型语言和弱类型语言的区别和对比总结 强类型语言和弱类型语言的区别和对比 强类型语言和弱类型语言是编程语言的两种不同类型系统&#xff0c;它们处理变量类型的方式有所不同。 强类型语言&#xff1a; 强类型语言要求在使用变量之前必须明确声明其类型&#xff0c;…...

[红日靶机渗透] ATKCK红队评估实战靶场三

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【ATK&CK红队评估实战靶场】 【VulnHub靶场复现】【面试分析】 &#x1f…...

网课:N皇后问题——牛客(题解和疑问)

题目描述 给出一个nnn\times nnn的国际象棋棋盘&#xff0c;你需要在棋盘中摆放nnn个皇后&#xff0c;使得任意两个皇后之间不能互相攻击。具体来说&#xff0c;不能存在两个皇后位于同一行、同一列&#xff0c;或者同一对角线。请问共有多少种摆放方式满足条件。 输入描述: …...

[大厂实践] Netflix容器平台内核panic可观察性实践

在某些情况下&#xff0c;K8S节点和Pod会因为出错自动消失&#xff0c;很难追溯原因&#xff0c;其中一种情况就是发生了内核panic。本文介绍了Netflix容器平台针对内核panic所做的可观测性增强&#xff0c;使得发生内核panic的时候&#xff0c;能够导出信息&#xff0c;帮助排…...

2024/2/8

数据类型与作用域练习 1、选择题 1.1、以下选项中,不能作为合法常量的是 ___b_______ A&#xff09;1.234e04 B&#xff09;1.234e0.4 C&#xff09;1.234e4 D&#xff09;1.234e0 1.2、以下定义变量并初始化错误的是______d_______。 A) char c1 ‘H’ &am…...

Verilog刷题笔记23

题目: Suppose you’re building a circuit to process scancodes from a PS/2 keyboard for a game. Given the last two bytes of scancodes received, you need to indicate whether one of the arrow keys on the keyboard have been pressed. This involves a fairly simp…...

C#验证字符串的长度,用正则表达式 vs 字符数组长度或字符串的长度

目录 一、使用的方法 1.使用正则表达式 2.通过计算字符串的长度验证 二、实例 1.源码 2.生成效果 一、使用的方法 1.使用正则表达式 使用正则表达式可以判断和限制用户输入的字符串长度。 比如验证用户密码不得少于8为&#xff0c;匹配的正则表达式"^.{8,}$"…...

opencv C++ dnn模块调用yolov5以及Intel RealSense D435深度相机联合使用进行目标检测

一、代码 #include <opencv2/opencv.hpp> #include <opencv2/dnn/dnn.hpp> #include <librealsense2/rs.hpp> // Include RealSense Cross Platform APIusing namespace cv; using namespace dnn; using namespace std; using namespace rs2;// 类名数组&am…...

2024牛客寒假算法基础集训营1(视频讲解全部题目)

2024牛客寒假算法基础集训营1&#xff08;题目全解&#xff09; ABCDEFGHIJKLM 2024牛客寒假算法基础集训营1&#xff08;视频讲解全部题目&#xff09; A #include<bits/stdc.h> #define endl \n #define deb(x) cout << #x << " " << …...

第三百一十三回

文章目录 1. 概念介绍2. 实现方法2.1 obscureText属性2.2 decoration属性 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何实现倒计时功能"相关的内容&#xff0c;本章回中将介绍如何实现密码输入框.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍…...

倒计时61天

M-智乃的36倍数(normal version)_2024牛客寒假算法基础集训营3 (nowcoder.com) //非ac代码,超时了,54.17/100#include<bits/stdc.h> using namespace std; const int N1e55; const int inf0x3f3f3f3f; #define int long long int n; string s1[N]; void solve() {cin>…...

npm后Truffle找不到命令(ubantu20系统)

Truffle找不到命令 方法1方法2 方法1 # 编辑.profile vim ~/.profile # 在.profile末尾把nodejs的解压路径添加到$PATH环境变量中 PATH"$HOME/bin:$HOME/.local/bin:路径:$PATH" source 文件方法2 #ls -l 在nodejs的bin目录下查看truffle链接的脚本文件 truffle -&…...

嵌入式学习第三篇——51单片机

目录 1&#xff0c;嵌入式系统 1&#xff0c;嵌入式系统的定义 2&#xff0c;单片机的定义 2&#xff0c;51单片机 1&#xff0c;开发环境 2&#xff0c;开发板使用的基本思路 1&#xff0c;查看原理图&#xff0c;查看芯片手册 2&#xff0c;获得调用硬件的管…...

RabbitMQ详解

RabbitMQ 1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得到响应&a…...

CGAL::2D Arrangements-4

4. Free函数 Arrangement_on_surface_2类模板是用曲线切分二维的面。因为它的接口设计是最简化的&#xff0c;这意味着它的成员函数很少执行几何操作。本章将解释怎么利用这些Free function来达到Arrangement操作。执行这些操作通常需要优秀的几何算法&#xff0c;而且有时会对…...

终端命令提示符:如何查看我们电脑端口是否被占用和处理方式

文章目录 端口信息查看1、Windows:2、Linux/macOS: 使用 netstat使用 lsof 端口信息查看 在不同的操作系统中&#xff0c;查看端口是否被占用的指令有所不同。以下是一些常见的指令&#xff1a; 1、Windows: 使用命令行工具 netstat 来查看端口占用情况。 电脑键盘按住 win…...

elasticsearch重置密码操作

安装es的时候需要测试这个url&#xff1a;http://127.0.0.1:9200/ 出现弹窗让我输入账号和密码。我第一次登录&#xff0c;没有设置过账号和密码&#xff0c; 解决方法是&#xff1a;在es的bin目录下打开cmd窗口&#xff0c;敲命令&#xff1a;.\elasticsearch-reset-password…...

从零开始手写mmo游戏从框架到爆炸(零)—— 导航

从今天开始我们尝试从零开始写一个mmo的游戏。主要技术还是netty。参考了网上很多的大神的框架&#xff0c;本来希望基于ioGame或者vert.x等来直接写功能的&#xff0c;觉得从零开始更有意义&#xff0c;而且咱们也不需要太NB的底层功能&#xff0c;够用就行。 下面是导航&…...

机器学习7-K-近邻算法(K-NN)

K-Nearest Neighbors&#xff08;K-近邻算法&#xff0c;简称KNN&#xff09;是一种基本的监督学习算法&#xff0c;用于解决分类和回归问题。KNN的核心思想是基于距离度量&#xff0c;在特征空间中找到最近的K个样本&#xff0c;然后使用它们的标签进行决策。以下是KNN的基本概…...

相机图像质量研究(7)常见问题总结:光学结构对成像的影响--镜片固化

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…...

猫头虎分享已解决Bug || Go Error: cannot convert int to string

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …...

前端bug手册

JavaScript错误&#xff1a;常见的JavaScript错误包括语法错误、未定义的变量、类型错误等。这些错误可能导致页面无法正常运行或功能无法正常使用。样式问题&#xff1a;前端开发中常见的样式问题包括布局错乱、元素位置不正确、样式覆盖等。这些问题可能导致页面显示不正常或…...

Elasticsearch中Document Routing特性

Document Routing在Elasticsearch中是一种高级特性&#xff0c;它允许用户在索引文档时指定一个路由值。通过这种方式&#xff0c;可以确保具有相同路由值的所有文档都存储在同一个分片中。这对于提高查询效率特别有用&#xff0c;因为它允许查询只针对包含相关文档的特定分片&…...

【Git版本控制 03】远程操作

目录 一、克隆远程仓库 二、推送远程仓库 三、拉取远程仓库 四、忽略特殊文件 五、命令配置别名 一、克隆远程仓库 Git是分布式版本控制系统&#xff0c;同⼀个Git仓库&#xff0c;可以分布到不同的机器上。怎么分布呢&#xff1f; 找⼀台电脑充当服务器的⻆⾊&#xff…...

【Git】Windows下通过Docker安装GitLab

私有仓库 前言基本思路拉取镜像创建挂载目录创建容器容器启动成功登录仓库设置中文更改密码人员审核配置邮箱 前言 由于某云存在人数限制&#xff0c;这个其实很好理解&#xff0c;毕竟使用的是云服务器&#xff0c;人家也是要交钱的。把代码完全放在别人的服务器上面&#xf…...

flutter 操作mysql

引入模块 dependencies: flutter: sdk: flutter mysql1: ^0.20.0 mysql helper 的代码 import dart:async; import package:mysql1/mysql1.dart; class MySqlHelper { static const _host localhost; static const _port 3333; static const _user user; static c…...

c++阶梯之类与对象(中)< 续集 >

前文&#xff1a; c阶梯之类与对象&#xff08;上&#xff09;-CSDN博客 c阶梯之类与对象&#xff08;中&#xff09;-CSDN博客 前言&#xff1a; 在上文中&#xff0c;我们学习了类的六个默认成员函数之构造&#xff0c;析构与拷贝构造函数&#xff0c;接下来我们来看看剩下…...

GitLag所有操作-汇总

1、MAC Git环境设置 跳转 Git通过Token拉代码&#xff1a; 跳转 Git基础操作&#xff1a;拉、put、删 跳转 Git回滚操作&#xff1a; 跳转 Git回滚操作-复杂 跳转 对于Commit但是还没有push的代码&#xff0c;如果回滚&#xff1a; 跳转...

JSch - 配置SFTP服务器SSH免密登录

文章目录 1. 什么是SFTP2. 什么是Jsch以及它的作用3. Linux中配置SSH密钥登录4. sftp服务器认证机制5. publickey和password两种方式登录sftp的API调用6. 代码可以如下改造&#xff1a; 需求&#xff1a;做一个通过ssh免密登录的需求&#xff0c;是基于原先密码登录sftp服务器的…...

RISC-V指令格式

RISC-V指令格式 1 RISC-V指令集命名规范2 RISC-V指令集组成2.1 基础整数指令集2.2 扩展指令集 3 RISC-V指令格式3.1 指令表述3.2 指令格式 本文属于《 RISC-V指令集基础系列教程》之一&#xff0c;欢迎查看其它文章。 1 RISC-V指令集命名规范 前面提到过RV32I&#xff0c;这是…...

Linux 文件比较工具

在Linux系统中&#xff0c;文件比较是一种常见的任务&#xff0c;用于比较两个文件之间的差异。文件比较可以帮助我们找出两个文件的不同之处&#xff0c;或者确定它们是否完全相同。在Linux中&#xff0c;有多种方法可以进行文件比较。 1. diff 在Linux中&#xff0c;diff命…...

【GAMES101】Lecture 17 材质

目录 材质 漫反射 镜面反射 折射-Snell’s Law Fresnel Reflection / Term&#xff08;菲涅耳项&#xff09; 微表面模型 各向同性与各向异性 BRDF的性质 测量BRDF 材质 渲染方程中的BRDF描述了物体是如何与光线作用的&#xff0c;而物体的材质决定了它看起来是怎么样…...

数模.matlab画图

一、mesh函数 上图是平常用到的方式 例题&#xff1a; 上图的meshgrid函数相当于上上图的前三个指令&#xff08;temp&#xff0c;x,y&#xff09; mash函数&#xff1a; mashc函数&#xff1a; mashz函数&#xff1a; 上图subplot函数的作用是将下标为index的图片放到对应的x&…...

[word] word表格表头怎么取消重复出现? #媒体#笔记#职场发展

word表格表头怎么取消重复出现&#xff1f; word表格表头怎么取消重复出现&#xff1f;在Word中的表格如果过长的话&#xff0c;会跨行显示在另一页&#xff0c;如果想要在其它页面上也显示表头&#xff0c;更直观的查看数据。难道要一个个复制表头吗&#xff1f;当然不是&…...

vue项目开发vscode配置

配置代码片段 步骤如下&#xff1a; 文件->首选项->配置用户代码片段新增全局代码片段起全局代码片段文件名“xxx.code-snippets” 这里以配置vue2初始代码片段为例&#xff0c;配置具体代码片段 {"name": "vue-sph","version": "…...

BUUCTF-Real-[Tomcat]CVE-2017-12615

目录 漏洞描述 一、漏洞编号&#xff1a;CVE-2017-12615 二、漏洞复现 get flag 漏洞描述 CVE-2017-12615&#xff1a;远程代码执行漏洞 影响范围&#xff1a;Apache Tomcat 7.0.0 - 7.0.79 (windows环境) 当 Tomcat 运行在 Windows 操作系统时&#xff0c;且启用了 HTTP P…...

Qt应用软件【协议篇】http协议get、post示例

文章目录 QT Http的APIHTTP GET 请求示例HTTP POST 请求示例伪装chrome浏览器get请求QT Http的API QNetworkAccessManager 作用:管理所有的网络请求,是发送请求和接收响应的中心点。主要功能: 发送HTTP请求(GET, POST, PUT, DELETE等)。处理网络请求的异步回调。管理网络…...

如何选择Centos的替代者

开篇废话&#xff1a;许久许久没有更新博客了。寒假回到故土&#xff0c;大雪虽然没有封路&#xff0c;还是增加了不出门的决心&#xff0c;虽然年岁已高&#xff0c;但是不学习还是不踏实&#xff0c;那就借着写作再继续前行。 背景&#xff1a;信息化部门&#xff0c;掌管着…...

【Java数据结构】ArrayList和LinkedList的遍历

一&#xff1a;ArrayList的遍历 import java.util.ArrayList; import java.util.Iterator; import java.util.List;/*** ArrayList的遍历*/ public class Test {public static void main(String[] args) {List<Integer> list new ArrayList<>();list.add(5);list…...

springboot163美食推荐商城的设计与实现

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四章 获取资料方式 **项…...

[机器学习]K-means——聚类算法

一.K-means算法概念 二.代码实现 # 0. 引入依赖 import numpy as np import matplotlib.pyplot as plt # 画图依赖 from sklearn.datasets import make_blobs # 从sklearn中直接生成聚类数据# 1. 数据加载 # 生成&#xff08;n_samples&#xff1a;样本点&#xff0c;centers&…...

并发编程 java锁机制

1、什么是锁&#xff0c;为什么需要锁&#xff1f; 并发环境下&#xff0c;会存在多个线程对同一个资源进行争抢的情况&#xff0c;假设线程A对资源正在进行修改&#xff0c;此时线程B又对同一资源进行了修改&#xff0c;就会导致数据不一致的问题。为了解决这个问题&#xff…...

Onerugged三防平板厂家丨三年质保承诺丨三防平板PAD

行业领先产品——Onerugged三防平板。凭借着十年的经验&#xff0c;我们深知终端设备在各个行业中的重要性&#xff0c;因此致力于为用户提供高可靠性的解决方案。 Onerugged三防平板以其卓越的性能和全方位的保护功能&#xff0c;在市场上脱颖而出。首先&#xff0c;它拥有IP…...

Android 系统启动流程

一.Android系统启动流程基本框架 Android系统完整的启动过程&#xff0c;从系统层次角度可分为 Linux 系统层、Android 系统服务层、Zygote进程模型三个阶段&#xff1b;从开机到启动 Home Launcher 完成具体的任务细节可分为七个步骤&#xff0c;下面就从具体的细节来解读 And…...