设计模式⑧ :管理状态

文章目录

  • 一、前言
  • 二、Observer 模式
    • 1. 介绍
    • 2. 应用
    • 3. 总结
  • 三、Memento 模式
    • 1. 介绍
    • 2. 应用
    • 3. 总结
  • 四、State 模式
    • 1. 介绍
    • 2. 应用
    • 3. 总结
  • 参考文章

一、前言

有时候不想动脑子,就懒得看源码又不像浪费时间所以会看看书,但是又记不住,所以决定开始写"抄书"系列。本系列大部分内容都是来源于《 图解设计模式》(【日】结城浩 著)。该系列文章可随意转载。

二、Observer 模式

Observer 模式 : 发送状态变化通知

1. 介绍

Observer 即 “观察者”,在 Observer 模式中,当观察对象的状态发生变化时,会通知给观察者。 Observer 模式适用于根据对象状态进行相应处理的场景。


Observer 模式 中出场的角色:

  • Subject (观察对象):Subject 角色表示观察对象。Subject 角色定义了注册观察者和删除观察者的方法。此外,他还声明了 “获取现在的状态” 的方法。
  • ConcreteSubject (具体的观察对象):ConcreteSubject 角色表示具体的被观察的对象。当自身状态发生变化后,他会通知所有已经注册的 Observer 角色。
  • Observer(观察者):Observer 角色负责接收来着Subject 角色的状态变化的通知。为此,它声明了 update 方法。
  • ConcreteObserver(具体的观察者):ConcreteObserver 角色负责具体的 Observer。当他的 update 方法被调用后,会去获取要观察的对象的最新状态。

类图如下:
在这里插入图片描述

Demo 如下

// 监听者接口
public interface Observer {void update(NumberGenerator generator);
}// 监听者实现类
public class NumberObserver implements Observer{@Overridepublic void update(NumberGenerator generator) {System.out.println("number = " + generator.getNumber());}
}// 数字生成器
public class NumberGenerator {private List<Observer> observers = Lists.newArrayList();@Getterprivate int number;/*** 执行某个操作*/public void execute() {Random random = new Random(System.currentTimeMillis());for (int i = 0; i < 20; i++) {number = random.nextInt();// 通知监听者动作执行notifyObservers();}}// 添加监听者public void addObserver(Observer observer) {observers.add(observer);}/*** 通知所有监听者*/public void notifyObservers() {observers.forEach(observer -> observer.update(this));}
}public class ObserverDemoMain {public static void main(String[] args) {final NumberGenerator generator = new NumberGenerator();NumberObserver observer = new NumberObserver();// 添加监听者generator.addObserver(observer);// 执行某个操作generator.execute();}
}

2. 应用

  • 诸如 MQ 中的消费者也是订阅了相关的 Queue,当相关的 Queue有消息进入后,会推送给消费者得知,消费者可以处理对应消息。

  • Spring 中向事务管理器TransactionSynchronizationManager注册事务某个阶段执行的监听事件,如下:

        @Transactional(rollbackFor = Exception.class)@Overridepublic String testTransactionSynchronization() {// step1 : 在 sys_role 表中插入一条 role_id = 1 的记录addRole(1);// step2 : 在 sys_role 表中插入一条 role_id = 2 的记录addRole(2);// 向事务同步管理器注册一个 TransactionSynchronization 匿名实现类,作用是当前事务提交后,执行 addPermission 方法。TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {// 当前事务提交后触发该方法// step3 : 在 sys_rermission 表中插入一条 permission_id = id 的记录。@Overridepublic void afterCommit() {addPermission(1);}});System.out.println("end");return "";}
  • Spring 中典型的监听器模式:即我们通过自定义 ApplicationEvent 同时通过实现 ApplicationListener 来监听指定事件的发生。如下,在 SpringBoot 启动时会调用如下几个方法,发送容器初始化、容器初始化结束等相关消息,监听对应事件的监听器会监听到该消息并可以做特定的处理。
    在这里插入图片描述



个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑)

  • 项目 A 中,对于订单状态的刷新可以有两个触发点:用户点击刷新或接收到 MQ 推送消息后刷新。因此这里做了一个统一,无论是 MQ 还是用户点击刷新,都会触发一个 RefreshEvent 事件,而存在一个 RefreshEvent 事件的监听器来监听该事件。当事件触发时会调用监听器的指定方法来完成刷新操作。

3. 总结

扩展思路

  • 可替换性:使用设计模式的目的之一就是一使类成为可复用的组件。在 Observer 模式中,有连接的仅仅是 Subject 角色 和 Observer 角色,而 ConcreteSubject 和 ConcreteObserver 角色并没有直接联系,可以随意替换。
  • 当 Observer 的行为会对 Subject 产生影响时 :当 Subject 触发 update 操作时会调用 Observer 方法,但是需要注意如果 Observer 角色中如果再调用 Subject 的 update 方法可能就会造成循环调用。
  • 传递更新信息的方式:有时我们可以在 Subject 触发时将需要更新的信息传递过去,这样就省去了 Observer 自己获取数据的麻烦,不过如此Subject 就知道了 Observer 中要进行的操作了,在复杂程序中这样的操作会使程序缺少灵活性,所以需要根据程序的复杂度来考虑在触发时数据传递的方式。
  • 从“观察”变为“通知”:Observer 本意是 观察者,但实际上 Observer 角色并非主动的去观察,而是被动的接受来自 Subject 角色的通知。因此 Observer 模式也被称为 Publish-Subscribe (发布-订阅)模式。

相关设计模式:

  • Mediator 模式:在 Mediator 模式中,有时会使用 Observer 模式来实现 Mediator 角色与 Colleague 角色之间的通信。就“发送状态变化通知”这一点而已,Mediator 模式与 Observer 模式是类似的。不过这两种模式中通知的目的和视角不同。

    在Mediator 模式中,虽然也会发送通知,不过那只是为了对 Colleague 角色进行仲裁而已,而在 Observer 模式中,将 Subject 角色的状态变化通知给 Observer 角色的目的则主要是为了使 Subject 角色与 Observer 角色同步。


三、Memento 模式

Memento 模式 :保存对象状态

1. 介绍

我们在文本编辑器编辑时,如果不小心误操作,可以通过撤回(undo)功能将内容恢复到之前的状态。而面对对象编程的方式实现撤销功能时,需要事先保存实例的相关状态信息,然后再撤销时,还需要根据所保存的信息将实例恢复到原先的状态。

想要恢复实例,需要一个可以自由访问实例内部结构的权限,但是如果稍微不注意又可能会将依赖于实例内部结构的代码分散到编写在程序的各个地方,导致程序变的难以维护。这种情况叫做“破坏了封装性”。

而通过引入表示实例状态的角色,可以在保存和回复实例时有效地方志对象的封装性遭到破坏,即 Memento 模式。


Memento 模式 中出场的角色

  • Originator(生成者): Originator角色会在保存自己的最新状态时生成 Memento角色。当把以前保存的 Memento 角色传递给 Originator 角色时,他会将自己恢复至生成该Memento角色时的状态。

  • Memento(纪念品):Memento 角色会将 Originator 角色的内部信息整合在一起,在Memento角色中虽然保存了 Originator 角色的信息,但是他不会向外部公开这些信息。Memento 角色有以下两种接口:

    • wide interface(宽接口):Memento 角色提供的宽接口是指所有用于获取回复对象状态信息的方法的集合。由于宽接口会暴露所有 Memento 角色的内部信息,因此能够使用宽接口的只有 Originator 角色。
    • narrow interface (窄接口) :Memento角色为外部的 Caretaker 角色提供了窄接口。可以通过窄接口获取的 Memento 角色的内部信息非常有限,因此可以有效防止信息泄漏。

    通过对外提供这两种接口,可由有效防止对象的封装性被破坏。

  • Caretaker(负责人):当Caretaker 角色想要保存当前的 Originator 角色状态时,会通过 Originator 角色。Originator角色在接收到通知后会生成 Memento 角色的实力并将其返回给 Caretaker 角色。由于以后可能会用 Memento 实例来将 Originator 恢复至原来的状态,所以 Caretaker 角色会一直保存Memento 实例。

    不过由于 Caretaker 角色只能使用Memento 角色的两种接口中的窄接口,也就是说它无法访问 Memento 角色内部的所有信息。它只是将 Originator 角色生成的Memento角色当做一个黑盒保存起来。

    虽然 Originator 角色和 Memento 角色之前是强关联,但是Caretaker 角色和 Memento 角色之前是弱关联关系。Memento 角色对 Caretaker 角色因此了自身的内部信息。


类图如下:
在这里插入图片描述


Demo 如下: 游戏自动进行,通过掷骰子来决定下一个状态,点数为1时增加金钱,2时减少,6时得到水果,而如果金钱减少则通过 Memento 对象回滚到上一轮的状态。

public class Memento {/*** 当前持有金钱*/private final int money;/*** 当前持有水果*/private final List<String> fruits = Lists.newArrayList();public Memento(int money) {this.money = money;}/*** 获取当前金钱* @return*/public int getMoney() {return money;}/*** 添加水果* @param fruit*/public void addFruit(String fruit){fruits.add(fruit);}/*** 获取持有水果* @return*/public List<String> getFruits(){return fruits;}
}public class Gamer {/*** 当前持有金钱*/private int money;/*** 当前持有水鬼*/private List<String> fruits = Lists.newArrayList();/*** 水果列表*/private String[] fruitsName = new String[]{"苹果", "橘子", "香蕉"};/*** 随机数*/private Random random = new Random(System.currentTimeMillis());public Gamer(int money) {this.money = money;}public int getMoney() {return money;}public List<String> getFruits() {return fruits;}public void bet() {final 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) {final String fruit = getFruit();fruits.add(fruit);System.out.println("获得了水果 " + fruit);} else {System.out.println("什么都没发生");}}/*** 创建快照** @return*/public Memento createMemento() {Memento memento = new Memento(money);fruits.stream().filter(f -> f.startsWith("好吃的")).forEach(memento::addFruit);return memento;}/*** 快照还原* @param memento*/public void restoreMemento(Memento memento) {this.money = memento.getMoney();this.fruits = memento.getFruits();}/*** 获取奖励水果** @return*/private String getFruit() {String prefix = random.nextBoolean() ? "好吃的" : "";return prefix + fruitsName[random.nextInt(fruitsName.length)];}
}public class MementoDemoMain {public static void main(String[] args) {Gamer gamer = new Gamer(100);Memento memento = gamer.createMemento();for (int i = 0; i < 100; i++) {System.out.println("i = " + i + ": 当前金钱 = " + gamer.getMoney() + "; 当前水果 = " + gamer.getFruits());gamer.bet();System.out.println("当前持有金钱 money = " + gamer.getMoney());if (gamer.getMoney() > memento.getMoney()) {System.out.println("当前持有金钱增加, 继续游戏");memento = gamer.createMemento();} else {System.out.println("当前持有金钱减少, 恢复快照状态");gamer.restoreMemento(memento);}}}
}

输出如下:
在这里插入图片描述


2. 应用

  • 想到了数据库中的事务回滚,效果表现与 Memento 模式有些类似,表象为事务执行失败后将数据回滚到事务开启前的快照版本,但是这并不是简单的回滚快照,而是通过 Undo 版本链(如有需要可参阅 https://blog.csdn.net/filling_l/article/details/112854716)来实现的,因此不能直接带入 State 模式,只是突然想到了而已
  • 除此之外又突然想到了 MySQL 中的 SavePoint 的内容(与本文无关,想到了就记录下):即 MySQL 中的事务中存在一个 SavePoint 的概念:即一个事务中可以创建一个或多个 SavePoint,当设置SavePoint 时 当事务只会回滚到 SavePoint,而 SavePoint 之前的内容不会回滚。如一个事务中,先增加两条记录,然后设置一个 SavePoint, 再删除两条记录,此时出现了某种异常,事务进行回滚时则只会回滚删除语句的操作,插入语句则不会回滚。


个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑)

  • 在项目A中对单证会进行多次修改并保存,而相关的操作人员经常会不小心修改某个参数并保存,而在后续过程中会被其他人员发现数据错误,而修改后操作人员并不承认自己曾经修改过数据。因此可以通过 Memento 模式记录人员的每次修改内容,以便追溯修改人员以及数据回滚。

3. 总结

扩展思路

  • 需要多少个 Memento :根据实际业务的不同,可以保存多个 Memento 实例,就可以是实现保存的各个时间点的对象的状态。
  • Memento 的有效期限是多久:如果将 Memento 永远保存在文件中,就可能会出现有效期限的问题。因为可能在后续的某个时间点将 Memento 保存在文件中之后升级了程序版本,导致保存的 Memento 与当前程勋内部不匹配。
  • 划分 Caretaker 角色 和 Originator 角色的意义:如果要实现撤销功能,为什么不直接在 Originator 角色中实现?这是因为 Caretaker 角色的职责是觉得何时拍摄快照、何时撤销以及保存 Memento 角色。另一方面 Originator 角色的职责是生成 Memento 角色和使用接收到的 Memento 角色来恢复自己的状态。以上是 Caretaker 角色和 Originator 角色的职责分担,有了这样的职责分担,当我们需要对应一下需求变更时,就可以完全不修改 Originator 角色。
    • 变更为可以多次撤销。
    • 变更为不仅可以撤销,还可以将现在的状态保存在文件中。

相关设计模式

  • Command 模式:在使用 Command 模式处理命令是,可以使用 Memento 模式实现撤销功能。
  • Protype 模式:在 Memento 模式中,为了能够实现快照和撤销功能,保存了对象当前的状态。保存的信息只是在恢复状态时所需要的那部分信息。而在 Protype 模式中,会生成一个与当前实例完全相同的另外一个实例。这两个实例的内容完全一样。
  • State 模式:在 Memento 模式中,是用实例来表示状态,而在 State 模式中,则是用类表示状态。

一时的小想法,仅仅个人理解,无需在意 :

  • 在实际保存快照的过程中,要考虑到保存时效的问题,时效过长则会占用空间并且可能会跟不上版本。另外还需要考虑到存储的方式,如果快照内容非常大,就不适合存到数据库中,因为快照一般都不需要搜索,所以可以将内容写入到文件中,数据库中保存文件地址即可。

四、State 模式

State 模式 :用类表示状态

1. 介绍

在面向对象编程中,是用类表示对象的。而在 State 模式中,我们可以用类来表示状态。


State 模式 中出场的角色

  • State (状态): State 角色表示状态,定义了根据不同状态进行不同处理的接口。该接口是那些处理内容依赖于状态的方法的集合。
  • ConcreteState(具体状态):ConcreteState角色表示各个具体的状态,它实现了State接口。
  • Context(状况、前后关系、上下文):Context 角色持有当前状态的 ConcreteState 角色。此外它还定义了供外部调用者调用 State 模式的接口。

类图如下
在这里插入图片描述


Demo如下 : 一个金库系统,分为白天和晚上两个状态,每小时使用一次金库,同步一次警局。

// 状态接口
public interface State {/*** 设置时间* @param context* @param hour*/void doClock(Context context, int hour);/*** 使用金库* @param context*/void doUse(Context context);/*** 正常通话* @param context*/void doPhone(Context context);
}// 白天状态
public class DayState implements State {private static final DayState INSTANCE = new DayState();private DayState() {}/*** 单例模式获取实例** @return*/public static DayState getInstance() {return INSTANCE;}@Overridepublic void doClock(Context context, int hour) {if (hour < 9 || hour >= 17) {context.changeState(NightState.getInstance());}}@Overridepublic void doUse(Context context) {System.out.println("白天使用金库");}@Overridepublic void doPhone(Context context) {System.out.println("白天正常通话");}@Overridepublic String toString() {return "白天";}
}// 晚上状态
public class NightState implements State {private static final NightState INSTANCE = new NightState();private NightState() {}/*** 单例模式获取实例** @return*/public static NightState getInstance() {return INSTANCE;}@Overridepublic void doClock(Context context, int hour) {if (hour >= 9 && hour < 17) {context.changeState(DayState.getInstance());}}@Overridepublic void doUse(Context context) {System.out.println("晚上使用金库");}@Overridepublic void doPhone(Context context) {System.out.println("晚上通话录音");}@Overridepublic String toString() {return "晚上";}
}// 上下文
public class Context {private State state;public Context(State state) {this.state = state;}/*** 设置时间** @param hour*/public void setClock(int hour) {System.out.println("现在时间是 : " + hour + ":00");state.doClock(this, hour);}/*** 改变状态** @param state*/public void changeState(State state) {System.out.println("状态变更 : " + this.state + " -> " + state);this.state = state;}/*** 使用金库** @param msg*/public void doUse(String msg) {this.state.doUse(this);}/*** 联系报警中心** @param msg*/public void callSecurityCenter(String msg) {this.state.doPhone(this);}
}// Main 方法
public class StateDemoMain {public static void main(String[] args) throws InterruptedException {// 执行状态,每小时使用一次金库, 拨打警局电话确认安全Context context = new Context(DayState.getInstance());for (int i = 0; i < 24; i++) {context.setClock(i);Thread.sleep(1000);context.doUse("");context.callSecurityCenter(String.valueOf(i));}}
}

输出如下:
在这里插入图片描述


2. 应用

  • 没想到


个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑)

  • 在 Memento 模式中举例的项目A中,实际上对于单证的处理不仅仅时修改,还存在多个环节状态的流转,而每个环节状态并非单独字段的判断,因此可以通过 State 模式来管控各个状态之间的流转,并且结合 Memento 模式,可以对每个环节状态做一个快照备份。

    @Data
    public class BillInfo {/*** 表头信息*/private String head;/*** 表体信息*/private List<String> details;@Overridepublic String toString() {return "BillInfo{" +"head='" + head + '\'' +", details=" + details +'}';}
    }public abstract class BillState {protected static final MementoContext MEMENTO_CONTEXT = new MementoContext();/*** 编辑操作** @param billInfo*/public void edit(BillInfo billInfo) {throw new RuntimeException("not support this operation");}/*** 发送操作** @param billInfo*/public void send(BillInfo billInfo) {throw new RuntimeException("not support this operation");}/*** 删除操作** @param billInfo*/public void delete(BillInfo billInfo) {throw new RuntimeException("not support this operation");}
    }@Data
    public class BillMemento {/*** 表头信息*/private String head;/*** 表体信息*/private List<String> details;/*** 创建时间*/private Date createTime = new Date();/*** 快照版本号*/private String version;/*** 上一个版本号*/private String lastVersion;}public class DeleteBillState extends BillState {@Overridepublic void delete(BillInfo billInfo) {System.out.println("单证状态已被删除");}
    }public class SendBillState extends BillState {@Overridepublic void send(BillInfo billInfo) {System.out.println("单证已发送");}@Overridepublic void delete(BillInfo billInfo) {System.out.println("单证状态无法删除");}
    }public class EditBillState extends BillState {@Overridepublic void edit(BillInfo billInfo) {System.out.println("单证被编辑");}@Overridepublic void delete(BillInfo billInfo) {System.out.println("单证被删除");}
    }public class BillStateProxy {private static final BillState EDIT_STATE = new EditBillState();private static final BillState DELETE_STATE = new DeleteBillState();private static final BillState SEND_STATE = new SendBillState();/*** 当前状态*/private BillState billState = EDIT_STATE;/*** 编辑操作** @param billInfo*/public String edit(BillInfo billInfo, String version) {billState.edit(billInfo);return MEMENTO_CONTEXT.createMemento(version, billInfo);}/*** 发送操作** @param billInfo*/public void send(BillInfo billInfo) {billState = SEND_STATE;billState.send(billInfo);}/*** 删除操作** @param billInfo*/public void delete(BillInfo billInfo) {billState = DELETE_STATE;billState.delete(billInfo);}/*** 快照恢复* @param billInfo* @param version*/public void restoreBillInfo(BillInfo billInfo, String version){MEMENTO_CONTEXT.restoreMemento(billInfo, version);}
    }public class MementoContext {/*** 快照存储*/private Map<String, BillMemento> store = Maps.newHashMap();/*** 创建快照** @param billInfo* @return*/public String createMemento(String laseVersion, BillInfo billInfo) {BillMemento memento = new BillMemento();memento.setHead(billInfo.getHead());memento.setDetails(Lists.newArrayList(billInfo.getDetails()));memento.setVersion(UUID.randomUUID().toString());memento.setLastVersion(laseVersion);storeMemento(memento);return memento.getVersion();}/*** 快照回滚** @param billInfo* @param version*/public void restoreMemento(BillInfo billInfo, String version) {final BillMemento memento = getMemento(version);billInfo.setHead(memento.getHead());billInfo.setDetails(memento.getDetails());}/*** 存储快照, 可以重写该方法以改变快照的存储方式** @param memento*/protected void storeMemento(BillMemento memento) {store.put(memento.getVersion(), memento);}/*** 获取快照** @param version*/protected BillMemento getMemento(String version) {return store.get(version);}}public class DemoMain {public static void main(String[] args) {BillInfo billInfo = new BillInfo();billInfo.setHead("start");billInfo.setDetails(Lists.newArrayList());BillStateProxy billState = new BillStateProxy();// 快照版本记录List<String> versions = Lists.newArrayList();String currVersion = "";// 单证初始编辑状态,编辑状态不允许发送for (int i = 0; i < 15; i++) {billInfo.getDetails().add(String.valueOf(i));currVersion = billState.edit(billInfo, currVersion);versions.add(currVersion);}// 单证发送后变成已发送状态billState.send(billInfo);// 已发送状态单证无法删除billState.delete(billInfo);System.out.println("操作结束后结果 :" + billInfo);// 利用快照回滚单证内容billState.restoreBillInfo(billInfo, versions.get(0));System.out.println("回滚后结果 :" + billInfo);}
    }
    

    输入如下:
    在这里插入图片描述

  • 突然想到 播放器的倍速播放似乎可以使用 State 模式?

3. 总结

扩展思路

  • 分而治之:当遇到庞大且复杂的问题时,我们一般都会将问题分解为多个小问题逐个解决。而在 State 模式中,我们可以用类来表示状态,并且可以为每种具体的都定义一个相应的类,这样问题就被分解了。对于每种状态在对应的实例中实现对应的操作,而无需考虑其他因素,更为重要的是无需通过 if 判断多种情况,从而导致分支过多的情况。
  • 依赖于状态的处理:在 State 接口中声明的所有方法都是“依赖于状态的处理”,都是“状态不同处理也不同”。
  • 谁来管理状态迁移:用类来表示状态,将依赖于状态的处理分散在每个 ConcreteState 角色中,这里需要注意应该是谁来管理状态迁移,除了通过代码配置实现,还可以通过状态迁移表来实现,这种实现即根据 “输入和内部状态” 得到 “输出和下一个状态”。
  • 不会自相矛盾:如果不使用 State 模式,我们需要用使用多个变量的值的集合来表示系统的状态,这时则需要注意不能让变量之间互相矛盾。而在 State 模式中使用类来表示状态,这样只需要一个表示系统状态的变量即可。
  • 易于增加新的状态:在 State模式中增加新的状态很简单,编写一个实现 State 接口的实例即可,但是在 State 模式中增加其他“依赖状态的处理”则很困难,因为我们需要再 State 接口中增加新的方法,并且让所有子类实现它。

相关设计模式

  • Singleton 模式: Singleton 模式常常会出现在 ConcreteState角色中。
  • Flyweight 模式:在表示状态的类中并没有定义任何实例字段,因此有时我们可以使用 Flyweight 模式在多个 Context 角色之间共享 ConcreteState 角色。

参考文章

https://mp.weixin.qq.com/s/fc8AB6MnYtCyp5jWvVgCjw

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

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

相关文章

大势浏览器DasViewer的底图能否改为卫星底图?

支持的。官网3.2.4版本tif格式的影像图可以加进来。 DasViewer是由大势智慧自主研发的免费的实景三维模型浏览器,采用多细节层次模型逐步自适应加载技术,让用户在极低的电脑配置下,也能流畅的加载较大规模实景三维模型,提供方便快捷的数据浏览操作。 #DasViewer##实景三维##三…

写了7年代码,第一次见这么狗血的小Bug!

大家好&#xff0c;我是程序员鱼皮。 孽起 Bug 年年有&#xff0c;今年特别多。前段时间给大家分享过一个 特别坑的小 Bug&#xff0c;结果这两天我个倒霉蛋又遇到一个特别离谱的 Bug&#xff0c;有多离谱&#xff1f;大家可以看看视频&#xff1a;https://www.bilibili.com/vi…

23111 C++ day1

思维导图 提示并输入一个字符串&#xff0c;统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数 要求使用C风格字符串完成 #include <iostream> #include<array>using namespace std;int main() {int a0,A0,num0,space0,other0;array<char…

前端实现转盘抽奖 - 使用 lucky-canvas 插件

目录 需求背景需求实现实现过程图片示意实现代码 页面效果lucky-canvas 插件官方文档 需求背景 要求实现转盘转动抽奖的功能&#xff1a; 只有正确率大于等于 80% 才可以进行抽奖&#xff1b;“谢谢参与”概率为 90%&#xff0c;“恭喜中奖”概率为 10%&#xff1b; 需求实现 实…

综合CRM客户管理系统

技术框架&#xff1a; JAVA MYSQL SSH 功能介绍&#xff1a; 个人工作、信息中心、客户管理、合同订单、财务管理、产品管理、人事管理以及数据回收站等8个模块。另包括权限管理模块用于系统的用户、角色和相关权限&#xff0c;收发邮件功能用于获得客户的详细需求&#xf…

docker 基础手册

文章目录 docker 基础手册docker 容器技术镜像与容器容器与虚拟机docker 引擎docker 架构docker 底层技术docker 二进制安装docker 镜像加速docker 相关链接docker 生态 docker 基础手册 docker 容器技术 开源的容器项目&#xff0c;使用 Go 语言开发原意“码头工人”&#x…

Java基础进阶02-xml

一、XML&#xff08;可拓展标记语言&#xff09; 1.学习网站&#xff1a; https://www.w3schoo1.com.cn 标记语言:通过标签来描述数据的一门语言(标签有时我们也将其称之为元素) 可扩展:标签的名字是可以自定义的 2.作用 用于进行存储数据和传输数据 作为软件的配置文件 …

MySQL 8.3 发布, 它带来哪些新变化?

1月16号 MySQL 官方发布 8.3 创新版 和 8.0.36 长期支持版本 (该版本 没有新增功能&#xff0c;更多是修复bug )&#xff0c;本文基于 官方文档 说一下 8.3 版本带来的变化。 一 增加的特性 1.1 GTID_NEXT 支持增加 TAG 选项。 之前的版本中 GTID_NEXTUUID:number &#xff…

使用IntelliJ IDEA快速搭建springboot 基础模板项目

使用IntelliJ IDEA快速搭建springboot 基础模板项目&#xff01;今天和大家分享一下&#xff0c;如何使用IntelliJ IDEA里面的maven插件&#xff0c;来快速搭建一个简单的Springboot基础项目。 第一步&#xff0c;菜单里面找到&#xff0c;文件-》新建-项目。如图。我们勾选了是…

ChatGPT用来润色论文\生成完整长篇论文\进行AI绘图,到底有多强大!!

​课程安排 学习内容 第一章 2024年AI领域最新技术 1.OpenAI新模型-GPT-5 2.谷歌新模型-Gemini Ultra 3.Meta新模型-LLama3 4.科大讯飞-星火认知 5.百度-文心一言 6.MoonshotAI-Kimi 7.智谱AI-GLM-4 第二章 OpenAI开发者大会后GPT最新技术 1.最新大模型GPT-4 Turbo详细介…

Spring Boot 整合 Camunda 实现工作流

工作流是我们开发企业应用几乎必备的一项功能&#xff0c;工作流引擎发展至今已经有非常多的产品。最近正好在接触Camunda&#xff0c;所以来做个简单的入门整合介绍。如果您也刚好在调研或者刚开始计划接入&#xff0c;希望本文对您有所帮助。如果您是一名Java开发或Spring框架…

【博客搭建记录贴】问题记录:hexo : 无法加载文件 C:\Program Files\nodejs\hexo.ps1,因为在此系统上禁止运行脚本。

1&#xff0c;背景 hexo&#xff08;博客框架&#xff09;安装完毕之后&#xff0c;正准备看看其版本&#xff0c;发现出现下面脚本禁止运行的错误。 PS C:\Users\PC> hexo -v hexo : 无法加载文件 C:\Program Files\nodejs\hexo.ps1&#xff0c;因为在此系统上禁止运行脚…

AMIS的组件学习使用

部分代码片段 {"id": "filterForm","className": " xysd-zbkb-pubquery","labelWidth": 130,"body": [{"type": "grid","className": "xysd-grid-query-input","c…

第12章_集合框架(Collection接口,Iterator接口,List,Set,Map,Collections工具类)

文章目录 第12章_集合框架本章专题与脉络1. 集合框架概述1.1 生活中的容器1.2 数组的特点与弊端1.3 Java集合框架体系1.4 集合的使用场景 2. Collection接口及方法2.1 添加2.2 判断2.3 删除2.4 其它 3. Iterator(迭代器)接口3.1 Iterator接口3.2 迭代器的执行原理3.3 foreach循…

dolphinscheduler节点二次开发需要改动的部分

dolphinscheduler节点二次开发需要改动的部分 前端 在dolphinscheduler-ui/public/images/task-icons/目录下新增两个节点的logo图片&#xff0c;一个为激活状态的一个为非激活状态的&#xff0c;如下。 修改文件dolphinscheduler-ui/src/views/projects/task/constants/task…

实战:加密传输数据解密

前言 下面将分享一些实际的渗透测试经验&#xff0c;帮助你应对在测试中遇到的数据包内容加密的情况。我们将以实战为主&#xff0c;技巧为辅&#xff0c;进入逆向的大门。 技巧 开局先讲一下技巧&#xff0c;掌握好了技巧&#xff0c;方便逆向的时候可以更加快速的找到关键…

HCIE之BGP基础概念(一)

BGP 一、BGP的基本概述二、BGP分类三、BGP的工作原理BGP报文类型&#xff1a;BGP状态机&#xff1a; 四、BGP对等体之间的交互原则解决BGP路由黑洞方法&#xff1a; 五、路由反射器路由反射规则路由反射器下防环联邦 六、BGP属性特点优选协议首选值&#xff08;PrefVal&#xf…

PHP编程实践:实际商品价格数据采集

引言 在电子商务领域&#xff0c;对商品价格进行数据采集和对比是一项常见的需求。本文将介绍如何使用PHP编程语言实现对1688和淘宝商品价格数据的采集和对比&#xff0c;帮助读者了解实际的编程实践过程。 一、数据采集原理 数据采集是指从互联网上获取数据的过程&#xff…

【前端web入门第一天】01 开发环境、HTML基本语法文本标签

文章目录: 1. 准备开发环境 1.1 vs Code基本使用 2.HTML文本标签 2.1 标签语法2.2 HTML基本骨架2.3 标签的关系2.4 注释2.5 标题标签2.6 段落标签2.7 换行与水平线标签2.8 文本格式化标签 1. 准备开发环境 VSCode与谷歌浏览器离线版,安装包评论区自提. VSCode默认安装位置:C…

3、非数值型的分类变量

非数值型的分类变量 有很多非数字的数据,这里介绍如何使用它来进行机器学习。 在本教程中,您将了解什么是分类变量,以及处理此类数据的三种方法。 本课程所需数据集夸克网盘下载链接:https://pan.quark.cn/s/9b4e9a1246b2 提取码:uDzP 文章目录 1、简介2、三种方法的使用1…