【HF設計模式】03-裝飾者模式

聲明:僅為個人學習總結,還請批判性查看,如有不同觀點,歡迎交流。

摘要

《Head First設計模式》第3章筆記:結合示例應用和代碼,介紹裝飾者模式,包括遇到的問題、遵循的 OO 原則、達到的效果。

目錄

  • 摘要
  • 1 示例應用
  • 2 遇到問題
  • 3 引入設計模式
    • 3.1 OO 原則:開閉原則
    • 3.2 完善“裝飾者”設計
    • 3.3 完善“被裝飾對象”設計
    • 3.4 裝飾者模式定義
  • 4 示例代碼
    • 4.1 Java 示例
    • 4.2 C++11 示例
  • 5 設計工具箱
    • 5.1 OO 基礎
    • 5.2 OO 原則
    • 5.3 OO 模式
  • 參考


1 示例應用

示例應用是星巴茲(Starbuzz)咖啡店的訂單系統。

最開始,店里只提供黑咖啡,系統類圖如下:

?abstract?
Beverage
description
getDescription()
cost()
HouseBlend
cost()
DarkRoast
cost()
Decaf
cost()
Espresso
cost()
  1. Beverage(飲料),是一個抽象類,咖啡店售賣的所有飲料都繼承這個類
    • 定義 description 實例變量,用于保存飲料描述;
    • 定義 getDescription() 方法,用于獲取 description
    • 聲明抽象的 cost() 方法,用于獲取飲料價格,由子類負責實現。
  2. Beverage 的子類:HouseBlend(混合咖啡)、DarkRoast(深度烘焙)、Decaf(低咖啡因)、Espresso(濃縮咖啡)
    • 實現 cost() 方法,計算具體飲料價格;
    • 將具體飲料描述賦值給 description,例如“最優深度烘焙”。

后來,咖啡店提供了牛奶等調味配料,每份調料會收取一點費用。

隨著調料和飲料的種類不斷增加,現在系統急需進行更新。下面是當前的系統類圖:

類爆炸

類爆炸!

調料的種類包括 Steamed Milk(蒸牛奶)、Soy(豆奶)、Mocha(摩卡,也被稱為巧克力)、Whip(奶油泡沫)等,系統為每個“飲料和調料的組合”都創建了類。

我們來近距離觀察一下 DarkRoastWithMochaAndWhip(深焙摩卡奶泡咖啡)類的定義:

public class DarkRoastWithMochaAndWhip extends Beverage {public DarkRoastWithMochaAndWhip() {description = "Dark Roast with Mocha and Whip";}public double cost() {return 0.99 + 0.2 + 0.1; // 基礎飲料價格 + 各種調料價格}
}

訂購一杯“深焙摩卡奶泡咖啡”的方式如下:

Beverage beverage = new DarkRoastWithMochaAndWhip();
System.out.println(beverage.getDescription() + " $" + beverage.cost());

思考題:

很明顯,星巴茲為自己制造了一個維護噩夢。請思考如下問題:【參考答案在第 20 行】
It’s pretty obvious that Starbuzz has created a maintenance nightmare for themselves. The Brain Power exercises:1. 在牛奶價格上漲時,系統需要怎樣調整? What happens when the price of milk goes up? 
2. 當新增一種焦糖調料時,系統需要怎樣調整? What do they do when they add a new caramel topping?
3. 系統設計明顯違反了下面哪些設計原則?分離變與不變、針對接口編程、優先使用組合、松耦合設計(下文 “5.2.2 原則回顧” 部分有詳細些的原則介紹)Which of the design principles that we’ve covered so far are they violating? (Hint: they’re violating two of them in a big way!)書上沒有提供答案,以下是我的理解,不知和你的是否一樣。
1. 對于所有帶牛奶調料的飲料類,都需要修改其 cost() 方法;(如果價格在每個類中硬編碼)
2. 對于所有可搭配焦糖調料的飲料,都需要創建新的類;(類就是這樣爆炸的)
3. 違反的原則1)分離變與不變:變化的方面包括調料價格、調料類型、為基礎飲料添加不同的調料2)優先使用組合:系統完全采用繼承方式進行設計

2 遇到問題

為了解決“類爆炸”,系統有了第1個版本的新設計,類圖如下:

Beverage
String description
boolean milk
boolean soy
boolean mocha
boolean whip
getDescription()
cost()
hasMilk()
setMilk()
hasSoy()
setSoy()
hasMocha()
setMocha()
hasWhip()
setWhip()
HouseBlend
cost()
DarkRoast
cost()
Decaf
cost()
Espresso
cost()

Beverage 類中:

  1. 定義 milksoymochawhip 實例變量,代表是否添加相應調料;
  2. 定義 has調料()set調料() 方法,用于獲取和設置調料的布爾值;
  3. cost() 不再是抽象方法:
    • 在超類 cost() 中,計算當前飲料實例中所有調料的價格;
    • 在子類 cost() 中,通過超類 cost() 獲得調料價格,再加上子類的基礎飲料價格,就可以計算出總價格。

超類 Beveragecost() 方法實現如下:

public class Beverage {public double cost() {double condimentCost = 0.0; // 調料(condiment)總價格if (hasMilk())  { condimentCost += 0.10; }if (hasSoy())   { condimentCost += 0.15; }if (hasMocha()) { condimentCost += 0.20; }if (hasWhip())  { condimentCost += 0.10; }return condimentCost;}
}

子類 DarkRoastcost() 方法實現如下:

public class DarkRoast extends Beverage {public double cost() {return 0.99 + super.cost(); // 基礎飲料價格 + 所有調料價格}
}

訂購一杯“深焙摩卡奶泡咖啡”的方式如下:

Beverage beverage = new DarkRoast();
beverage.setMocha(true);
beverage.setWhip(true);
System.out.println(beverage.getDescription() + " $" + beverage.cost());

在第1版的新設計中,一共只有5個類,已經解決了“類爆炸”的問題。

思考題:

哪些需求或其它因素的改變會影響這個設計?(多選)【答案在第 20 行】
What requirements or other factors might change that will impact this design? (Choose all that apply.)A. 任意一種調料的價格改變,都需要修改超類代碼。Price changes for condiments will force us to alter existing code.
B. 每次增加新的調料時,都需要在超類中添加新的方法,并修改其 cost() 方法。New condiments will force us to add new methods and alter the cost method in the superclass.
C. 未來可能會有某些飲料,并不適合某些調料,但是在這些飲料子類中,仍然要繼承超類中的“那些不適合調料的”相關方法。We may have new beverages. For some of these beverages (iced tea?), the condiments may not be appropriate, yet the Tea subclass will still inherit methods like hasWhip().
D. 如果顧客想添加雙份摩卡,該怎么辦?What if a customer wants a double mocha?答案:A B C D

3 引入設計模式

3.1 OO 原則:開閉原則

設計原則(Design Principle)
類應該對擴展開放,對修改關閉。
Classes should be open for extension, but closed for modification.

  • 對修改關閉
    • 抱歉,我們的類已經關閉,不能被修改。
      Sorry, our classes must remain closed to modification.
    • 我們花了許多時間得到了正確的代碼,解決了所有的bug,所以不能讓你修改現有代碼。如果你不喜歡,可以找經理談。
      We spent a lot of time getting this code correct and bug free, so we can’t let you alter the existing code. If you don’t like it, you can speak to the manager.
  • 對擴展開放
    • 歡迎擴展我們的類,加入任何你想要的新行為。
      Feel free to extend our classes with any new behavior you like.
    • 如果你的要求或需求有所改變(我們知道這一定會發生),那就開始吧,創建你自己的擴展。
      If your needs or requirements change (and we know they will), just go ahead and make your own extensions.

“開閉原則”的目標是允許類能夠輕松地擴展,在不修改現有代碼的情況下,就可以加入新的行為。
Our goal is to allow classes to be easily extended to incorporate new behavior without modifying existing code.
這樣的設計具有彈性,可以應對改變;并且足夠靈活,能夠添加新的功能以滿足不斷變化的需求。
Designs that are resilient to change and flexible enough to take on new functionality to meet changing requirements.

結合“深焙摩卡奶泡咖啡”,我們來了解一種符合“開閉原則”的設計方式:

DarkRoast
description
getDescription()
cost()
DarkRoastWithMocha
DarkRoast wrappedObj
  1. 定義 DarkRoastDarkRoastWithMocha 兩個類(暫時不考慮其它類)
  2. DarkRoastWithMocha 組合 DarkRoast,即 HAS-A(有一個)DarkRoast 類型對象 wrappedObj
    • DarkRoastWithMocha 叫做 “裝飾者”;
    • DarkRoast 類型的實例變量 wrappedObj 叫做 “被裝飾對象”;
    • “裝飾者” 可以將行為委托給 “被裝飾對象”,并且在 “被裝飾對象” 行為的基礎上,“裝飾” 新的行為。
  3. DarkRoastWithMocha 繼承 DarkRoast,即 IS-A(是一個)DarkRoast 類型
    • “裝飾者” 和 “被裝飾對象” 具有相同的類型;
    • 當 “裝飾者” 是 “被裝飾對象” 類型(DarkRoast)變量時:
      • 使用變量的用戶并不知道 “裝飾者” 類型的存在(“裝飾者” 對用戶透明);
      • 用戶可以像使用 “被裝飾對象” 一樣,使用 “裝飾者”;
      • 用戶可以將 “裝飾者” 當做 “被裝飾對象” 再次進行 “裝飾” ,即 “遞歸裝飾”。

這樣,就可以在不改變 “被裝飾對象” 的情況下,通過 “裝飾者” 對 “被裝飾對象” 進行擴展。即 “對修改關閉,對擴展開放”。

下面結合示例代碼,了解具體的實現過程。

“被裝飾對象”類的定義:

public class DarkRoast {String description;public DarkRoast() { description = "Dark Roast"; }public String getDescription() { return description; }public double cost() { return 0.99; }
}

“裝飾者”類的定義:

public class DarkRoastWithMocha extends DarkRoast {DarkRoast wrappedObj; // “被裝飾對象”// 創建“裝飾者”時,需要指定“被裝飾對象”public DarkRoastWithMocha(DarkRoast darkRoast) { wrappedObj = darkRoast; }// “裝飾者”將行為委托給“被裝飾對象”,并且在“被裝飾對象”行為的基礎上,“裝飾”新的行為public String getDescription() {return wrappedObj.getDescription() + ", Mocha";}public double cost() {return wrappedObj.cost() + 0.2;}
}

訂購一杯“深焙摩卡奶泡咖啡”的方式如下:

DarkRoast

// 生成一份“深焙”
DarkRoast darkRoast = new DarkRoast();
System.out.println(darkRoast.getDescription()  // "Dark Roast"+ " $" + darkRoast.cost());   // 0.99// 為“深焙”(被裝飾對象)添加摩卡裝飾,生成“深焙摩卡”(既是裝飾者,又可以作為被裝飾對象)
darkRoast = new DarkRoastWithMocha(darkRoast);
System.out.println(darkRoast.getDescription()  // "Dark Roast, Mocha"+ " $" + darkRoast.cost());   // 0.99 + 0.2 = 1.19// 如果有奶泡裝飾者 DarkRoastWithWhip 類,我們就可以
// 為“深焙摩卡”(被裝飾對象)添加奶泡裝飾,生成“深焙摩卡奶泡”(遞歸裝飾)
// darkRoast = new DarkRoastWithWhip(darkRoast);
// System.out.println(darkRoast.getDescription()  // "Dark Roast, Mocha, Whip"
//                  + " $" + darkRoast.cost());   // 1.19 + 0.1 = 1.29// 目前還沒有奶泡裝飾者,但是我們可以
// 為“深焙摩卡”(被裝飾對象)繼續添加摩卡裝飾,生成“深焙雙摩卡”(重復裝飾)
darkRoast = new DarkRoastWithMocha(darkRoast);
System.out.println(darkRoast.getDescription()  // "Dark Roast, Mocha, Mocha"+ " $" + darkRoast.cost());   // 1.19 + 0.2 = 1.39

上述示例在不改變 DarkRoast 對象的情況下,通過 DarkRoastWithMocha,以動態(運行時)、透明(類型不變)的方式,擴展了對象的 getDescription()cost() 行為。

3.2 完善“裝飾者”設計

在訂單系統中,要采用上述類圖結構,我們還需要進行一些完善。

首先,類圖中不能只有摩卡,還要添加其它調料:

  1. 由于調料的價格和類型可能會發生改變,所以需要對每種調料進行封裝;
  2. 遵循“針對接口編程”原則,需要為所有調料定義統一的接口。

完善“裝飾者”設計后的類圖如下:

DarkRoast
description
getDescription()
cost()
?abstract?
CondimentDecorator
DarkRoast wrappedObj
getDescription()
cost()
Milk
getDescription()
cost()
Mocha
getDescription()
cost()
Soy
getDescription()
cost()
Whip
getDescription()
cost()

關于 “裝飾者” 部分:

  1. 調料裝飾者抽象類 CondimentDecorator
    • 繼承 DarkRoast,并引用 DarkRoast 類型的 “被裝飾對象” wrappedObj
    • 聲明抽象的 getDescription()cost() 方法,由子類負責實現。
  2. 調料裝飾者類 MilkMochaSoyWhip
    • 繼承 CondimentDecorator,實現 getDescription()cost() 方法,
      在方法內部,委托 “被裝飾對象” 執行行為,并在 “被裝飾對象” 行為的基礎上,“裝飾” 新的行為(補充描述,增加價格)。

3.3 完善“被裝飾對象”設計

接下來,類圖中也不能只有深度烘焙,還要添加其它飲料:
和調料部分的設計一樣,也將每種飲料封裝在各自的類中,并定義統一的接口。

現在,我們獲得了完整的、更新后的系統類圖:

?abstract?
Beverage
description
getDescription()
cost()
?abstract?
CondimentDecorator
Beverage beverage
getDescription()
HouseBlend
cost()
DarkRoast
cost()
Decaf
cost()
Espresso
cost()
Milk
getDescription()
cost()
Mocha
getDescription()
cost()
Soy
getDescription()
cost()
Whip
getDescription()
cost()

關于 “被裝飾對象” 部分:

  1. 飲料抽象類 Beverage
    • 定義 getDescription() 方法,用于獲取飲料描述 description
    • 聲明抽象的 cost() 方法,由子類負責實現,包括飲料子類和調料子類。
  2. 飲料具體類 HouseBlendDarkRoastDecafEspresso
    • 繼承 Beverage,為 description 賦值,實現 cost() 方法。

采用最新的設計之后,第1版設計 存在的問題 已經得到解決,系統 可以應對改變并易于擴展。

3.4 裝飾者模式定義

剛剛我們使用裝飾者模式重新設計了咖啡店的訂單系統,裝飾者模式的正式定義如下:

裝飾者模式(Decorator Pattern)
動態地給一個對象添加一些額外的職責。就增加功能來說,裝飾者模式相比生成子類更為靈活。
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

增加功能的方式:生成子類 vs 裝飾者模式

序號生成子類裝飾者模式裝飾者模式【優缺點】
1給整個類添加功能給某個對象添加功能優點:讓“被裝飾對象”的類保持簡單;
缺點:系統會產生許多看上去相似的“裝飾者”類,不便于學習和調試;
缺點:在使用時,不僅要實例化“被裝飾對象”,還要實例化各種需要的“裝飾者”
2編譯時靜態添加功能運行時動態添加功能優點:可以在運行時動態添加功能、組合多種不同功能、重復添加一種功能
3直接執行功能委托“被裝飾對象”
執行功能
優點:因為“裝飾者”與“被裝飾對象”具有相同的“超類型”,所以可以對用戶透明;
缺點:不適用于依賴具體類型的代碼,例如“被裝飾對象”定義了某個方法 f()
而“超類型”并沒有定義 f(),則不能通過“裝飾者”執行 f()

將訂單系統中的類圖抽象化,就可以得到設計模式中的類圖:

?abstract?
Component
operation()
?abstract?
Decorator
Component wrappedObj
operation() : CallOperationOfWrappedObj
ConcreteComponent
operation()
ConcreteDecoratorA
operation() : CallAddedBehaviorAndOperation
addedBehavior()
ConcreteDecoratorB
newState
operation()
newBehavior()
  1. Component 抽象類
    • 為 “被裝飾對象” 定義統一的接口,聲明抽象方法 operation()
  2. ConcreteComponent “被裝飾對象” 類
    • 繼承 Component,實現 operation() 方法;
    • “被裝飾對象” 可以單獨使用;也可以被 “裝飾者” 加上新行為(裝飾,wrapped)后再使用。
  3. Decorator “裝飾者” 抽象類
    • 繼承 Component,即 IS-A(是一個)Component,和 “被裝飾對象” 具有相同的超類型;
      • 繼承的目的是:達到類型匹配(這樣 “裝飾者” 可以充當 “被裝飾對象”),而不是復用超類的行為。
    • 組合 Component,即 HAS-A(有一個)Component 類型的 “被裝飾對象” wrappedObj
      • 組合的目的是:將行為委托給 “被裝飾對象”。
    • 實現 operation() 方法,將行為委托給 “被裝飾對象” wrappedObj
      注:由于當前 mermaid 類圖不支持 note,所以方法(method)的返回類型都被用于作為注釋,如 CallOperationOfWrappedObj
  4. ConcreteDecoratorA “裝飾者” 具體類
    • 繼承 Decorator,和 “被裝飾對象” 具有相同的超類型;
    • 定義 addedBehavior() 方法,實現向 “被裝飾對象” 添加的新行為;
    • 實現 operation() 方法,在委托 “被裝飾對象” 執行 operation() 的基礎上,附加 addedBehavior() 行為;
      • 對于附加行為,即可以放在 “被裝飾對象” 行為的前面,也可以放在其后面。
  5. ConcreteDecoratorB “裝飾者” 具體類
    • 繼承 Decorator,和 “被裝飾對象” 具有相同的超類型;
    • 定義 newState 實例變量和 newBehavior() 方法
      • 為 “當前類的特定功能” 添加新狀態和新行為;
      • 例如,“被裝飾對象” 是文本視圖 TextView,“裝飾者” 是滾動條 ScrollDecorator
        為了實現視圖滾動功能,就會在 ScrollDecorator 中定義滾動狀態 scrollPosition 和滾動行為 ScrollTo()
    • 實現 operation() 方法,在委托 “被裝飾對象” 執行 operation() 的基礎上,根據需要附加額外行為。

注:在設計模式中,重要的是模式的核心思想;在實際設計時,選擇定義為接口還是抽象類,以及是否提供默認的方法實現等,可以根據具體的情境來決定。

延伸閱讀:《設計模式:可復用面向對象軟件的基礎》 4.4 Decorator(裝飾)— 對象結構型模式 [P132-139]

4 示例代碼

4.1 Java 示例

抽象飲料和調料類定義:

// Beverage.java
public abstract class Beverage {String description = "Unknown Beverage";public String getDescription() { return description;}public abstract double cost();
}// CondimentDecorator.java
public abstract class CondimentDecorator extends Beverage {Beverage beverage;public abstract String getDescription();
}

具體飲料類定義:

// DarkRoast.java
public class DarkRoast extends Beverage {public DarkRoast() { description = "Dark Roast Coffee"; }public double cost() { return .99; }
}

具體調料類定義:

// Mocha.java
public class Mocha extends CondimentDecorator {public Mocha(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Mocha";}public double cost() {return .20 + beverage.cost();}
}// Whip.java
public class Whip extends CondimentDecorator {public Whip(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Whip";}public double cost() {return .10 + beverage.cost();}
}

測試代碼:

// StarbuzzCoffee.java
public class StarbuzzCoffee {public static void main(String args[]) {Beverage beverage = new DarkRoast();System.out.println(beverage.getDescription() + " $" + beverage.cost());beverage = new Mocha(beverage);System.out.println(beverage.getDescription() + " $" + beverage.cost());beverage = new Mocha(beverage);beverage = new Whip(beverage);System.out.println(beverage.getDescription() + " $" + beverage.cost());}
}

4.2 C++11 示例

抽象飲料和調料類定義:

struct Beverage {virtual ~Beverage() = default;virtual std::string getDescription() const { return description; }virtual double cost() = 0;protected:std::string description = "Unknown Beverage";
};struct CondimentDecorator : public Beverage {virtual ~CondimentDecorator() = default;virtual std::string getDescription() const override { return beverage->getDescription(); }virtual double cost() override { return beverage->cost(); }protected:CondimentDecorator(std::shared_ptr<Beverage> beverage) : beverage(beverage) {}std::shared_ptr<Beverage> beverage;
};

具體飲料類定義:

struct DarkRoast : public Beverage {DarkRoast() { description = "Dark Roast Coffee"; }double cost() override { return .99; }
};

具體調料類定義:

struct Mocha : public CondimentDecorator {Mocha(std::shared_ptr<Beverage> beverage) : CondimentDecorator(beverage) {}std::string getDescription() const override {return CondimentDecorator::getDescription() + ", Mocha";}double cost() override { return CondimentDecorator::cost() + .20; }
};struct Whip : public CondimentDecorator {Whip(std::shared_ptr<Beverage> beverage) : CondimentDecorator(beverage) {}std::string getDescription() const override {return CondimentDecorator::getDescription() + ", Whip";}double cost() override { return CondimentDecorator::cost() + .10; }
};

測試代碼:

#include <iostream>
#include <memory>
#include <string>// 在這里添加相關接口和類的定義int main() {std::shared_ptr<Beverage> beverage = std::make_shared<DarkRoast>();std::cout << beverage->getDescription() << " $" << beverage->cost() << "\n";beverage = std::make_shared<Mocha>(beverage);std::cout << beverage->getDescription() << " $" << beverage->cost() << "\n";beverage = std::make_shared<Mocha>(beverage);beverage = std::make_shared<Whip>(beverage);std::cout << beverage->getDescription() << " $" << beverage->cost() << "\n";
}

5 設計工具箱

5.1 OO 基礎

OO 基礎回顧

  1. 抽象(Abstraction)
  2. 封裝(Encapsulation)
  3. 繼承(Inheritance)
  4. 多態(Polymorphism)

5.2 OO 原則

5.2.1 新原則

類應該對擴展開放,對修改關閉。
Classes should be open for extension, but closed for modification.

5.2.2 原則回顧

  1. 封裝變化。
    Encapsulate what varies.
  2. 針對接口編程,而不是針對實現編程。
    Program to interfaces, not implementations.
  3. 優先使用組合,而不是繼承。
    Favor composition over inheritance.
  4. 盡量做到交互對象之間的松耦合設計。
    Strive for loosely coupled designs between objects that interact.

5.3 OO 模式

5.3.1 新模式

裝飾者模式(Decorator Pattern)

  • 裝飾者模式動態地給一個對象添加一些額外的職責。
    The Decorator Pattern attaches additional responsibilities to an object dynamically.
  • 就增加功能來說,裝飾者模式相比生成子類更為靈活。
    Decorators provide a flexible alternative to subclassing for extending functionality.

5.3.2 模式回顧

  1. 策略模式(Strategy Pattern)
    • 定義一個算法家族,把其中的算法分別封裝起來,使得它們之間可以互相替換。
      Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable.
    • 讓算法的變化獨立于使用算法的客戶。
      Strategy lets the algorithm vary independently from clients that use it.
  2. 觀察者模式(Observer Pattern)
    • 定義對象之間的一對多依賴,
      The Observer Pattern defines a one-to-many dependency between objects
    • 這樣一來,當一個對象改變狀態時,它的所有依賴者都會被通知并自動更新。
      so that when one object changes state, all of its dependents are notified and updated automatically.

參考

  1. [美]弗里曼、羅布森著,UMLChina譯.Head First設計模式.中國電力出版社.2022.2
  2. [美]伽瑪等著,李英軍等譯.設計模式:可復用面向對象軟件的基礎.機械工業出版社.2019.3
  3. wickedlysmart: Head First設計模式 Java 源碼

Hi, I’m the ENDing, nice to meet you here! Hope this article has been helpful.

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/62961.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/62961.shtml
英文地址,請注明出處:http://en.pswp.cn/web/62961.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Mysql數據庫中,什么情況下設置了索引但無法使用?

在MySQL數據庫中&#xff0c;即使已經正確設置了索引&#xff0c;但在某些情況下索引可能無法被使用。 以下是一些常見的情況&#xff1a; 1. 數據分布不均勻 當某個列的數據分布非常不均勻時&#xff0c;索引可能無法有效地過濾掉大部分的數據&#xff0c;導致索引失效。 …

秒殺業務中的庫存扣減為什么不加分布式鎖?

前言 說到秒殺業務的庫存扣減&#xff0c;就還是得先確認我們的扣減基本方案。 秒殺場景的庫存扣減方案 一般的做法是&#xff0c;先在Redis中做扣減&#xff0c;然后發送一個MQ消息&#xff0c;消費者在接到消息之后做數據庫中庫存的真正扣減及業務邏輯操作。 如何解決數據…

ChatGPT生成測試用例的最佳實踐(一)

前面介紹的案例主要展示了ChatGPT在功能、安全和性能測試用例生成方面的應用和成果。通過ChatGPT生成測試用例&#xff0c;測試團隊不僅可以提升工作效率&#xff0c;還可以加快測試工作的速度&#xff0c;盡早發現被測系統中的問題。問題及早發現有助于提高軟件的質量和用戶滿…

基于Redis實現令牌桶算法

基于Redis實現令牌桶算法 令牌桶算法算法流程圖優點缺點 實現其它限流算法 令牌桶算法 令牌桶是一種用于分組交換和電信網絡的算法。它可用于檢查數據包形式的數據傳輸是否符合定義的帶寬和突發性限制&#xff08;流量不均勻或變化的衡量標準&#xff09;。它還可以用作調度算…

操作系統(8)死鎖

一、概念 死鎖是指在一個進程集合中的每個進程都在等待只能由該集合中的其他進程才能引起的事件&#xff0c;而無限期地僵持下去的局面。在多任務環境中&#xff0c;由于資源分配不當&#xff0c;導致兩個或多個進程在等待對方釋放資源時陷入無限等待的狀態&#xff0c;這就是死…

Micropython 擴展C模塊<HelloWorld>

開發環境 MCU&#xff1a;Pico1&#xff08;無wifi版&#xff09;使用固件&#xff1a;自編譯版本開發環境&#xff1a;MacBook Pro Sonoma 14.5開發工具&#xff1a;Thonny 4.1.6開發語言&#xff1a;MicroPython 1.24 執行示例 在github上獲取micropython&#xff0c;我使…

并查集基礎

abstract 并查集&#xff08;Union-Find Set&#xff09;是一種數據結構&#xff0c;主要用于處理動態連通性問題&#xff08;Dynamic Connectivity Problem&#xff09;&#xff0c;例如在圖論中判斷兩點是否屬于同一個連通分量&#xff0c;以及動態地合并集合。 它廣泛應用…

CloudberryDB(一)安裝部署多節點分布式數據庫集群

CloudberryDB&#xff1a; 一個 Greenplum Database 分布式數據庫開源版本的衍生項目&#xff0c; 針對開源 Greenplum Database 優化的地方&#xff0c; CloudberryDB制定了路線圖&#xff08;https://github.com/orgs/cloudberrydb/discussions/369&#xff09;并在逐步改…

解決Logitech G hub 無法進入一直轉圈的方案(2024.12)

如果你不是最新版本無法加載嘗試以下方案&#xff1a;刪除AppData 文件夾下的logihub文件夾 具體路徑&#xff1a;用戶名根據實際你的請情況修改 C:\Users\Administrator\AppData\Local 如果你有通過lua編譯腳本&#xff0c;記得備份&#xff01;&#xff01; ↓如果你是最新…

數據庫范式與反范式化:如何權衡性能與數據一致性

目錄 1. 什么是數據庫范式&#xff08;Normalization&#xff09;&#xff1f;第一范式&#xff08;1NF&#xff09;第二范式&#xff08;2NF&#xff09;第三范式&#xff08;3NF&#xff09; 2. 什么是反范式化&#xff08;Denormalization&#xff09;&#xff1f;3. 反范式…

Nmap使用總結

0X00 背景 nmap是測試中常用的網絡探測工具&#xff0c;但是這回簡單的操作&#xff0c;一直了解不深入&#xff0c;現在深入的了解和學習一下。 在文章結構上&#xff0c;我把平時常用的內容提前了&#xff0c;以便再次查閱的時候&#xff0c;比較方便。 0X01 安裝 nmap可…

【記錄49】vue2 vue-office在線預覽 docx、pdf、excel文檔

vue2 在線預覽 docx、pdf、excel文檔 docx npm install vue-office/docx vue-demi0.14.6 指定版本 npm install vue-office/docx vue-demi <template><VueOfficeDocx :src"pdf" style"height: 100vh;" rendere"rendereHandler" error&…

MVC模式的理解和實踐

在軟件開發中&#xff0c;MVC&#xff08;Model-View-Controller&#xff09;模式是一種經典的設計模式&#xff0c;特別適用于構建用戶界面復雜的Web應用程序。MVC通過將應用程序的業務邏輯、數據顯示和用戶交互分離&#xff0c;使代碼結構更加清晰&#xff0c;易于維護和擴展…

[A-22]ARMv8/v9-SMMU多級頁表架構

ver0.1 [看前序文章有驚喜,關注W\X\G=Z+H=“浩瀚架構師”,可以解鎖全部文章] 前言 前文我們對SMMU的系統架構和基本功能做了簡要的介紹,現在大家大致對SMMU在基于ARM體系的系統架構下的總線位置和產品形態有了基本的了解。這里我們還是簡單做個前情回顧,從總線架構角度看…

【UE5 “RuntimeLoadFbx”插件】運行時加載FBX模型

前言 為了解決在Runtime時能夠直接根據FBX模型路徑直接加載FBX的問題&#xff0c;推薦一款名為“RuntimeLoadFBX”的插件。 用法 插件用法如下&#xff0c;只需要指定fbx的地址就可以在場景中生成Actor模型 通過指定輸入參數“Cal Collision”來設置FBX模型的碰撞 還可以通過…

(11)(3.1) ESC接地和接線注意事項

文章目錄 前言 1 歸納 2 電容式 3 電阻 前言 ESC 接地問題由 3 種形式的 ESC 信號/耦合問題組成&#xff0c;即電阻、電容和電感。在制造飛機時&#xff0c;應考慮這三個因素。 1 歸納 這是電流突然變化導致系統中出現大電壓尖峰的趨勢。電源系統中的電感主要是由 ESC 和…

精品基于Python實現的微信小程序校園導航系統-微信小程序

[含文檔PPT源碼等] [包運行成功永久免費答疑輔導] 《django微信小程序校園導航系統》該項目采用技術Python的django框架、mysql數據庫 &#xff0c;項目含有源碼、文檔、PPT、配套開發軟件、軟件安裝教程、項目發布教程、核心代碼介紹視頻等 軟件開發環境及開發工具&#xf…

Rstudio-server的安裝、配置、維護

一、安裝Rstudio-server (1)安裝R語言&#xff1a; sudo apt install r-base # 如果沒有管理員權限無法操作 # 這樣裝上R默認在/usr/bin/R其實基本上的流程都可以參考posit的官網&#xff08;也就是Rstudio的官網&#xff09;&#xff1a; https://posit.co/download/rstudio…

Python序列的應用(八):元組、字典

前言&#xff1a;在Python編程語言中&#xff0c;序列是一種非常重要的數據結構&#xff0c;它允許我們存儲和操作有序的數據集合。在前幾期的內容中&#xff0c;我們已經探討了列表&#xff08;List&#xff09;和集合&#xff08;Set&#xff09;這兩種序列的應用&#xff0c…

OpenCV 功能函數介紹

一&#xff0c; 二值化函數 功能&#xff1a; 用于對圖像進行二值化處理 參數&#xff1a; cv2.threshold(輸入你的圖像所對應的灰度圖&#xff0c; 閾值&#xff1a;是浮點還是整數取決予圖像的數據類型 最大值;高于閾值的像素值&#xff0c; 閾值類型&#xff1a;cv2.THR…