零基础设计模式——第二部分:创建型模式 - 原型模式

第二部分:创建型模式 - 5. 原型模式 (Prototype Pattern)

我们已经探讨了单例、工厂方法、抽象工厂和生成器模式。现在,我们来看创建型模式的最后一个主要成员——原型模式。这种模式关注的是通过复制现有对象来创建新对象,而不是通过传统的构造函数实例化。

  • 核心思想:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

原型模式 (Prototype Pattern)

“用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。”

想象一下细胞分裂:一个细胞(原型)可以通过分裂(克隆)产生一个新的、与自身几乎完全相同的细胞。或者,在绘图软件中,你画了一个复杂的图形(原型),然后可以通过“复制”和“粘贴”操作快速创建多个相同的图形副本,再对副本进行微调。

原型模式的核心就是“克隆”或“复制”。当创建一个对象的成本很高(例如,需要复杂的计算、数据库查询或网络请求)时,如果已经有一个相似的对象存在,通过复制这个现有对象来创建新对象可能会更高效。

1. 目的 (Intent)

原型模式的主要目的:

  1. 提高性能:当创建新对象的成本较大时(如初始化时间长、资源消耗多),通过复制已有的原型实例来创建新对象,可以避免重复执行这些昂贵的初始化操作。
  2. 简化对象创建:如果一个对象的创建过程比较复杂,或者需要依赖某些运行时的状态,通过克隆一个已配置好的原型对象可以简化新对象的创建。
  3. 动态添加或删除产品:可以在运行时通过注册原型实例来动态地增加或删除系统中可用的产品类型,而无需修改工厂类(如果与工厂模式结合使用)。
  4. 避免与产品具体类耦合:客户端可以只知道抽象的原型接口,通过克隆来获取新对象,而无需知道具体的实现类名。

2. 生活中的例子 (Real-world Analogy)

  • 复印文件

    • 原型 (Prototype):原始文件(比如一份合同模板)。
    • 克隆操作 (Clone):复印机复印的过程。
    • 新对象 (Cloned Object):复印出来的文件副本。
      你不需要重新打字排版来得到一份新的合同,只需要复印原件,然后在副本上修改少量信息(如签约方、日期)即可。
  • 生物克隆:如克隆羊多莉。多莉就是通过复制现有羊的细胞(原型)而创建的。

  • 制作模具和铸件

    • 原型:一个精心制作的模具。
    • 克隆操作:使用模具进行浇筑。
    • 新对象:通过模具生产出来的多个相同铸件。
  • 游戏中的敌人复制:在一个游戏中,当需要生成大量相同类型的敌人时,可以先创建一个敌人对象作为原型,并设置好其初始属性(如生命值、攻击力、模型等)。之后需要新的敌人时,直接克隆这个原型,而不是每次都从头加载资源和设置属性。

3. 结构 (Structure)

原型模式的结构相对简单,通常包含以下角色:

  1. Prototype (抽象原型):声明一个克隆自身的接口(通常是一个 clone() 方法)。
  2. ConcretePrototype (具体原型):实现 Prototype 接口,重写 clone() 方法来复制自身。这个类是实际被复制的对象。
  3. Client (客户端):让一个原型克隆自身从而创建一个新的对象。客户端不需要知道具体的原型类名,只需要通过抽象原型接口来操作。
    在这里插入图片描述
    克隆过程
  4. 客户端持有一个抽象原型对象的引用。
  5. 当客户端需要一个新的对象时,它调用原型对象的 clone() 方法。
  6. 具体原型类实现 clone() 方法,创建一个当前对象的副本并返回。
  7. 客户端得到一个新的对象,这个新对象与原型对象具有相同的初始状态(属性值)。

4. 深拷贝 vs. 浅拷贝 (Deep Copy vs. Shallow Copy)

这是原型模式中一个非常重要的概念。

  • 浅拷贝 (Shallow Copy)

    • 当复制一个对象时,只复制对象本身和其中的基本数据类型成员的值。
    • 如果对象包含对其他对象的引用(引用类型成员),则只复制这些引用,而不复制引用所指向的对象。因此,原对象和副本中的引用类型成员将指向内存中的同一个对象。
    • 修改副本中的引用类型成员所指向的对象,会影响到原对象中对应的成员(因为它们指向同一个东西)。
  • 深拷贝 (Deep Copy)

    • 当复制一个对象时,不仅复制对象本身和基本数据类型成员,还会递归地复制所有引用类型成员所指向的对象。
    • 原对象和副本中的引用类型成员将指向不同的、内容相同的对象。
    • 修改副本中的引用类型成员所指向的对象,不会影响到原对象。

选择深拷贝还是浅拷贝取决于具体需求。如果希望副本的修改不影响原型,或者原型和副本需要独立地管理其引用的对象,那么应该使用深拷贝。如果共享引用的对象是不可变的,或者业务逻辑允许共享,那么浅拷贝可能就足够了,并且性能通常更高。

在Java中,Object类的 clone() 方法默认执行的是浅拷贝。要实现深拷贝,需要在 clone() 方法中对引用类型的字段也进行递归克隆。
在Go中,没有内建的 clone() 方法。复制通常通过创建一个新实例并手动复制字段值来完成。对于引用类型(如切片、映射、指针),需要特别注意是复制引用还是复制底层数据。

5. 适用场景 (When to Use)

  • 当一个系统应该独立于它的产品创建、构成和表示时,并且你想要在运行时指定实例化的类。
  • 当要实例化的类是在运行时指定时,例如,通过动态装载。
  • 为了避免创建一个与产品类层次平行的工厂类层次时(即不想为了创建不同产品而创建一堆工厂类)。
  • 当一个类的实例只能有几种不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
  • 创建对象的成本很高:例如,对象初始化需要大量计算、I/O操作或网络通信。
  • 需要创建大量相似对象:只有少量属性不同的对象。
  • 系统需要在运行时动态添加或修改可创建的对象类型

6. 优缺点 (Pros and Cons)

优点:

  1. 性能提升:对于创建成本高的对象,克隆比重新创建更快。
  2. 简化对象创建:可以复制一个已经初始化好的复杂对象,避免重复的初始化逻辑。
  3. 灵活性高:可以在运行时动态地获取和复制原型对象。
  4. 对客户端隐藏具体类型:客户端只需要知道抽象原型接口即可创建对象。

缺点:

  1. 需要为每个类实现克隆方法:每个需要作为原型的类都必须实现 clone() 方法,这可能比较繁琐,特别是当类层次结构很深或包含许多字段时。
  2. 深拷贝实现复杂:正确实现深拷贝可能比较复杂,需要仔细处理所有引用类型的成员,以避免意外共享或循环引用问题。
  3. 可能违反开闭原则(如果克隆逻辑复杂):如果克隆逻辑非常复杂且依赖于具体类的内部结构,当具体类修改时,克隆方法可能也需要修改。

7. 实现方式 (Implementations)

让我们通过一个图形绘制的例子来看看原型模式的实现。假设我们有不同形状(圆形、矩形)的对象,它们可以被克隆。

抽象原型 (Shape)
// shape.go
package shapeimport "fmt"// Shape 抽象原型接口
type Shape interface {Clone() ShapeDraw()SetID(id string)GetID() string
}
// Shape.java
package com.example.shape;// 抽象原型接口
// Java 中通常让原型类实现 Cloneable 接口并重写 clone() 方法
public interface Shape extends Cloneable { // Cloneable 是一个标记接口Shape cloneShape(); // 自定义一个更明确的克隆方法名void draw();void setId(String id);String getId();
}
具体原型 (Circle, Rectangle)
// circle.go
package shapeimport "fmt"// Circle 具体原型
type Circle struct {ID     stringRadius intX, Y   int // 圆心坐标
}func NewCircle(id string, radius, x, y int) *Circle {return &Circle{ID: id, Radius: radius, X: x, Y: y}
}func (c *Circle) SetID(id string) { c.ID = id }
func (c *Circle) GetID() string   { return c.ID }// Clone 实现浅拷贝,因为 Circle 的字段都是值类型或string (string在Go中是值类型行为)
func (c *Circle) Clone() Shape {return &Circle{ID:     c.ID + "_clone", // 给克隆体一个新IDRadius: c.Radius,X:      c.X,Y:      c.Y,}
}func (c *Circle) Draw() {fmt.Printf("Drawing Circle [ID: %s, Radius: %d, Center: (%d,%d)]\n", c.ID, c.Radius, c.X, c.Y)
}// rectangle.go
package shapeimport "fmt"// Rectangle 具体原型
type Rectangle struct {ID            stringWidth, Height intX, Y          int // 左上角坐标
}func NewRectangle(id string, width, height, x, y int) *Rectangle {return &Rectangle{ID: id, Width: width, Height: height, X: x, Y: y}
}func (r *Rectangle) SetID(id string) { r.ID = id }
func (r *Rectangle) GetID() string   { return r.ID }// Clone 实现浅拷贝
func (r *Rectangle) Clone() Shape {return &Rectangle{ID:     r.ID + "_clone",Width:  r.Width,Height: r.Height,X:      r.X,Y:      r.Y,}
}func (r *Rectangle) Draw() {fmt.Printf("Drawing Rectangle [ID: %s, Width: %d, Height: %d, TopLeft: (%d,%d)]\n", r.ID, r.Width, r.Height, r.X, r.Y)
}
// Circle.java
package com.example.shape;// 具体原型
public class Circle implements Shape {private String id;private int radius;private Point center; // 假设 Point 是一个自定义的可变类public Circle(String id, int radius, int x, int y) {this.id = id;this.radius = radius;this.center = new Point(x, y);System.out.println("Circle created with ID: " + id);}// 私有构造,用于克隆private Circle(String id, int radius, Point center) {this.id = id;this.radius = radius;this.center = center; // 注意这里,如果是浅拷贝,center会被共享}@Overridepublic void setId(String id) { this.id = id; }@Overridepublic String getId() { return this.id; }public int getRadius() { return radius; }public Point getCenter() { return center; }public void setCenter(int x, int y) { this.center.setX(x); this.center.setY(y); }@Overridepublic Shape cloneShape() {System.out.println("Cloning Circle with ID: " + this.id);Circle clonedCircle = null;try {// 默认的 Object.clone() 是浅拷贝clonedCircle = (Circle) super.clone(); // 为了实现深拷贝,需要对可变引用类型字段进行单独克隆clonedCircle.id = this.id + "_clone"; // 通常给克隆体新IDclonedCircle.center = (Point) this.center.clone(); // 假设 Point 也实现了 Cloneable 和 clone()} catch (CloneNotSupportedException e) {// This should not happen if we implement Cloneablee.printStackTrace();}return clonedCircle;}@Overridepublic void draw() {System.out.printf("Drawing Circle [ID: %s, Radius: %d, Center: %s]%n", id, radius, center);}
}// Rectangle.java
package com.example.shape;public class Rectangle implements Shape {private String id;private int width;private int height;private Point topLeft; // 可变引用类型public Rectangle(String id, int width, int height, int x, int y) {this.id = id;this.width = width;this.height = height;this.topLeft = new Point(x,y);System.out.println("Rectangle created with ID: " + id);}@Overridepublic void setId(String id) { this.id = id; }@Overridepublic String getId() { return this.id; }public Point getTopLeft() { return topLeft; }public void setTopLeft(int x, int y) { this.topLeft.setX(x); this.topLeft.setY(y); }@Overridepublic Shape cloneShape() {System.out.println("Cloning Rectangle with ID: " + this.id);Rectangle clonedRectangle = null;try {clonedRectangle = (Rectangle) super.clone();clonedRectangle.id = this.id + "_clone";clonedRectangle.topLeft = (Point) this.topLeft.clone(); // 深拷贝 Point} catch (CloneNotSupportedException e) {e.printStackTrace();}return clonedRectangle;}@Overridepublic void draw() {System.out.printf("Drawing Rectangle [ID: %s, Width: %d, Height: %d, TopLeft: %s]%n", id, width, height, topLeft);}
}// Point.java (辅助类,用于演示深拷贝)
package com.example.shape;public class Point implements Cloneable {private int x;private int y;public Point(int x, int y) { this.x = x; this.y = y; }public int getX() { return x; }public void setX(int x) { this.x = x; }public int getY() { return y; }public void setY(int y) { this.y = y; }@Overridepublic String toString() { return "(" + x + "," + y + ")"; }@Overrideprotected Object clone() throws CloneNotSupportedException {// Point 只包含基本类型,所以 super.clone() 已经是深拷贝效果了// 如果 Point 内部还有其他引用类型,则需要进一步处理return super.clone();}
}
原型管理器 (可选, PrototypeManager / ShapeCache)

有时会引入一个原型管理器类,用于存储和检索原型实例。客户端向管理器请求一个特定类型的原型,然后克隆它。

// shape_cache.go
package shapeimport "fmt"// ShapeCache 原型管理器
type ShapeCache struct {prototypes map[string]Shape
}func NewShapeCache() *ShapeCache {cache := &ShapeCache{prototypes: make(map[string]Shape)}cache.loadCache()return cache
}// loadCache 初始化原型实例并存储
func (sc *ShapeCache) loadCache() {circle := NewCircle("circle1", 10, 0, 0)rectangle := NewRectangle("rect1", 20, 10, 0, 0)sc.prototypes[circle.GetID()] = circlesc.prototypes[rectangle.GetID()] = rectanglefmt.Println("ShapeCache: Prototypes loaded.")
}// GetShape 克隆并返回指定ID的原型
func (sc *ShapeCache) GetShape(id string) (Shape, error) {prototype, found := sc.prototypes[id]if !found {return nil, fmt.Errorf("prototype with id '%s' not found", id)}return prototype.Clone(), nil
}// AddShape 允许运行时添加新的原型
func (sc *ShapeCache) AddShape(id string, shape Shape) {sc.prototypes[id] = shapefmt.Printf("ShapeCache: Prototype '%s' added.\n", id)
}
// ShapeCache.java
package com.example.shape;import java.util.Hashtable;// 原型管理器
public class ShapeCache {private static Hashtable<String, Shape> shapeMap = new Hashtable<>();public static Shape getShape(String shapeId) throws CloneNotSupportedException {Shape cachedShape = shapeMap.get(shapeId);if (cachedShape == null) {System.err.println("ShapeCache: Prototype with ID '" + shapeId + "' not found.");return null;}System.out.println("ShapeCache: Returning clone of prototype with ID: " + shapeId);return cachedShape.cloneShape(); // 调用我们自定义的克隆方法}// loadCache 会加载每种形状的实例,并将它们存储在 Hashtable 中public static void loadCache() {System.out.println("ShapeCache: Loading initial prototypes...");Circle circle = new Circle("circle-proto", 10, 0, 0);shapeMap.put(circle.getId(), circle);Rectangle rectangle = new Rectangle("rect-proto", 20, 10, 5, 5);shapeMap.put(rectangle.getId(), rectangle);System.out.println("ShapeCache: Prototypes loaded.");}// 允许运行时添加新的原型public static void addPrototype(String id, Shape shape) {shapeMap.put(id, shape);System.out.println("ShapeCache: Prototype '" + id + "' added.");}
}
客户端使用
// main.go (示例用法)
/*
package mainimport ("fmt""./shape"
)func main() {cache := shape.NewShapeCache()// 从缓存获取原型并克隆circle1, err := cache.GetShape("circle1")if err != nil {fmt.Println("Error:", err)return}circle1.Draw() // ID: circle1_clonerect1, err := cache.GetShape("rect1")if err != nil {fmt.Println("Error:", err)return}rect1.Draw() // ID: rect1_clone// 修改克隆体的属性circle1.SetID("myCustomCircle")// 如果是 Circle 类型,可以进行类型断言来访问特定属性if c, ok := circle1.(*shape.Circle); ok {c.Radius = 100c.X = 50}circle1.Draw() // ID: myCustomCircle, Radius: 100, Center: (50,0)// 原始原型不受影响originalCircle, _ := cache.GetShape("circle1") // 再次获取会重新克隆originalCircleProto := cache.prototypes["circle1"] // 直接访问原型 (不推荐直接修改原型)fmt.Println("--- Original prototype vs new clone from cache ---")originalCircleProto.Draw() // ID: circle1, Radius: 10originalCircle.Draw()      // ID: circle1_clone, Radius: 10 (新克隆的)// 运行时添加新原型trianglePrototype := shape.NewTriangle("triangle-proto", 5, 10) // 假设有 Triangle 类型cache.AddShape(trianglePrototype.GetID(), trianglePrototype)clonedTriangle, _ := cache.GetShape("triangle-proto")if clonedTriangle != nil {clonedTriangle.Draw()}
}// 假设添加一个 Triangle 类型 (triangle.go)
/*
package shape
import "fmt"
type Triangle struct { ID string; Base, Height int }
func NewTriangle(id string, base, height int) *Triangle { return &Triangle{id, base, height} }
func (t *Triangle) SetID(id string) { t.ID = id }
func (t *Triangle) GetID() string   { return t.ID }
func (t *Triangle) Clone() Shape    { return &Triangle{t.ID + "_clone", t.Base, t.Height} }
func (t *Triangle) Draw()           { fmt.Printf("Drawing Triangle [ID: %s, Base: %d, Height: %d]\n", t.ID, t.Base, t.Height) }
*/
// Main.java (示例用法)
/*
package com.example;import com.example.shape.Circle;
import com.example.shape.Rectangle;
import com.example.shape.Shape;
import com.example.shape.ShapeCache;public class Main {public static void main(String[] args) {ShapeCache.loadCache(); // 加载原型try {System.out.println("--- Cloning and using shapes ---");Shape clonedCircle1 = ShapeCache.getShape("circle-proto");if (clonedCircle1 != null) {clonedCircle1.draw(); // ID: circle-proto_clone}Shape clonedRectangle1 = ShapeCache.getShape("rect-proto");if (clonedRectangle1 != null) {clonedRectangle1.draw(); // ID: rect-proto_clone}System.out.println("\n--- Modifying a cloned shape ---");// 修改克隆体的属性if (clonedCircle1 != null) {clonedCircle1.setId("myCustomCircle");if (clonedCircle1 instanceof Circle) {Circle customCircle = (Circle) clonedCircle1;customCircle.setCenter(100, 100); // 修改 Point 对象}clonedCircle1.draw(); // ID: myCustomCircle, Center: (100,100)}System.out.println("\n--- Verifying original prototype is unchanged ---");// 原始原型不受影响 (因为我们实现了深拷贝 Point)Shape originalCircleProto = ShapeCache.shapeMap.get("circle-proto"); // 直接访问原型 (不推荐)if (originalCircleProto != null) {System.out.print("Original Prototype in Cache: ");originalCircleProto.draw(); // ID: circle-proto, Center: (0,0)}Shape newlyClonedCircle = ShapeCache.getShape("circle-proto");if (newlyClonedCircle != null) {System.out.print("Newly Cloned from Cache: ");newlyClonedCircle.draw(); // ID: circle-proto_clone, Center: (0,0)}// 演示如果 Point 是浅拷贝会发生什么// 如果 Circle.cloneShape() 中对 center 只是 clonedCircle.center = this.center;// 那么修改 customCircle.setCenter(100,100) 会同时修改 originalCircleProto 的 centerSystem.out.println("\n--- Adding a new prototype at runtime ---");Circle newProto = new Circle("circle-large-proto", 50, 10, 10);ShapeCache.addPrototype(newProto.getId(), newProto);Shape clonedLargeCircle = ShapeCache.getShape("circle-large-proto");if(clonedLargeCircle != null) {clonedLargeCircle.draw();}} catch (CloneNotSupportedException e) {e.printStackTrace();}}
}
*/

8. 总结

原型模式通过复制(克隆)现有对象来创建新对象,从而在特定场景下(如对象创建成本高、需要大量相似对象)提供了一种高效且灵活的对象创建方式。核心在于实现 clone() 方法,并正确处理深拷贝与浅拷贝的问题。当与原型管理器结合使用时,还可以实现运行时的动态产品配置。

记住它的核心:克隆现有对象,高效创建

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

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

相关文章

C++(初阶)(十九)——红黑树

红黑树 红黑树概念规则实现结点插入变色变色参考代码&#xff1a; 查找查找参考代码 遍历 红黑树检查完整代码 概念 红⿊树是⼀棵⼆叉搜索树。它的每个结点增加⼀个存储位来表示结点的颜⾊&#xff0c;可以是红色或者黑色&#xff08;并不会出现第三种颜色&#xff09;。 通过…

Mistral AI 开源最新 Small 模型——Devstral-Small-2505

Devstral 是一款专为软件工程任务设计的代理型大语言模型&#xff08;LLM&#xff09;&#xff0c;由 Mistral AI 和 All Hands AI 合作开发 &#x1f64c;。Devstral 擅长使用工具探索代码库、编辑多个文件以及驱动软件工程代理。该模型在 SWE-bench 上表现出色&#xff0c;使…

CDGA|一线二线企业数据治理项目目前发展状况

一线城市与二线城市企业在数据治理项目的发展状况上存在一定差异&#xff0c;主要体现在目标、资源投入、策略实施以及文化培育等方面。 一线城市企业数据治理项目发展状况 ‌数据治理目标全面系统‌&#xff1a; ‌数据质量与安全‌&#xff1a;一线城市的大型企业通常拥有海量…

Lyra学习笔记1地图角色加载流程

目录 1 地图加载流程1.1 默认Experience的加载1.2 加载角色1.3 加载场景中的几个传送点 2 几个内建类的笔记2.1 UDataAsset2.2 UAssetManager 纯个人笔记&#xff0c;有错误欢迎指正&#xff0c;学习阶段基本看到不会的就写一写&#xff0c;最后有时间会梳理整体结构 先看完了官…

SurfaceFlinger及Android应用RenderThread角度观察Jank丢帧卡顿

SurfaceFlinger及Android应用RenderThread角度观察Jank丢帧卡顿 CPU、GPU、Display 三个部分&#xff1a;CPU 负责计算帧数据&#xff0c;把计算好的数据交给 GPU&#xff0c;GPU 会对图形数据进行渲染&#xff0c;渲染好后放到 buffer &#xff08;图像缓冲区&#xff09;存起…

《牛客》数组中出现次数超过一半的数字

牛客的刷题之路不停歇 ⌓‿⌓ 不积跬步无以至千里&#xff0c;不积小流无以成江海 The harder you work,the luckier you will be 题目及示例 题目链接 描述 给一个长度为 n 的数组&#xff0c;数组中有一个数字出现的次数超过数组长度的一半&#xff0c;请找出这个数字。 例…

七彩喜康养护理——科技赋能下的全周期健康守护

在当今社会&#xff0c;随着人们健康意识的不断提高&#xff0c;护理行业逐渐走向专业化、精细化&#xff0c;而七彩喜智养护理作为一种新兴的护理方式&#xff0c;逐渐受到了广泛的关注和应用。 它不仅仅是针对单一病症的治疗护理&#xff0c;而是一种全面的、全方位的健康管…

【爬虫】12306自动化购票

上文&#xff1a; 【爬虫】12306查票-CSDN博客 下面是简单的自动化进行抢票&#xff0c;只写到预定票&#xff0c;没有写完登陆&#xff0c; 跳出登陆后与上述代码同理修改即可。 感觉xpath最简单&#xff0c;复制粘贴&#xff1a; 还有很多写法&#xff1a; 官网地址&#…

Java设计模式之组合模式:从入门到精通(保姆级教程)

文章目录 1. 组合模式概述1.1 专业定义1.2 通俗解释1.3 模式结构2. 组合模式详细解析2.1 模式优缺点2.2 适用场景3. 组合模式实现详解3.1 基础实现3.2 代码解析4. 组合模式进阶应用4.1 透明式 vs 安全式组合模式4.2 组合模式与递归4.3 组合模式与迭代器5. 组合模式在实际开发中…

游戏如何应对反编译工具dnspy

Unity Mono 是 Unity 引擎默认的脚本运行时环境&#xff0c;由跨平台的开源 .NET 框架实现&#xff0c;它允许开发者使用 C# 等编程语言编写游戏逻辑&#xff0c;凭借简单易用的开发环境和高效的脚本编译速度&#xff0c;得到了众多游戏的青睐。 在 Mono 模式下&#xff0c;游…

腾讯云证书过期提醒的应对措施,Caddy 自动管理的 Let‘s Encrypt 证书.

用腾讯的免费证书&#xff0c;90天需要换一次。 Caddy 自动管理的 Lets Encrypt 证书. 在网站上按F12然后找到security选项&#xff0c;然后选择View certifcate 就可以看到证书的有效期。 完全无需操作 你的网站实际使用的是 Caddy 自动管理的 Lets Encrypt 证书&#xff0c;…

[Java实战]Spring Boot整合Elasticsearch(二十六)

[Java实战]Spring Boot整合Elasticsearch&#xff08;二十六&#xff09; 摘要&#xff1a;本文通过完整的实战演示&#xff0c;详细讲解如何在Spring Boot项目中整合Elasticsearch&#xff0c;实现数据的存储、检索和复杂查询功能。包含版本适配方案、Spring Data Elasticsea…

【关联git本地仓库,上传项目到github】

目录 1.下载git2.绑定用户3.git本地与远程仓库交互4.github项目创建5.上传本地项目到github6.完结撒花❀❀❀&#xff01;&#xff01;&#xff01; 1.下载git git下载地址&#xff1a;https://git-scm.com/downloads 下载安装后创建快捷地址&#xff1a;&#xff08;此处比较…

[Vue]路由基础使用和路径传参

实际项目中不可能就一个页面&#xff0c;会有很多个页面。在Vue里面&#xff0c;页面与页面之间的跳转和传参会使用我们的路由: vue-router 基础使用 要使用我们需要先给我们的项目添加依赖:vue-router。使用命令下载: npm install vue-router 使用路由会涉及到下面几个对象:…

软考-软件工程开发模型

软考-软件工程开发模型 参考视频&#xff1a; 软件工程概述&开发模型 &#xff0c;配合视频理解更清晰&#xff5e; 软件的生命周期为&#xff1a;需求分析、软件设计、软件开发、运行维护直至被淘汰 几个阶段。 软件工程支持 4 个活动&#xff0c;简称 PDCA&#xff0c…

【写在创作纪念日】基于SpringBoot和PostGIS的各省东西南北四至极点区县可视化

目录 前言 一、空间检索简介 1、空间表结构 2、四至空间检索 二、前后端实现 1、后端实现 2、前端集成 三、成果展示 1、东部省份 2、西部省份 3、南部省份 4、北部省份 5、中部省份 四、总结 前言 在当今数字化时代&#xff0c;地理信息数据的分析与可视化对于众…

智能守护校园“舌尖安全“:AI视频分析赋能名厨亮灶新时代

引言&#xff1a; 在校园食品安全备受关注的今天&#xff0c;一套融合视频监控管理平台与AI视频分析盒子的智能解决方案正在全国多地学校食堂悄然落地&#xff0c;为传统的"名厨亮灶"工程注入科技新动能。这套系统不仅实现了后厨操作的"透明化"&#xff0…

【软件设计师】计算机网络考点整理

以下是软件设计师考试中 ​​计算机网络​​ 的核心考点总结&#xff0c;帮助您高效备考&#xff1a; ​​一、网络体系结构与协议​​ ​​OSI七层模型 & TCP/IP四层模型​​ 各层功能&#xff08;物理层-数据链路层-网络层-传输层-会话层-表示层-应用层&#xff09;对应协…

Starrocks的CBO基石--统计信息的来源 StatisticAutoCollector

背景 本文来从底层代码的实现来分析一下Starrocks怎么获取统计信息&#xff0c;这些统计信息在后续基于CBO的代价计算的时候有着重要的作用 本文基于Starrrocks 3.3.5 结论 Starrocks的统计信息的收集是通过周期性的运行一系列的SQL&#xff08;以分区为维度&#xff0c;如果…

深度学习模型部署(四)——RKNN

一、RKNN部署及工具包安装 参考1&#xff1a;https://blog.csdn.net/qq_40280673/article/details/136211086#/ 参考2&#xff1a;瑞芯微官方教程 RKNN部署针对瑞芯微芯片优化&#xff0c;支持NPU硬件加速&#xff0c;需要安装rknn-toolkit&#xff0c;用于将pytorch模型转换为…