零基础设计模式——行为型模式 - 状态模式

第四部分:行为型模式 - 状态模式 (State Pattern)

我们继续学习行为型模式,接下来是状态模式。这个模式允许一个对象在其内部状态改变时改变它的行为,对象看起来就像是改变了它的类。

  • 核心思想:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

状态模式 (State Pattern)

“允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。” (Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.)

想象一下一个自动售货机:

  • 自动售货机 (Context):这是我们的主要对象。
  • 状态 (State):售货机有多种状态,比如:
    • 无币状态 (NoCoinState):没有投币。此时你按购买按钮是无效的。
    • 有币状态 (HasCoinState):已经投币。此时你可以选择商品或退币。
    • 售出商品状态 (SoldState):正在出货。此时你不能再投币或选择商品。
    • 商品售罄状态 (SoldOutState):所有商品都卖完了。此时投币会立即退回,选择商品无效。

当用户进行操作(如投币、按按钮)时,售货机的行为会根据其当前状态而有所不同。例如,在“无币状态”下投币,售货机会转换到“有币状态”。在“有币状态”下按购买按钮,如果商品充足,售货机会转换到“售出商品状态”,然后(如果还有货)可能回到“无币状态”。

状态模式将每种状态的行为封装在不同的状态对象中,Context 对象(售货机)会将行为委托给当前的状态对象。当 Context 的状态改变时,它会切换到另一个状态对象,从而改变其行为。

1. 目的 (Intent)

状态模式的主要目的:

  1. 封装与状态相关的行为:将不同状态下的行为逻辑分离到各自的状态类中。
  2. 使状态转换明确:状态转换的逻辑可以分布在状态类中,或者由 Context 统一管理。
  3. 避免大量的条件语句:如果不使用状态模式,Context 类中可能会充斥着大量的 if/elseswitch 语句来根据当前状态执行不同的行为。状态模式通过多态性消除了这些条件分支。
  4. 使对象看起来像改变了类:当对象的状态改变时,它的行为也随之改变,给外部调用者的感觉就像是对象的类发生了变化。

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

  • 电灯开关

    • 状态:开 (OnState),关 (OffState)。
    • 行为:按下开关。在“关”状态下按,灯会亮,状态变为“开”。在“开”状态下按,灯会灭,状态变为“关”。
  • TCP连接状态

    • 状态:已建立 (Established),监听 (Listen),关闭 (Closed),正在关闭 (Closing) 等。
    • 行为:发送数据、接收数据、关闭连接等操作在不同状态下有不同的表现或限制。
  • 播放器状态

    • 状态:播放中 (PlayingState),暂停 (PausedState),停止 (StoppedState)。
    • 行为:点击播放/暂停按钮,点击停止按钮。在不同状态下,这些按钮的行为不同。
  • 游戏角色的状态

    • 状态:站立、行走、跑步、跳跃、攻击、防御、中毒、冰冻等。
    • 行为:玩家输入指令(如移动、攻击)时,角色的响应会根据其当前状态而变化。

3. 结构 (Structure)

状态模式通常包含以下角色:

  1. Context (上下文)

    • 定义客户端感兴趣的接口。
    • 维护一个 ConcreteState 子类的实例,这个实例定义当前状态。
    • 可以将行为委托给当前的状态对象。
    • 负责状态的转换,可以由 Context 自身或 State 对象来管理转换逻辑。
  2. State (状态接口或抽象类)

    • 定义一个接口以封装与 Context 的一个特定状态相关的行为。
    • 通常包含处理各种请求的方法,这些方法的实现在具体状态类中。
  3. ConcreteState (具体状态)

    • 实现 State 接口。
    • 每一个子类实现一个与 Context 的一种状态相关的行为。
    • 可以负责状态的转换,即在执行完某个行为后,改变 Context 的当前状态到下一个状态。
      在这里插入图片描述

状态转换的职责

  • 由 Context 决定:Context 接收到请求后,根据当前状态和请求类型,决定转换到哪个新状态。
  • 由 State 子类决定:每个 ConcreteState 在处理完请求后,自行决定下一个状态是什么,并通知 Context 改变状态。这种方式更符合“状态对象知道下一个状态”的逻辑,但可能导致状态类之间产生依赖。

4. 适用场景 (When to Use)

  • 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
  • 代码中包含大量与对象状态有关的条件语句(if/elseswitch)。状态模式可以将这些分支逻辑分散到不同的状态类中。
  • 当操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态时。状态模式将每一个分支封装到一个独立的类中。
  • 当你希望代码更清晰地表达状态和状态转换时。

5. 优缺点 (Pros and Cons)

优点:

  1. 封装了与状态相关的行为:将所有与特定状态相关的行为都放入一个对象中,使得代码更加集中和易于维护。
  2. 使得状态转换明确:将状态转换逻辑封装在状态类或 Context 中,使得状态转换的规则更加清晰。
  3. 消除了大量的条件分支:通过多态性替代了冗长的 if/elseswitch 语句,使代码更简洁,更易于理解和扩展。
  4. 易于增加新的状态:增加新的状态只需要添加一个新的 State 子类,并修改相关的转换逻辑,符合开闭原则。

缺点:

  1. 类数量增多:状态模式会导致系统中类的数量增加,每个状态都需要一个对应的类。
  2. 结构可能变得复杂:如果状态过多或者状态转换逻辑非常复杂,整个系统的结构可能会变得难以理解。
  3. Context 与 State 的耦合:State 对象通常需要访问 Context 对象来改变其状态或获取 Context 的数据,这可能导致一定的耦合。如果 State 对象也负责状态转换,那么 State 对象之间也可能产生耦合。

6. 实现方式 (Implementations)

让我们以一个简单的文档编辑器为例,它有草稿 (Draft)、审核中 (Moderation) 和已发布 (Published) 三种状态。

状态接口 (State)
// document_state.go (State interface and concrete states)
package state// Forward declaration for Document context
type Documenter interface {SetState(state Statelike)// Potentially other methods the state might need from the documentGetContent() stringSetContent(content string)
}// Statelike 状态接口
type Statelike interface {Render(doc Documenter)Publish(doc Documenter)Review(doc Documenter) // New method for review processGetName() string
}
// State.java (State interface)
package com.example.state;public interface State {void render(Document document);void publish(Document document);void review(Document document); // New method for review processString getName();
}
具体状态 (DraftState, ModerationState, PublishedState)
// document_state.go (continued)
package stateimport "fmt"// --- DraftState --- (草稿状态)
type DraftState struct{}func NewDraftState() *DraftState { return &DraftState{} }func (s *DraftState) Render(doc Documenter) {fmt.Printf("Draft: Rendering content - '%s' (can be edited)\n", doc.GetContent())
}func (s *DraftState) Publish(doc Documenter) {fmt.Println("Draft: Content submitted for review.")doc.SetState(NewModerationState()) // Transition to Moderation
}func (s *DraftState) Review(doc Documenter) {fmt.Println("Draft: Cannot review a draft directly. Submit for review first.")
}func (s *DraftState) GetName() string { return "Draft" }// --- ModerationState --- (审核中状态)
type ModerationState struct{}func NewModerationState() *ModerationState { return &ModerationState{} }func (s *ModerationState) Render(doc Documenter) {fmt.Printf("Moderation: Rendering content - '%s' (awaiting review, read-only)\n", doc.GetContent())
}func (s *ModerationState) Publish(doc Documenter) {fmt.Println("Moderation: Content approved and published.")doc.SetState(NewPublishedState()) // Transition to Published
}func (s *ModerationState) Review(doc Documenter) {// Simulate review logic, e.g., admin approves or rejects// For simplicity, let's assume it's always approved here by calling Publishfmt.Println("Moderation: Reviewing content...")s.Publish(doc) // If approved, it publishes// If rejected, it might go back to DraftState:// fmt.Println("Moderation: Content rejected, returning to draft.")// doc.SetState(NewDraftState())
}func (s *ModerationState) GetName() string { return "Moderation" }// --- PublishedState --- (已发布状态)
type PublishedState struct{}func NewPublishedState() *PublishedState { return &PublishedState{} }func (s *PublishedState) Render(doc Documenter) {fmt.Printf("Published: Displaying content - '%s' (live, read-only)\n", doc.GetContent())
}func (s *PublishedState) Publish(doc Documenter) {fmt.Println("Published: Content is already published.")
}func (s *PublishedState) Review(doc Documenter) {fmt.Println("Published: Content is already published, no further review needed.")
}func (s *PublishedState) GetName() string { return "Published" }
// DraftState.java
package com.example.state;public class DraftState implements State {@Overridepublic void render(Document document) {System.out.println("Draft: Rendering content - '" + document.getContent() + "' (can be edited)");}@Overridepublic void publish(Document document) {System.out.println("Draft: Content submitted for review.");document.setState(new ModerationState()); // Transition to Moderation}@Overridepublic void review(Document document) {System.out.println("Draft: Cannot review a draft directly. Submit for review first.");}@Overridepublic String getName() {return "Draft";}
}// ModerationState.java
package com.example.state;public class ModerationState implements State {@Overridepublic void render(Document document) {System.out.println("Moderation: Rendering content - '" + document.getContent() + "' (awaiting review, read-only)");}@Overridepublic void publish(Document document) { // This is effectively 'approve and publish'System.out.println("Moderation: Content approved and published.");document.setState(new PublishedState()); // Transition to Published}@Overridepublic void review(Document document) {// In a real scenario, this might involve more complex logic or user roles.// For simplicity, let's say reviewing it means it's ready for publishing.System.out.println("Moderation: Reviewing content... Content looks good!");// If approved, it transitions to Published. This could be done here or by calling publish().// Let's assume the 'publish' action from Moderation state means 'approve and publish'.// If rejected, it might go back to DraftState:// System.out.println("Moderation: Content rejected, returning to draft.");// document.setState(new DraftState());// For this example, let's assume review leads to publish if called.this.publish(document); // Or a more specific 'approve' method that then calls publish.}@Overridepublic String getName() {return "Moderation";}
}// PublishedState.java
package com.example.state;public class PublishedState implements State {@Overridepublic void render(Document document) {System.out.println("Published: Displaying content - '" + document.getContent() + "' (live, read-only)");}@Overridepublic void publish(Document document) {System.out.println("Published: Content is already published.");}@Overridepublic void review(Document document) {System.out.println("Published: Content is already published, no further review needed.");}@Overridepublic String getName() {return "Published";}
}
上下文 (Document - Context)
// document.go (Context)
package context // Renamed package to avoid conflict with built-in contextimport ("../state""fmt"
)// Document 上下文
type Document struct {currentState state.Statelikecontent      string
}func NewDocument() *Document {doc := &Document{}doc.currentState = state.NewDraftState() // Initial statedoc.content = "Initial draft content."fmt.Printf("Document created. Initial state: %s\n", doc.currentState.GetName())return doc
}func (d *Document) SetState(s state.Statelike) {fmt.Printf("Document: Changing state from %s to %s\n", d.currentState.GetName(), s.GetName())d.currentState = s
}func (d *Document) GetContent() string {return d.content
}func (d *Document) SetContent(content string) {if d.currentState.GetName() == "Draft" { // Only allow content change in Draft stated.content = contentfmt.Printf("Document: Content updated to '%s'\n", content)} else {fmt.Printf("Document: Cannot set content in %s state.\n", d.currentState.GetName())}
}// Delegate actions to the current state
func (d *Document) Render() {d.currentState.Render(d)
}func (d *Document) Publish() {d.currentState.Publish(d)
}func (d *Document) Review() {d.currentState.Review(d)
}func (d *Document) GetCurrentStateName() string {return d.currentState.GetName()
}
// Document.java (Context)
package com.example.state;public class Document {private State currentState;private String content;public Document() {this.currentState = new DraftState(); // Initial statethis.content = "Initial draft content.";System.out.println("Document created. Initial state: " + currentState.getName());}public void setState(State state) {System.out.println("Document: Changing state from " + this.currentState.getName() + " to " + state.getName());this.currentState = state;}public String getContent() {return content;}public void setContent(String content) {// Example: Only allow content change in Draft stateif (this.currentState instanceof DraftState) {this.content = content;System.out.println("Document: Content updated to '" + content + "'");} else {System.out.println("Document: Cannot set content in " + this.currentState.getName() + " state.");}}// Delegate actions to the current statepublic void render() {this.currentState.render(this);}public void publish() {this.currentState.publish(this);}public void review() {this.currentState.review(this);}public String getCurrentStateName() {return this.currentState.getName();}
}
客户端使用
// main.go (示例用法)
/*
package mainimport ("./context""fmt"
)func main() {doc := context.NewDocument()fmt.Println("\n--- Current State:" , doc.GetCurrentStateName(), "---")doc.Render() // Draft: Rendering content...doc.SetContent("My awesome first draft!")doc.Render()fmt.Println("\n--- Attempting to publish (from Draft) ---")doc.Publish() // Draft: Content submitted for review. (Transitions to Moderation)fmt.Println("\n--- Current State:" , doc.GetCurrentStateName(), "---")doc.Render() // Moderation: Rendering content...doc.SetContent("Trying to edit in moderation") // Cannot set contentdoc.Publish() // Moderation: Content approved and published. (Transitions to Published if publish means approve)// If publish from Moderation means 'request publish again', then it might stay or error.// In our example, ModerationState.publish() transitions to PublishedState.fmt.Println("\n--- Current State:" , doc.GetCurrentStateName(), "---")doc.Render() // Published: Displaying content...doc.Publish() // Published: Content is already published.// Let's try the review processfmt.Println("\n--- Resetting to a new document for review flow ---")doc2 := context.NewDocument()doc2.SetContent("Content for review process")doc2.Render()fmt.Println("\n--- Submitting for review (Publish from Draft) ---")doc2.Publish() // Transitions to Moderationfmt.Println("\n--- Current State:" , doc2.GetCurrentStateName(), "---")doc2.Render()fmt.Println("\n--- Reviewing the content (from Moderation) ---")doc2.Review() // Moderation: Reviewing content... Content approved and published. (Transitions to Published)fmt.Println("\n--- Current State:" , doc2.GetCurrentStateName(), "---")doc2.Render()
}
*/
// Main.java (示例用法)
/*
package com.example;import com.example.state.Document;public class Main {public static void main(String[] args) {Document doc = new Document();System.out.println("\n--- Current State: " + doc.getCurrentStateName() + " ---");doc.render(); // Draft: Rendering content...doc.setContent("My awesome first draft!");doc.render();System.out.println("\n--- Attempting to publish (from Draft) ---");doc.publish(); // Draft: Content submitted for review. (Transitions to Moderation)System.out.println("\n--- Current State: " + doc.getCurrentStateName() + " ---");doc.render(); // Moderation: Rendering content...doc.setContent("Trying to edit in moderation"); // Cannot set content// In our ModerationState, publish() means 'approve and publish'.// If we want a separate 'approve' action, we'd add an 'approve()' method to State and ConcreteStates.System.out.println("\n--- Attempting to publish/approve (from Moderation) ---");doc.publish(); // Moderation: Content approved and published. (Transitions to Published)System.out.println("\n--- Current State: " + doc.getCurrentStateName() + " ---");doc.render(); // Published: Displaying content...doc.publish(); // Published: Content is already published.// Let's try the review process more explicitlySystem.out.println("\n--- Resetting to a new document for review flow ---");Document doc2 = new Document();doc2.setContent("Content for review process");doc2.render();System.out.println("\n--- Submitting for review (Publish from Draft) ---");doc2.publish(); // Transitions to ModerationSystem.out.println("\n--- Current State: " + doc2.getCurrentStateName() + " ---");doc2.render();System.out.println("\n--- Reviewing the content (from Moderation) ---");doc2.review(); // Moderation: Reviewing content... Content approved and published. (Transitions to Published)System.out.println("\n--- Current State: " + doc2.getCurrentStateName() + " ---");doc2.render();}
}
*/

7. 状态模式 vs. 策略模式 (State vs. Strategy)

状态模式和策略模式在结构上非常相似(都依赖于组合和委托,将行为封装在独立的对象中),但它们的意图不同:

  • 状态模式 (State Pattern)

    • 意图:允许一个对象在其内部状态改变时改变它的行为。关注点是对象在不同状态下的行为变化
    • 如何改变行为:Context 或 State 对象自身在运行时改变 Context 持有的 State 对象,从而改变行为。
    • 客户端:客户端通常不直接选择状态。状态的改变是内部驱动的(基于操作的结果)或由 Context 自动管理的。
    • 生命周期:State 对象通常代表对象生命周期中的不同阶段或条件。
  • 策略模式 (Strategy Pattern)

    • 意图:定义一系列算法,将它们封装起来,并使它们可以互相替换。关注点是提供多种算法选择,并使它们可互换
    • 如何改变行为:客户端在运行时选择并向 Context 传递一个具体的 Strategy 对象。
    • 客户端:客户端通常知道有多种策略,并主动选择一个策略来配置 Context。
    • 生命周期:Strategy 对象通常代表解决某个问题的不同方法或算法,与对象的内部状态不一定直接关联。

简单来说

  • 状态模式来表示“我是谁”(我的当前状态决定了我的行为)。状态转换通常是预定义的,并且可能由对象内部事件触发。
  • 策略模式来表示“我如何做”(我选择哪种算法来完成任务)。策略通常由客户端在外部设置。

在某些情况下,状态对象本身也可以使用策略模式来处理其内部的某些行为变化,但这已经是模式的组合应用了。

8. 总结

状态模式是一种强大的行为设计模式,它通过将与特定状态相关的行为局部化,并将这些行为委托给代表当前状态的对象,从而使得对象在内部状态改变时能够改变其行为。这不仅消除了大量的条件判断语句,使得代码更加清晰和易于维护,还使得添加新的状态和转换变得更加容易。当你发现一个对象的行为高度依赖于其内部状态,并且这些状态和转换可以用清晰的界限划分时,状态模式会是一个非常好的选择。

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

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

相关文章

面向对象面试题集合

前言 记录面向对象面试题相关内容,方便复习及查漏补缺 题1.简述面向对象?主要特征是什么? 面向对象编程(Object-Oriented Programming,简称OOP)是一种以“对象”为核心的编程范式,通过将现实…

二十一、【用户管理与权限 - 篇三】角色管理:前端角色列表与 CRUD 实现

【用户管理与权限 - 篇三】角色管理:前端角色列表与 CRUD 实现 前言准备工作第一部分:更新 API 服务以包含角色管理第二部分:添加角色管理页面的路由和侧边栏入口第三部分:实现角色列表页面第四部分:实现角色表单对话框组件第五部分:全面测试总结前言 一个完善的权限系统…

Objective-c protocol 练习

题目描述: 请使用 Objective-C 中的 protocol 协议机制,实现一个简易的门禁控制系统。 系统包含两个类: AccessControlSystem —— 门禁系统,用于执行开门操作;Admin —— 实现权限判断逻辑的管理员。 要求如下&am…

科技创新赋能产业创新,双轮驱动助力新疆高质量发展!

在新疆维吾尔自治区成立70周年之际,中国产学研合作促进会于6月14日在乌鲁木齐举办“天山对话:推动新疆科技创新与产业创新”盛会。多位院士、专家、学者及企业代表齐聚一堂,探寻推动新疆科技创新和产业创新的新路径、新动能。活动现场&#x…

C#最佳实践:推荐使用 nameof 而非硬编码名称

C#最佳实践:推荐使用 nameof 而非硬编码名称 在 C# 编程领域,代码的可维护性、健壮性和可读性是衡量程序质量的重要指标。在日常开发中,我们常常会遇到需要引用类型、成员或变量名称的场景,比如在抛出异常时指定错误相关的变量名、在日志记录中标记关键元素名称等。传统的…

vue3 iframe 跨域-通讯

一、基础嵌套方法 直接在 HTML 中使用 <iframe> 标签指定 src 属性&#xff1a; <iframe src"https://目标网址.com" width"800" height"600"></iframe>‌限制‌&#xff1a;若目标网站设置了 X-Frame-Options 响应头&#x…

Iceberg与Hive集成深度

一、Iceberg在Hive中的ACID事务实现与实战 1.1 传统Hive的事务局限性 Hive原生仅支持非事务表&#xff08;Non-ACID&#xff09;&#xff0c;存在以下痛点&#xff1a; 不支持行级更新/删除并发写入时数据一致性无法保证无事务回滚机制历史版本查询需手动实现 1.2 Iceberg为…

深入剖析 Celery:分布式异步任务处理的利器

本文在创作过程中借助 AI 工具辅助资料整理与内容优化。图片来源网络。 文章目录 引言一、Celery 概述1.1 Celery 的定义和作用1.2 Celery 的应用场景 二、Celery 架构分析2.1 Celery 的整体架构2.2 消息中间件&#xff08;Broker&#xff09;2.3 任务队列&#xff08;Task Que…

Flask应用中处理异步事件(后台线程+事件循环)的方法(2)

在上一节&#xff0c;我们讲述了最简单最基础的后线程的建立&#xff0c;现在我们将进行拓展 Flask应用中处理异步事件&#xff08;后台线程事件循环&#xff09;的方法&#xff08;1&#xff09; 在我们的实际应用当中&#xff0c;我们需要定义三个东西 一个多线程的信号旗&am…

C++(面向对象编程)

思维导图 面向对象 1.面向对象思想 概念&#xff1a;面向对象编程&#xff08;OOP&#xff09;是一种以对象为基础的编程范式&#xff0c;强调将数据和操作数据的方法封装在一起。这就是上篇文章讲过的。面向过程是以“怎么解决问题”为核心&#xff0c;而面向对象思想在于“谁…

驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接,

驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接,Error: “The server selected protocol version TLS10 is not accepted by client preferences [TLS13&#xff0c;TLS12]”. ClientConnectionId:d5fd8d69-ae88-4055-9f6d-6e8515224ce2】。 基本上就是…

【三大前端语言之一】交互:JavaScript详解

【三大前端语言之一】交互&#xff1a;JavaScript详解 在学习完HTML和CSS之后&#xff0c;最后一门前端语言——JavaScript&#xff0c;是重中之重。HTML负责页面结构&#xff0c;CSS负责页面样式&#xff0c;而JavaScript则赋予网页“生命”&#xff0c;让网页可以动起来、响…

LangChain面试内容整理-知识点12:检索器(Retriever)接口与实现

在LangChain中,检索器(Retriever)是一个抽象接口,负责根据用户查询从数据源中检索相关文档。可以把Retriever理解为“搜索工具”:给它一个未经结构化的查询文本(如用户问题),它返回一组与之相关的 Document 对象。内部可以基于向量相似度、数据库查询、甚至网络搜索。 …

LLVM前端和优化层

文章目录 LLVM ArchitectueLLVM 前端Lexical Analysis词法分析Syntactic analysis 语法分析Syntactic Analyze语义分析 LLVM 优化层Pass 基础概念Pass 依赖关系Pass API 总结 LLVM Architectue LLVM 前端 LLVM 的前端其实是把源代码也就是 C、C、Python 这些高级语言变为编译器…

工作流和Agent 的区别与联系

工作流和智能体可能让人混淆的地方就是他们都可能有大模型的加持&#xff0c;都可能有工具的加入供大模型调用&#xff0c;本文做一下对比和联系 工作流 (Workflow) 定义&#xff1a; 工作流是一系列预定义、结构化且可重复的步骤或任务&#xff0c;旨在完成特定的业务目标或解…

leetcode--用StringBulider反转字符串单词的巧妙解法

反转字符串中的单词 这道题理想中的操作方式就是先去除前导和尾随空格&#xff0c;之后设一个尾指针&#xff0c;往前检索&#xff0c;扫到一个单词就把这个单词放到字符串的第一个位置。 很明显&#xff0c;java中我们不能直接对字符串进行修改&#xff0c;而我们想实现一个一…

连锁零售行业智慧能源管理解决方案:精准管控,让每一度电创造价值

在连锁超市、便利店等业态中&#xff0c;门店分布广、用能场景复杂、管理成本高是普遍难题。传统能源管理模式依赖人工抄表与分散管理&#xff0c;存在数据滞后、响应效率低、安全隐患难排查等问题。以某全国几千家门店的连锁便利店为例&#xff0c;其面临的挑战包括&#xff1…

在 PostgreSQL 中实现 `lck`, `special`, `item` 与 `org_id` 或 `user_id` 组合唯一的约束

在 PostgreSQL 中实现 lck, special, item 与 org_id 或 user_id 组合唯一的约束 要实现 lck, special, item 这三个字段必须与 org_id 或 user_id 中的一个&#xff08;但不能同时&#xff09;组合唯一的约束&#xff0c;你需要创建以下约束&#xff1a; 方案1&#xff1a;使…

g++ a.cpp -o a ‘pkg-config --cflags --libs opencv4‘/usr/bin/ld: 找不到 没有那个文件或目录

这个错误表明 pkg-config 命令没有正确执行&#xff0c;导致编译器无法找到 OpenCV 的库文件和头文件路径。pkg-config 是一个工具&#xff0c;用于查询已安装库的编译和链接选项。如果 pkg-config 无法找到 OpenCV 的配置文件&#xff0c;就会导致这个错误。 以下是解决这个问…

定制平板在智能家居中能做些什么?全面解析其核心功能

大家有没有发现&#xff0c;现在智能家居越来越普及了&#xff0c;很多家庭都在逐步升级自己的居住体验。而在这一过程中&#xff0c;一种设备正悄悄地取代我们以前常用的开关面板和手机APP&#xff0c;成为整个家庭智能控制的核心&#xff0c;这就是——定制平板。 它可不是我…