零基礎設計模式——第二部分:創建型模式 - 原型模式

第二部分:創建型模式 - 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
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/82031.shtml
英文地址,請注明出處:http://en.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模型轉換為…