设计模式_备忘录模式_Memento

案例引入

游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力),当大战Boss后攻击力和防御力下降,可以从备忘录对象恢复到大战前的状态

传统设计方案

针对每一种角色,设计一个类来存储该角色的状态

【分析】

  • 一个对象,就对应一个保存对象状态的对象, 这样当我们游戏的对象很多时,不利于管理,开销也很大
  • 传统的方式是简单地做备份,new出另外一个对象出来,再把需要备份的数据放到这个新对象,但是这样暴露了对象内部的细节
  • 优化方式:使用备忘录模式

介绍

基本介绍

  • 备忘录模式在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态
  • 可以这样理解备忘录模式: 现实生活中的备忘录是用来记录某些要去做的事情或者是记录已经达成的共同意见的事情,以防忘记。而在软件层面,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作
  • 备忘录模式属于行为型模式

登场角色

在这里插入图片描述

  • Originator(生成者):Originator角色会在保存自己的最新状态时生成Memento角色。当把以前保存的Memento角色传递给Originator角色时,它会将自已恢复至生成该Memento角色时的状态
  • Memento(备忘录):Memento角色会将Originator 角色的内部信息整合在一起。在 Memento 角色中虽然保存了Originator 角色的信息,但它不会向外部公开这些信息(通过对方法的权限字符进行设置)
  • Caretaker(负责人):当Caretaker角色想要保存当前的Originator角色的状态时,会通知Originator角色Originator角色在接收到通知后会生成Memento角色的实例并将其返回给Caretaker角色。由于以后可能会用Memento实例来将Originator恢复至原来的状态,因此Caretaker角色会一直保存 Memento实例在示例程序中。Caretaker角色只能使用Memento角色的两种接口(API)中的窄接口,也就是说它无法访问Memento角色内部的所有信息(比如案例三中Caretaker角色只能获取Memento角色的金钱信息,无法进行创建Memento实例等操作)。它只是将Originator角色生成的Memento角色当作一个黑盒子保存起来。虽然Originator角色Memento角色之间是强关联关系,但Caretaker角色Memento角色之间是弱关联关系。Memento角色Caretaker角色隐藏了自身的内部信息

【划分Caretaker角色Originator角色的意义】

Caretaker角色的职责是决定何时拍摄快照,何时撤销以及保存Memento角色。另一方面,Originator 角色的职责则是生成Memento角色和使用接收到的Memento角色来恢复自己的状态。有了这样的职责分担,当我们需要应对以下的需求变更时,就不用修改Originator角色

  • 变更为可以多次撤销
  • 变更为不仅可以撤销,还可以将现在的状态保存在文件中

【Caretaker角色只能通过窄接口(API)来操作 Memento角色,如果Caretaker角色可以随意地操作Memento角色,会发生什么问题呢】

  • 破坏对象的封装性:Memento角色负责存储Originator角色的状态数据,如果Caretaker角色可以随意地操作Memento角色,那么就破坏了Memento对象和Originator对象的封装性
  • 不安全的状态恢复:如果Caretaker角色可以随意地操作Memento角色,可能会改变Memento对象中存储的状态数据,导致状态恢复产生错误
  • 无法保证数据的完整性:如果Caretaker角色可以随意地操作Memento角色,可能会在不正确的时间点保存状态数据,导致数据的不完整性

案例实现

案例一(基础案例)

类图

在这里插入图片描述

  • Originator:需要被保存状态的对象
  • Memento:备忘录对象,负责记录Originator对象的状态
  • Caretaker:守护者对象,负责保存多个备忘录对象,一般使用集合进行管理,提高管理效率
实现

【Originator】

package com.test.memento.theory;

public class Originator {

/**
 * 角色的状态信息
 */
private String state;

public String getState() {
    return state;
}

public void setState(String state) {
    this.state = state;
}

/**
 * 编写一个方法,可以保存一个状态对象 Memento
 * 因此编写一个方法,返回 Memento
 *
 * @return
 */
public Memento saveStateMemento() {
    return new Memento(state);
}

/**
 * 通过备忘录对象获取原有状态信息,恢复状态
 *
 * @param memento
 */
public void getStateFromMemento(Memento memento) {
    state = memento.getState();
}
}

【Memento】

package com.test.memento.theory;

public class Memento {
/**
 * 用来保存状态信息
 */
private String state;

/**
 * 构造器
 *
 * @param state
 */
public Memento(String state) {
    super();
    this.state = state;
}

/**
 * 获取保存的状态信息
 * @return
 */
public String getState() {
    return state;
}

}

【Caretaker】

package com.test.memento.theory;

import java.util.ArrayList;
import java.util.List;

/**
* 统一管理备忘录对象
*/
public class Caretaker {

/**
 * 在 List 集合中会有很多的备忘录对象
 * 如果想要保存多个Originator的多个状态,可以使用HashMap<Originator的ID,List<Memento>>
 */
private List<Memento> mementoList = new ArrayList<Memento>();

public void add(Memento memento) {
    mementoList.add(memento);
}

/**
 * 获取 Originator 的 第 index 个 备忘录对象(即所保存的状态)
 *
 * @param index
 * @return
 */
public Memento get(int index) {
    return mementoList.get(index);
}
}

【主类】

package com.test.memento.theory;

public class Client {

public static void main(String[] args) {

  Originator originator = new Originator();
  // 备忘录对象管理器
  Caretaker caretaker = new Caretaker();

  originator.setState(" 状态#1 攻击力 100 ");

  //保存了当前的状态
  caretaker.add(originator.saveStateMemento());

  originator.setState(" 状态#2 攻击力 80 ");

  caretaker.add(originator.saveStateMemento());

  originator.setState(" 状态#3 攻击力 50 ");
  caretaker.add(originator.saveStateMemento());

  System.out.println("当前的状态是 =" + originator.getState());

  //希望得到状态 1, 将 originator 恢复到状态1
  originator.getStateFromMemento(caretaker.get(0));
  System.out.println("恢复到状态1 , 当前的状态是 =" + originator.getState());

}

}

【运行】

当前的状态是 = 状态#3 攻击力 50 
恢复到状态1 , 当前的状态是 = 状态#1 攻击力 100 

Process finished with exit code 0
1234

案例二

类图

在这里插入图片描述

实现

【Originator:GameRole】

package com.test.memento.game;

public class GameRole {

private int vit;
private int def;

/**
 * 创建Memento,即根据当前的状态得到Memento
 *
 * @return
 */
public Memento createMemento() {
    return new Memento(vit, def);
}

/**
 * 从备忘录对象,恢复GameRole的状态
 *
 * @param memento
 */
public void recoverGameRoleFromMemento(Memento memento) {
    this.vit = memento.getVit();
    this.def = memento.getDef();
}

/**
 * 显示当前游戏角色的状态
 */
public void display() {
    System.out.println("游戏角色当前的攻击力:" + this.vit + " 防御力: " + this.def);
}

public int getVit() {
    return vit;
}

public void setVit(int vit) {
    this.vit = vit;
}

public int getDef() {
    return def;
}

public void setDef(int def) {
    this.def = def;
}


}

【Memento】

package com.test.memento.game;

public class Memento {

/**
 * 攻击力
 */
private int vit;
/**
 * 防御力
 */
private int def;

public Memento(int vit, int def) {
    this.vit = vit;
    this.def = def;
}

public int getVit() {
    return vit;
}

public void setVit(int vit) {
    this.vit = vit;
}

public int getDef() {
    return def;
}

public void setDef(int def) {
    this.def = def;
}

}

【Caretaker】

package com.test.memento.game;

/**
* 守护者对象, 保存游戏角色的状态
*/
public class Caretaker {

/**
 * 因为大战Boss之前只有一个状态,所以这里保存一次状态,不需要使用集合
 */
private Memento memento;
//对GameRole保存多次状态
//private ArrayList<Memento> mementos;
//对多个GameRole保存多个状态
//private HashMap<String, ArrayList<Memento>> rolesMementos;

public Memento getMemento() {
    return memento;
}

public void setMemento(Memento memento) {
    this.memento = memento;
}

}

【主类】

package com.test.memento.game;

public class Client {

public static void main(String[] args) {
  //创建游戏角色
  GameRole gameRole = new GameRole();
  gameRole.setVit(100);
  gameRole.setDef(100);

  System.out.println("和boss大战前的状态");
  gameRole.display();

  //把当前状态保存caretaker
  Caretaker caretaker = new Caretaker();
  caretaker.setMemento(gameRole.createMemento());

  System.out.println("和boss大战~~~");
  gameRole.setDef(30);
  gameRole.setVit(30);

  gameRole.display();

  System.out.println("大战后,使用备忘录对象恢复到站前");

  gameRole.recoverGameRoleFromMemento(caretaker.getMemento());
  System.out.println("恢复后的状态");
  gameRole.display();
}

}

【运行】

和boss大战前的状态
游戏角色当前的攻击力:100 防御力: 100
和boss大战~~~
游戏角色当前的攻击力:30 防御力: 30
大战后,使用备忘录对象恢复到站前
恢复后的状态
游戏角色当前的攻击力:100 防御力: 100

Process finished with exit code 0

案例三

案例说明
  • 游戏是自动进行的
  • 游戏的主人公通过掷骰子来决定下一个状态
  • 当骰子点数为1的时候,主人公的金钱会增加
  • 当骰子点数为2的时候,主人公的金钱会减少
  • 当骰子点数为6的时候,主人公会得到水果
  • 主人公没有钱时游戏就会结束
类图

在这里插入图片描述

实现

【Originator:Gamer】

package com.test.memento.Sample.game;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

public class Gamer {
/**
 * 所持金钱
 */
private int money;
/**
 * 获得的水果
 */
private List fruits = new ArrayList();
/**
 * 随机数生成器
 */
private Random random = new Random();
/**
 * 表示水果种类的数组
 */
private static String[] fruitsName = {
        "苹果", "葡萄", "香蕉", "橘子",
};

/**
 * 构造函数
 *
 * @param money
 */
public Gamer(int money) {
    this.money = money;
}

/**
 * 获取当前所持金钱
 *
 * @return
 */
public int getMoney() {
    return money;
}

/**
 * 投掷骰子进行游戏
 */
public void bet() {
    // 掷骰子,随机获取一个点数
    int dice = random.nextInt(6) + 1;

    if (dice == 1) {
        //  骰子结果为1…增加所持金钱
        money += 100;
        System.out.println("所持金钱增加了。");
    } else if (dice == 2) {
        // 骰子结果为2…所持金钱减半
        money /= 2;
        System.out.println("所持金钱减半了。");
    } else if (dice == 6) {
        // 骰子结果为6…获得水果
        String f = getFruit();
        System.out.println("获得了水果(" + f + ")。");
        fruits.add(f);
    } else {
        // 骰子结果为3、4、5则什么都不会发生
        System.out.println("什么都没有发生。");
    }
}

/**
 * 拍摄快照
 *
 * @return
 */
public Memento createMemento() {
    Memento m = new Memento(money);
    Iterator it = fruits.iterator();
    while (it.hasNext()) {
        String f = (String) it.next();
        if (f.startsWith("好吃的")) {
            // 只保存好吃的水果
            m.addFruit(f);
        }
    }
    return m;
}

/**
 * 撤销到指定状态
 *
 * @param memento
 */
public void restoreMemento(Memento memento) {
    this.money = memento.money;
    this.fruits = memento.getFruits();
}

/**
 * 用字符串输出主人公的状态
 *
 * @return
 */
public String toString() {
    return "[money = " + money + ", fruits = " + fruits + "]";
}

/**
 * 随机获取一个水果
 *
 * @return
 */
private String getFruit() {
    String prefix = "";
    if (random.nextBoolean()) {
        prefix = "好吃的";
    }
    return prefix + fruitsName[random.nextInt(fruitsName.length)];
}
}

【Memento】

package com.test.memento.Sample.game;

import java.util.ArrayList;
import java.util.List;

public class Memento {
/**
 * 游戏主人公所持金钱
 */
int money;
/**
 * 游戏主人公当前获得的水果
 */
ArrayList fruits;
/**
 * 获取当前所持金钱(narrow interface)
 * narrow interface:Memento角色为外部的 Caretaker 角色提供了“窄接口(API)”。可以通过窄接口(API)获取的Memento角色的内部信息非常有限,因此可以有效地防止信息泄露
 * @return
 */
public int getMoney() {
    return money;
}

/**
 * 构造函数(wide interface),只有相同包的Gamer类才使用该构造方法,因为方法修饰类型是default
 *
 * @param money
 */
Memento(int money) {
    this.money = money;
    this.fruits = new ArrayList();
}

/**
 * 添加水果(wide interface)
 *
 * @param fruit
 */
void addFruit(String fruit) {
    fruits.add(fruit);
}

/**
 * 获取当前所持所有水果(wide interface)
 * wide interface:“宽接口(API)”是指所有用于获取恢复对象状态信息的方法的集合。由于宽接口(API)会暴露所有Memento角色的内部信息,因此能够使用宽接口(API)的只有Originator 角色
 * @return
 */
List getFruits() {
    return (List) fruits.clone();
}
}

【Caretaker】

package com.test.memento.Sample;


import com.test.memento.Sample.game.Gamer;
import com.test.memento.Sample.game.Memento;

public class Main {
public static void main(String[] args) {
    // 最初的所持金钱数为100
    Gamer gamer = new Gamer(100);
    // 保存最初的状态
    Memento memento = gamer.createMemento();
    for (int i = 0; i < 10; i++) {
        // 显示掷骰子的次数
        System.out.println("==== " + i);
        // 显示主人公现在的状态
        System.out.println("当前状态:" + gamer);

        // 进行游戏
        gamer.bet();

        System.out.println("所持金钱为" + gamer.getMoney() + "元。");

        // 决定如何处理Memento
        if (gamer.getMoney() > memento.getMoney()) {
            System.out.println("    (所持金钱增加了许多,因此保存游戏当前的状态)");
            memento = gamer.createMemento();
        } else if (gamer.getMoney() < memento.getMoney() / 2) {
            System.out.println("    (所持金钱减少了许多,因此将游戏恢复至以前的状态)");
            gamer.restoreMemento(memento);
        }

        // 等待一段时间
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        System.out.println("");
    }
}
}

【运行】

==== 0
当前状态:[money = 100, fruits = []]
什么都没有发生。
所持金钱为100元。

==== 1
当前状态:[money = 100, fruits = []]
所持金钱增加了。
所持金钱为200元。
(所持金钱增加了许多,因此保存游戏当前的状态)

==== 2
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 3
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 4
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 5
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 6
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 7
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 8
当前状态:[money = 200, fruits = []]
所持金钱减半了。
所持金钱为100元。

==== 9
当前状态:[money = 100, fruits = []]
所持金钱增加了。
所持金钱为200元。


Process finished with exit code 0
拓展

使用序列化(Serialization)功能来将Memento类的实例保存为文件。请修改示例程序以实现下列功能。

  • 在应用程序启动时,如果发现不存在game.dat文件时,以所持金钱数目为100开始游戏;如果发现 game.dat 已经存在,则以文件中所保存的状态开始游戏
  • 当所持金钱大量增加后,将Memento类的实例保存到game.dat文件中
  1. 将Memento继承Serializable接口来实现序列化操作,除此之外,不需要进行其他修改
public class Memento implements Serializable {}
1
  1. 想要扩展实例保存到文件的功能,只需要修改Caretaker
package com.test.memento.A4;


import com.test.memento.A4.game.Gamer;
import com.test.memento.A4.game.Memento;

import java.io.*;

public class Main {
/**
 * 数据保存的文件名
 */
public static final String SAVE_FILE_NAME = "game.dat";

public static void main(String[] args) {
    // 最初的所持金钱数为100
    Gamer gamer = new Gamer(100);
    // 从文件中读取起始状态
    Memento memento = loadMemento();
    if (memento != null) {
        System.out.println("读取上次保存存档开始游戏。");
        gamer.restoreMemento(memento);
    } else {
        System.out.println("新游戏。");
        memento = gamer.createMemento();
    }
    for (int i = 0; i < 100; i++) {
        // 显示次数
        System.out.println("==== " + i);
        // 显示当前主人公的状态
        System.out.println("当前状态:" + gamer);
        // 进行游戏
        gamer.bet();   

        System.out.println("所持金钱为" + gamer.getMoney() + "元。");

        // 决定如何处理Memento
        if (gamer.getMoney() > memento.getMoney()) {
            System.out.println("    (所持金钱增加了许多,因此保存游戏当前的状态)");
            memento = gamer.createMemento();
            // 将实例保存至文件中
            saveMemento(memento);
        } else if (gamer.getMoney() < memento.getMoney() / 2) {
            System.out.println("    (所持金钱减少了许多,因此将游戏恢复至以前的状态)");
            gamer.restoreMemento(memento);
        }

        // 等待一段时间
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        System.out.println("");
    }
}

/**
 * 保存实例到文件中
 * @param memento
 */
public static void saveMemento(Memento memento) {   
    try {
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream(SAVE_FILE_NAME));
        out.writeObject(memento);
        out.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

/**
 * 从文件中读取实例
 * @return
 */
public static Memento loadMemento() {               
    Memento memento = null;
    try {
        ObjectInput in = new ObjectInputStream(new FileInputStream(SAVE_FILE_NAME));
        memento = (Memento)in.readObject();
        in.close();
    } catch (FileNotFoundException e) {
        System.out.println(e.toString());
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    return memento;
}
}

总结

【优点】

  • 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回退到某个历史的状态
  • 实现了信息的封装,使用户不需要关心状态的保存细节

【缺点】

  • 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存,这个需要注意。为了节约内存,备忘录模式可以和原型模式配合使用

【适用应用场景】

  • 打游戏时的存档
  • Windows 里的ctrl+z
  • IE 中的后退
  • 数据库的事务管理

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何使用 Supabase Auth 在您的应用程序中设置身份验证

在本文中&#xff0c;您将学习基本的关键概念&#xff0c;这些概念将帮助您掌握身份验证和授权的工作原理。 您将首先了解什么是身份验证和授权&#xff0c;然后了解如何使用 Supabase auth 在应用程序中实现身份验证。 &#xff08;本文内容参考&#xff1a;java567.com&…

【Linux取经路】进程控制——进程等待

文章目录 一、进程创建1.1 初识 fork 函数1.2 fork 函数返回值1.3 写时拷贝1.4 fork 的常规用法1.5 fork 调用失败的原因1.6 创建一批进程 二、进程终止2.1 进程退出场景2.2 strerror函数2.3 errno全局变量2.4 程序异常2.5 进程常见退出方法2.6 exit 函数2.7 _exit 函数和 exit…

mysql8安装基础操作(一)

一、下载mysql8.0 1.查看系统glibc版本 这里可以看到glibc版本为2.17&#xff0c;所以下载mysql8.0的版本时候尽量和glibc版本对应 [rootnode2 ~]# rpm -qa |grep -w glibc glibc-2.17-222.el7.x86_64 glibc-devel-2.17-222.el7.x86_64 glibc-common-2.17-222.el7.x86_64 gl…

【Tomcat与网络9】提高Tomcat启动速度的八大措施

本文我们来看一下如何对Tomcat进行调优&#xff0c;我们对于Tomcat的调优主要集中在三个方面&#xff1a;提高启动速度、提高系统稳定性和提高并发能力&#xff0c;后两者很多时候是相辅相成的&#xff0c;我们放在一起看。 Tomcat现在一般都嵌入在SpringBoot里&#xff0c;因…

基于ecal的foxglove studio可视化工具的使用

ecal通讯在自动驾驶和机器人中的应用越来越多,在调试测试过程中,可以使用ecal monitor,ecal recoder和ecal player等工具,对ecal 消息进行监测录制回播。但是,有时候需要对消息进行可视化查看,比如雷达点云信息,相机图像等,可以使用foxglove studio可视化工具。 Foxg…

高性能跨平台网络通信框架 HP-Socket v6.0.1

项目主页 : http://www.oschina.net/p/hp-socket开发文档 : https://www.docin.com/p-4592706661.html下载地址 : https://github.com/ldcsaa/HP-SocketQQ Group: 44636872, 663903943 v6.0.1 更新 一、主要更新 优化Linux通信组件多路复用处理架构&#xff0c;避免“惊群”问…

Android 高德地图切换图层

一、默认样式 Android 地图 SDK 提供了几种预置的地图图层&#xff0c;包括卫星图、白昼地图&#xff08;即最常见的黄白色地图&#xff09;、夜景地图、导航地图、路况图层。 findViewById<TextView>(R.id.normal).setOnClickListener {updateSelectedStatus(TYPE_NORMA…

基于SpringBoot+Vue实现的物流快递仓库管理系统

基于SpringBootVue实现的物流快递仓库管理系统 文章目录 基于SpringBootVue实现的物流快递仓库管理系统系统介绍技术选型成果展示账号地址及其他说明源码获取 系统介绍 系统演示 关注视频号【全栈小白】&#xff0c;观看演示视频 基于SpringBootVue实现的物流快递仓库管理系…

【PyQt】02-基本UI

文章目录 前言一、首先了解什么是GUI&#xff1f;二、初学程序1.界面展示代码运行结果 2.控件2.1按钮展示代码运行结果 2.2 纯文本和输入框代码运行结果 3、重新设置大小 -resize4、移动窗口-move()5、设置界面在电脑中央5.1 代码运行结果 6、设置窗口图标代码运行结果 7、布局…

上岸秘籍来啦!TOGAF认证考试全攻略

上岸秘籍来啦&#xff01;手把手教你如何顺利通过TOGAF认证考试&#xff01; &#x1f31f;考试内容 TOGAF 9.2认证分为两个级别&#xff1a; ✅ TOGAF基础级&#xff1a;掌握标准术语、结构和基本概念&#xff0c;理解企业架构和核心标准。 ✅ TOGAF鉴定级&#xff1a;深入分析…

Skywalking trace UI页面中字段信息详解,包括端点、跨度类型和Peer

刚上手Skywalking的同学可能对 trace UI 页面信息中的字段信息不是很了解&#xff0c;在这里就给大家一一讲解&#xff0c;重点关注端点、跨度类型和Peer 服务 :服务的名称 实例&#xff1a;服务对应的实例 端点&#xff1a;端点(Endpoint) 对于特定服务所接收的请求路径, 如…

2024技术发展洞察与趋势学习总结

2023技术发展洞察 2024技术发展趋势

算法--数论

这里写目录标题 质数&#xff08;素数&#xff09;定义判断是否为质数暴力写法&#xff0c;试除法基本思想具体写法 优化基本思想&#xff08;时间复杂度根号n&#xff09;具体写法 分解质因数分析题意暴力写法基本思想具体代码 优化基本思想&#xff08;时间复杂度小于等于根号…

【GEE】基于GEE可视化和下载Landsat8 L2A数据(镶嵌、裁剪)

之前发过一篇使用GEE下载Landsat8的文章&#xff0c;然后有很多小伙伴私信我各种问题&#xff0c;如L1C、L2数据代码怎么修改&#xff0c;如何镶嵌&#xff0c;如何去云、 如何裁剪等一系列问题。正好快过年了&#xff0c;手头的事也没有多少了&#xff0c;所以这两天整理了一下…

解决PS图片打印不清楚(锐化)

解决PS图片打印不清楚&#xff08;锐化&#xff09; 操作如下&#xff1a;

Antd中使用Select框 在点出弹出下拉列表后 鼠标移到外面滚动会导致下拉框位置偏移

今天使用Select框的时候 由于页面可以滚动 出现了 在点出弹出下拉列表后 鼠标移到外面滚动会导致下拉框位置偏移的bug 如图 上下滚动外部页面会导致下拉框偏移 解决方案&#xff1a; Antd 官方文档 文档中Select框有一个元素 getPopupContainer 并且有一行小字提示 注意…

使用goland IDE编写go windows ui

最近突发奇想&#xff0c;想实现一款工作节奏的提示安排小闹钟。那首先解决的就是UI。本人擅长go语言。那go在windows ui的探索肯定有人做过了吧。一查还真有&#xff0c;通过知乎&#xff0c;csdn等查到目前支持最好的就是walk库了。那走起试试。 一、拷贝go代码 将官网例子…

miniReact<一>

一、工程化配置 1.1 目录结构 1.1.1 Multi-repo VS Mono-repo Multi-repo 每个库有自己独立的仓库&#xff0c;逻辑清晰&#xff0c;协同管理复杂 Mono-repo 很方便管理不同独立的库的生命周期&#xff0c;会有更高的操作复杂度 项目有很多包&#xff0c;同时管理多个不同的…

Java技术栈 —— Spring MVC 与 Spring Boot

参考文章或视频链接[1] Spring vs. Spring Boot vs. Spring MVC[2] Key Differences Between Spring vs Spring Boot vs Spring MVC

如何给图片压缩大小?3种方法任你选择!

如何给图片压缩大小&#xff1f;在日常生活中&#xff0c;将图片压缩可以带来很多便利。首先&#xff0c;对于需要在网络上分享或传输的图片&#xff0c;压缩可以大大减少文件大小&#xff0c;加快上传和下载速度。其次&#xff0c;还可以帮助他们节省空间&#xff0c;存放更多…