設(shè)計(jì)模式反模式:避免濫用設(shè)計(jì)模式的10個(gè)常見誤區(qū)

2024-12-27 14:39 更新

Hello,大家好,我是V哥。很多文章都在介紹設(shè)計(jì)模式怎么用,講解設(shè)計(jì)模式的原理等等,設(shè)計(jì)模式的思想是編程中的精髓,用好了可以讓代碼結(jié)構(gòu)利于維護(hù)和擴(kuò)展,同時(shí)代碼風(fēng)格也更加優(yōu)雅,V 哥也寫過這樣一篇文章,但很少有人從反模式的角度來講一講,過度濫用設(shè)計(jì)模式將給項(xiàng)目帶來災(zāi)難。

設(shè)計(jì)模式反模式(Anti-Pattern)是指那些表面上看起來像是設(shè)計(jì)模式,但實(shí)際上會(huì)導(dǎo)致軟件設(shè)計(jì)問題的做法。這些做法可能會(huì)導(dǎo)致代碼難以維護(hù)、擴(kuò)展或測(cè)試。

在實(shí)際開發(fā)中,設(shè)計(jì)模式的誤用可能會(huì)導(dǎo)致軟件設(shè)計(jì)的問題,下面 V 哥整理了10種常見的設(shè)計(jì)模式誤用案例,來看一下:

  1. 濫用工廠模式
    • 誤用:創(chuàng)建一個(gè)萬能工廠類來生成所有類型的實(shí)例,導(dǎo)致工廠類變得龐大且難以維護(hù)。
    • 正確使用:應(yīng)該為不同類型的對(duì)象創(chuàng)建專門的工廠類,遵循單一職責(zé)原則。
  2. 過度使用單例模式
    • 誤用:在不應(yīng)該使用單例模式的場(chǎng)景中使用它,例如在需要頻繁創(chuàng)建和銷毀對(duì)象的系統(tǒng)中。
    • 正確使用:?jiǎn)卫J竭m用于全局配置或共享資源,但應(yīng)該謹(jǐn)慎使用以避免全局狀態(tài)的問題。
  3. 錯(cuò)誤使用裝飾器模式
    • 誤用:在不支持?jǐn)U展的類上使用裝飾器模式,或者裝飾器類與原始類耦合太緊。
    • 正確使用:確保裝飾器模式用于可擴(kuò)展的對(duì)象,并且裝飾器應(yīng)該只添加額外的行為,不改變?cè)袑?duì)象的行為。
  4. 不恰當(dāng)?shù)睦^承使用
    • 誤用:通過繼承來實(shí)現(xiàn)代碼復(fù)用,而不是基于“是一個(gè)(is-a)”的關(guān)系。
    • 正確使用:應(yīng)該使用組合而不是繼承來復(fù)用代碼,繼承應(yīng)該僅用于表示類型層次結(jié)構(gòu)。
  5. 濫用觀察者模式
    • 誤用:在不需要觀察者模式的場(chǎng)景中使用它,或者將過多的邏輯放入觀察者中。
    • 正確使用:觀察者模式應(yīng)該用于實(shí)現(xiàn)發(fā)布-訂閱機(jī)制,而不是作為通信的唯一手段。
  6. 錯(cuò)誤使用命令模式
    • 誤用:將簡(jiǎn)單的操作也封裝成命令對(duì)象,導(dǎo)致系統(tǒng)復(fù)雜度增加。
    • 正確使用:命令模式適用于需要記錄、排隊(duì)、撤銷或日志記錄的操作。
  7. 不恰當(dāng)?shù)臓顟B(tài)模式使用
    • 誤用:將狀態(tài)模式用于簡(jiǎn)單的狀態(tài)變化,或者狀態(tài)轉(zhuǎn)換邏輯過于復(fù)雜。
    • 正確使用:狀態(tài)模式應(yīng)該用于對(duì)象狀態(tài)復(fù)雜且狀態(tài)變化頻繁的場(chǎng)景。
  8. 錯(cuò)誤使用代理模式
    • 誤用:在不需要代理的場(chǎng)景中使用代理模式,例如直接訪問遠(yuǎn)程服務(wù)而不是使用本地代理。
    • 正確使用:代理模式應(yīng)該用于控制對(duì)對(duì)象的訪問,提供額外的安全或控制邏輯。
  9. 濫用策略模式
    • 誤用:將策略模式用于只有一個(gè)或很少幾個(gè)策略的場(chǎng)景,或者策略類與上下文類耦合太緊。
    • 正確使用:策略模式應(yīng)該用于在運(yùn)行時(shí)需要切換算法或行為的場(chǎng)景。
  10. 不恰當(dāng)?shù)慕M合/聚合使用
    • 誤用:錯(cuò)誤地使用組合/聚合來表示整體與部分的關(guān)系,或者管理不當(dāng)導(dǎo)致內(nèi)存泄漏。
    • 正確使用:應(yīng)該明確整體與部分的關(guān)系,并且正確管理對(duì)象的生命周期。

下面我們來一一介紹這10種濫用設(shè)計(jì)模式的場(chǎng)景案例和分析。

1. 濫用工廠模式

濫用工廠模式通常指的是創(chuàng)建一個(gè)過于龐大和復(fù)雜的工廠類,它試圖創(chuàng)建和管理系統(tǒng)中所有不同類型的對(duì)象。這種做法違反了設(shè)計(jì)模式的意圖,即應(yīng)該保持代碼的簡(jiǎn)潔性和可維護(hù)性。

濫用工廠模式的案例

假設(shè)我們有一個(gè)應(yīng)用程序,其中包含多個(gè)不同類型的產(chǎn)品,每個(gè)產(chǎn)品都有其特定的創(chuàng)建邏輯。在濫用工廠模式的情況下,我們可能會(huì)創(chuàng)建一個(gè)“萬能工廠”來處理所有產(chǎn)品的創(chuàng)建。

  1. public class UniversalFactory {
  2. // 這是一個(gè)濫用工廠模式的例子,工廠類過于龐大和復(fù)雜
  3. public ProductA createProductA() {
  4. // 復(fù)雜的創(chuàng)建邏輯
  5. return new ProductA();
  6. }
  7. public ProductB createProductB() {
  8. // 復(fù)雜的創(chuàng)建邏輯
  9. return new ProductB();
  10. }
  11. public ProductC createProductC() {
  12. // 復(fù)雜的創(chuàng)建邏輯
  13. return new ProductC();
  14. }
  15. // ... 更多的產(chǎn)品創(chuàng)建方法
  16. public static class ProductA {
  17. // ProductA 的實(shí)現(xiàn)
  18. }
  19. public static class ProductB {
  20. // ProductB 的實(shí)現(xiàn)
  21. }
  22. public static class ProductC {
  23. // ProductC 的實(shí)現(xiàn)
  24. }
  25. // ... 更多的產(chǎn)品類
  26. }

在上面的代碼中,UniversalFactory 包含了創(chuàng)建所有產(chǎn)品的方法。隨著應(yīng)用程序的發(fā)展,這個(gè)工廠類可能會(huì)變得越來越龐大,難以維護(hù)。每次添加新產(chǎn)品時(shí),都需要修改工廠類,這違反了開閉原則(對(duì)擴(kuò)展開放,對(duì)修改封閉)。

具體說明

  1. 違反開閉原則:每次添加新產(chǎn)品時(shí),都需要修改工廠類,這增加了維護(hù)成本。
  2. 職責(zé)不明確:工廠類承擔(dān)了過多的職責(zé),它不僅負(fù)責(zé)創(chuàng)建對(duì)象,還可能包含了與產(chǎn)品相關(guān)的邏輯。
  3. 難以測(cè)試:由于工廠類與產(chǎn)品類緊密耦合,單元測(cè)試變得更加困難。
  4. 代碼復(fù)雜性增加:隨著工廠類變得越來越龐大,理解和使用它也變得更加復(fù)雜。

正確使用工廠模式

正確的做法是為每個(gè)產(chǎn)品系列創(chuàng)建專門的工廠類,這樣可以更好地組織代碼,并且每個(gè)工廠類只關(guān)注其特定的產(chǎn)品。

  1. public class ProductAFactory {
  2. public ProductA create() {
  3. // 創(chuàng)建 ProductA 的邏輯
  4. return new ProductA();
  5. }
  6. }
  7. public class ProductBFactory {
  8. public ProductB create() {
  9. // 創(chuàng)建 ProductB 的邏輯
  10. return new ProductB();
  11. }
  12. }
  13. // ... 為其他產(chǎn)品創(chuàng)建更多的工廠類

在這個(gè)例子中,每個(gè)工廠類只負(fù)責(zé)創(chuàng)建一種類型的產(chǎn)品,這樣代碼更加清晰,也更容易維護(hù)和擴(kuò)展。如果需要添加新產(chǎn)品,只需添加一個(gè)新的工廠類,而不需要修改現(xiàn)有的工廠類。

2. 過度使用單例模式

過度使用單例模式的案例

單例模式確保一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)全局訪問點(diǎn)。然而,在一些業(yè)務(wù)場(chǎng)景中,過度使用單例模式可能會(huì)導(dǎo)致問題,尤其是當(dāng)單例對(duì)象的狀態(tài)需要在多個(gè)用戶或線程之間共享時(shí)。

業(yè)務(wù)場(chǎng)景

假設(shè)我們正在開發(fā)一個(gè)多用戶的博客平臺(tái),每個(gè)用戶都可以發(fā)布博客文章。我們有一個(gè)BlogPostManager類,負(fù)責(zé)管理博客文章的創(chuàng)建、編輯和刪除。

錯(cuò)誤使用單例模式

  1. public class BlogPostManager {
  2. private static BlogPostManager instance;
  3. private List<BlogPost> blogPosts;
  4. private BlogPostManager() {
  5. blogPosts = new ArrayList<>();
  6. }
  7. public static synchronized BlogPostManager getInstance() {
  8. if (instance == null) {
  9. instance = new BlogPostManager();
  10. }
  11. return instance;
  12. }
  13. public void addBlogPost(BlogPost blogPost) {
  14. blogPosts.add(blogPost);
  15. }
  16. public List<BlogPost> getBlogPosts() {
  17. return blogPosts;
  18. }
  19. // ... 其他管理博客文章的方法
  20. }
  21. public class BlogPost {
  22. private String title;
  23. private String content;
  24. // ... 其他博客文章屬性和方法
  25. }

在這個(gè)例子中,BlogPostManager被設(shè)計(jì)為單例,這意味著所有用戶共享同一個(gè)BlogPostManager實(shí)例和它管理的博客文章列表。這在多用戶環(huán)境中是不安全的,因?yàn)橐粋€(gè)用戶的操作可能會(huì)影響其他用戶看到的數(shù)據(jù)。

具體說明

  1. 共享狀態(tài)問題:?jiǎn)卫J綄?dǎo)致的全局狀態(tài)使得在多用戶環(huán)境中難以維護(hù)獨(dú)立用戶的數(shù)據(jù)隔離。
  2. 線程安全問題:在多線程環(huán)境中,單例模式可能會(huì)導(dǎo)致線程安全問題,尤其是在懶漢式初始化的情況下。
  3. 測(cè)試?yán)щy:?jiǎn)卫J绞沟靡蕾囎⑷胱兊美щy,因?yàn)樗ǔR蕾囉谌譅顟B(tài),這會(huì)影響單元測(cè)試的編寫。
  4. 擴(kuò)展性問題:當(dāng)系統(tǒng)需要擴(kuò)展時(shí),單例模式可能會(huì)導(dǎo)致擴(kuò)展性問題,因?yàn)樗拗屏藢?duì)象的實(shí)例化方式。

正確使用單例模式

正確的做法是確保每個(gè)用戶都有自己的BlogPostManager實(shí)例,或者使用依賴注入來管理BlogPostManager的生命周期。

使用依賴注入

  1. public class BlogPostManager {
  2. private List<BlogPost> blogPosts;
  3. public BlogPostManager() {
  4. blogPosts = new ArrayList<>();
  5. }
  6. public void addBlogPost(BlogPost blogPost) {
  7. blogPosts.add(blogPost);
  8. }
  9. public List<BlogPost> getBlogPosts() {
  10. return blogPosts;
  11. }
  12. // ... 其他管理博客文章的方法
  13. }
  14. // 在用戶類中
  15. public class User {
  16. private BlogPostManager blogPostManager;
  17. public User(BlogPostManager blogPostManager) {
  18. this.blogPostManager = blogPostManager;
  19. }
  20. // ... 用戶的方法
  21. }

在這個(gè)例子中,每個(gè)User對(duì)象都有一個(gè)自己的BlogPostManager實(shí)例,這樣可以確保用戶之間的數(shù)據(jù)隔離。這種方式更適合多用戶環(huán)境,并且使得單元測(cè)試更加容易。

所以啊,單例模式應(yīng)該謹(jǐn)慎使用,特別是在需要數(shù)據(jù)隔離的多用戶系統(tǒng)中。在這些情況下,考慮使用依賴注入或其他設(shè)計(jì)模式來替代單例模式。

3. 錯(cuò)誤使用裝飾器模式

錯(cuò)誤使用裝飾器模式的案例

裝飾器模式是一種結(jié)構(gòu)型設(shè)計(jì)模式,它允許向一個(gè)現(xiàn)有的對(duì)象添加新的功能,同時(shí)又不改變其結(jié)構(gòu)。這種設(shè)計(jì)模式通過創(chuàng)建一個(gè)包裝對(duì)象,即裝飾器,來封裝實(shí)際對(duì)象。

業(yè)務(wù)場(chǎng)景

假設(shè)我們有一個(gè)在線商店,其中有一個(gè)Product類,表示商店中的商品。我們希望通過裝飾器模式為商品添加不同的包裝選項(xiàng),如禮品包裝。

錯(cuò)誤使用裝飾器模式

  1. public interface Product {
  2. double getCost();
  3. String getDescription();
  4. }
  5. public class Book implements Product {
  6. private String title;
  7. public Book(String title) {
  8. this.title = title;
  9. }
  10. @Override
  11. public double getCost() {
  12. return 15.00;
  13. }
  14. @Override
  15. public String getDescription() {
  16. return "Book: " + title;
  17. }
  18. }
  19. public abstract class ProductDecorator implements Product {
  20. protected Product decoratedProduct;
  21. public ProductDecorator(Product decoratedProduct) {
  22. this.decoratedProduct = decoratedProduct;
  23. }
  24. public double getCost() {
  25. return decoratedProduct.getCost();
  26. }
  27. public String getDescription() {
  28. return decoratedProduct.getDescription();
  29. }
  30. }
  31. public class GiftWrapDecorator extends ProductDecorator {
  32. private double giftWrapCost = 3.00;
  33. public GiftWrapDecorator(Product decoratedProduct) {
  34. super(decoratedProduct);
  35. }
  36. @Override
  37. public double getCost() {
  38. return decoratedProduct.getCost() + giftWrapCost;
  39. }
  40. @Override
  41. public String getDescription() {
  42. return decoratedProduct.getDescription() + ", with gift wrap";
  43. }
  44. }
  45. // 錯(cuò)誤使用裝飾器模式的客戶端代碼
  46. public class Main {
  47. public static void main(String[] args) {
  48. Product book = new Book("Java Design Patterns");
  49. Product giftWrappedBook = new GiftWrapDecorator(book);
  50. System.out.println("Cost: " + giftWrappedBook.getCost()); // Cost: 18.0
  51. System.out.println("Description: " + giftWrappedBook.getDescription()); // Description: Book: Java Design Patterns, with gift wrap
  52. }
  53. }

在這個(gè)例子中,GiftWrapDecorator 裝飾器正確地為Book 添加了禮品包裝功能。然而,如果我們錯(cuò)誤地將裝飾器模式應(yīng)用于不應(yīng)該被裝飾的對(duì)象,或者在裝飾器中引入了過多的邏輯,就可能導(dǎo)致問題。

具體說明

  1. 過度裝飾:如果一個(gè)對(duì)象被多層裝飾,可能會(huì)導(dǎo)致對(duì)象的復(fù)雜度過高,難以理解和維護(hù)。
  2. 裝飾器邏輯過于復(fù)雜:裝飾器應(yīng)該只負(fù)責(zé)添加額外的功能,如果裝飾器內(nèi)部邏輯過于復(fù)雜,可能會(huì)使得整個(gè)系統(tǒng)難以維護(hù)。
  3. 違反開閉原則:如果每次需要添加新功能時(shí)都需要修改裝飾器或引入新的裝飾器,這可能違反了開閉原則,即對(duì)擴(kuò)展開放,對(duì)修改封閉。
  4. 性能問題:裝飾器模式可能會(huì)導(dǎo)致性能問題,因?yàn)槊看窝b飾都可能增加額外的開銷。

正確使用裝飾器模式

正確的做法是確保裝飾器只添加必要的功能,并且裝飾器的邏輯應(yīng)該盡量簡(jiǎn)單。

  1. // 正確的客戶端代碼
  2. public class Main {
  3. public static void main(String[] args) {
  4. Product book = new Book("Java Design Patterns");
  5. Product giftWrappedBook = new GiftWrapDecorator(book);
  6. System.out.println("Cost: " + giftWrappedBook.getCost()); // Cost: 18.0
  7. System.out.println("Description: " + giftWrappedBook.getDescription()); // Description: Book: Java Design Patterns, with gift wrap
  8. }
  9. }

修改后的例子中,我們只添加了必要的裝飾器。如果需要更多的裝飾功能,我們應(yīng)該引入新的裝飾器類,而不是在現(xiàn)有裝飾器中添加更多的邏輯。

因此我們?nèi)苏J(rèn)識(shí)到,裝飾器模式是一種強(qiáng)大的設(shè)計(jì)模式,但應(yīng)該謹(jǐn)慎使用,確保它不會(huì)使系統(tǒng)變得過于復(fù)雜或難以維護(hù)。

4. 不恰當(dāng)?shù)睦^承使用

不恰當(dāng)?shù)睦^承使用案例

繼承是一種強(qiáng)大的工具,但它應(yīng)該謹(jǐn)慎使用。不恰當(dāng)?shù)睦^承通常是由于錯(cuò)誤地將“是一個(gè)(is-a)”關(guān)系應(yīng)用于代碼中,而不是基于功能的共享。

業(yè)務(wù)場(chǎng)景

假設(shè)我們有一個(gè)電子商務(wù)平臺(tái),需要處理不同類型的支付方式。我們可能會(huì)錯(cuò)誤地使用繼承來實(shí)現(xiàn)這些支付方式。

不恰當(dāng)?shù)睦^承使用

  1. // 基類 PaymentMethod 錯(cuò)誤地被用作所有支付方式的父類
  2. public abstract class PaymentMethod {
  3. protected String name;
  4. public PaymentMethod(String name) {
  5. this.name = name;
  6. }
  7. public abstract boolean processPayment(double amount);
  8. }
  9. // 信用卡支付類錯(cuò)誤地繼承了 PaymentMethod
  10. public class CreditCardPayment extends PaymentMethod {
  11. private String cardNumber;
  12. private String cardHolderName;
  13. private String expirationDate;
  14. private String cvv;
  15. public CreditCardPayment(String name, String cardNumber, String cardHolderName, String expirationDate, String cvv) {
  16. super(name);
  17. this.cardNumber = cardNumber;
  18. this.cardHolderName = cardHolderName;
  19. this.expirationDate = expirationDate;
  20. this.cvv = cvv;
  21. }
  22. @Override
  23. public boolean processPayment(double amount) {
  24. // 處理信用卡支付邏輯
  25. return true;
  26. }
  27. }
  28. // 銀行轉(zhuǎn)賬支付類錯(cuò)誤地繼承了 PaymentMethod
  29. public class BankTransferPayment extends PaymentMethod {
  30. private String accountNumber;
  31. private String bankName;
  32. public BankTransferPayment(String name, String accountNumber, String bankName) {
  33. super(name);
  34. this.accountNumber = accountNumber;
  35. this.bankName = bankName;
  36. }
  37. @Override
  38. public boolean processPayment(double amount) {
  39. // 處理銀行轉(zhuǎn)賬支付邏輯
  40. return true;
  41. }
  42. }

在這個(gè)例子中,PaymentMethod 被用作所有支付方式的基類。然而,這并不是一個(gè)恰當(dāng)?shù)氖褂美^承的情況,因?yàn)椤靶庞每ㄖЦ丁焙汀般y行轉(zhuǎn)賬支付”并不是“支付方式”的一種類型,它們是具體的支付實(shí)現(xiàn)。這種繼承關(guān)系違反了“是一個(gè)(is-a)”原則,因?yàn)樾庞每ㄖЦ恫⒉皇侵Ц斗绞降囊环N類型,而是一種具體的支付行為。

具體說明

  1. 違反“是一個(gè)(is-a)”原則:繼承應(yīng)該表示一個(gè)類是另一個(gè)類的特化。如果一個(gè)類不是另一個(gè)類的特化,那么繼承就是不恰當(dāng)?shù)摹?/li>
  2. 增加耦合性:繼承自同一個(gè)基類的所有子類都與基類緊密耦合。如果基類發(fā)生變化,所有的子類都可能受到影響。
  3. 限制了靈活性:繼承自基類的子類通常不能輕易地切換到另一種類型的基類,這限制了設(shè)計(jì)的靈活性。
  4. 繼承導(dǎo)致的復(fù)雜性:繼承層次結(jié)構(gòu)可能會(huì)變得復(fù)雜和難以理解,特別是當(dāng)它們很深或很寬時(shí)。

正確的使用繼承

正確的做法是使用組合而不是繼承來實(shí)現(xiàn)支付方式。

  1. // 支付方式接口
  2. public interface PaymentMethod {
  3. boolean processPayment(double amount);
  4. }
  5. // 信用卡支付類實(shí)現(xiàn)支付方式接口
  6. public class CreditCardPayment implements PaymentMethod {
  7. private String cardNumber;
  8. private String cardHolderName;
  9. private String expirationDate;
  10. private String cvv;
  11. public CreditCardPayment(String cardNumber, String cardHolderName, String expirationDate, String cvv) {
  12. this.cardNumber = cardNumber;
  13. this.cardHolderName = cardHolderName;
  14. this.expirationDate = expirationDate;
  15. this.cvv = cvv;
  16. }
  17. @Override
  18. public boolean processPayment(double amount) {
  19. // 處理信用卡支付邏輯
  20. return true;
  21. }
  22. }
  23. // 銀行轉(zhuǎn)賬支付類實(shí)現(xiàn)支付方式接口
  24. public class BankTransferPayment implements PaymentMethod {
  25. private String accountNumber;
  26. private String bankName;
  27. public BankTransferPayment(String accountNumber, String bankName) {
  28. this.accountNumber = accountNumber;
  29. this.bankName = bankName;
  30. }
  31. @Override
  32. public boolean processPayment(double amount) {
  33. // 處理銀行轉(zhuǎn)賬支付邏輯
  34. return true;
  35. }
  36. }

改進(jìn)后,我們使用接口PaymentMethod來定義支付行為,而不是使用繼承。這樣,不同的支付方式可以自由地實(shí)現(xiàn)這個(gè)接口,而不需要從特定的基類繼承。這種設(shè)計(jì)更加靈活,每個(gè)支付方式的具體實(shí)現(xiàn)都是獨(dú)立的,不會(huì)受到其他實(shí)現(xiàn)的影響。

5. 濫用觀察者模式

濫用觀察者模式的案例

觀察者模式是一種行為設(shè)計(jì)模式,它定義了對(duì)象之間的一對(duì)多依賴關(guān)系,當(dāng)一個(gè)對(duì)象狀態(tài)改變時(shí),所有依賴于它的對(duì)象都會(huì)得到通知。濫用觀察者模式可能會(huì)導(dǎo)致性能問題、代碼復(fù)雜性增加以及難以維護(hù)的代碼。

業(yè)務(wù)場(chǎng)景

假設(shè)我們有一個(gè)新聞發(fā)布平臺(tái),每當(dāng)有新的新聞發(fā)布時(shí),所有訂閱者都應(yīng)該收到通知。如果系統(tǒng)中有大量的訂閱者或者通知邏輯非常復(fù)雜,濫用觀察者模式可能會(huì)導(dǎo)致問題。

濫用觀察者模式的代碼示例

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. // 主題接口
  4. public interface Observer {
  5. void update(String news);
  6. }
  7. // 具體主題
  8. public class NewsPublisher {
  9. private List<Observer> observers;
  10. private String news;
  11. public NewsPublisher() {
  12. observers = new ArrayList<>();
  13. }
  14. public void addObserver(Observer observer) {
  15. observers.add(observer);
  16. }
  17. public void removeObserver(Observer observer) {
  18. observers.remove(observer);
  19. }
  20. public void notifyObservers() {
  21. for (Observer observer : observers) {
  22. observer.update(news);
  23. }
  24. }
  25. public void setNews(String news) {
  26. this.news = news;
  27. notifyObservers();
  28. }
  29. public String getNews() {
  30. return news;
  31. }
  32. }
  33. // 具體觀察者
  34. public class NewsSubscriber implements Observer {
  35. private String name;
  36. public NewsSubscriber(String name) {
  37. this.name = name;
  38. }
  39. @Override
  40. public void update(String news) {
  41. System.out.println(name + " received news: " + news);
  42. }
  43. }
  44. // 客戶端代碼
  45. public class NewsPlatform {
  46. public static void main(String[] args) {
  47. NewsPublisher publisher = new NewsPublisher();
  48. publisher.addObserver(new NewsSubscriber("Subscriber 1"));
  49. publisher.addObserver(new NewsSubscriber("Subscriber 2"));
  50. // ... 添加更多訂閱者
  51. // 發(fā)布新聞
  52. publisher.setNews("Breaking news!");
  53. }
  54. }

在這個(gè)例子中,NewsPublisher 是一個(gè)具體主題,它維護(hù)了一個(gè)觀察者列表,并在新聞更新時(shí)通知所有觀察者。NewsSubscriber 是一個(gè)具體觀察者,它實(shí)現(xiàn)了Observer接口,并在接收到新聞時(shí)打印出來。

具體說明

  1. 性能問題:如果觀察者數(shù)量非常多,每次狀態(tài)變化時(shí)通知所有觀察者可能會(huì)導(dǎo)致性能問題。
  2. 代碼復(fù)雜性:在觀察者模式中添加復(fù)雜的業(yè)務(wù)邏輯可能會(huì)導(dǎo)致代碼難以理解和維護(hù)。
  3. 循環(huán)依賴:如果觀察者和主題之間存在循環(huán)依賴,可能會(huì)導(dǎo)致難以追蹤的錯(cuò)誤。
  4. 內(nèi)存泄漏:如果觀察者沒有正確地從主題中移除,可能會(huì)導(dǎo)致內(nèi)存泄漏。

正確的使用觀察者模式

正確的做法是確保觀察者模式的使用場(chǎng)景適合,并且觀察者的數(shù)量和通知邏輯都得到了合理控制。

  1. // 客戶端代碼
  2. public class NewsPlatform {
  3. public static void main(String[] args) {
  4. NewsPublisher publisher = new NewsPublisher();
  5. publisher.addObserver(new NewsSubscriber("Subscriber 1"));
  6. // 添加適量的訂閱者,而不是無限制地添加
  7. // 發(fā)布新聞
  8. publisher.setNews("Breaking news!");
  9. }
  10. }

在這個(gè)修正后的客戶端代碼中,我們只添加了適量的訂閱者。此外,我們應(yīng)該確保在不再需要接收通知時(shí),從主題中移除觀察者,以避免內(nèi)存泄漏。

最后強(qiáng)調(diào)一下,觀察者模式是一種有用的設(shè)計(jì)模式,但應(yīng)該在適當(dāng)?shù)膱?chǎng)景中使用,并且要注意控制觀察者的數(shù)量和復(fù)雜性。在設(shè)計(jì)系統(tǒng)時(shí),應(yīng)該考慮到性能和可維護(hù)性。

6. 錯(cuò)誤使用命令模式

錯(cuò)誤使用命令模式的案例

命令模式是一種行為設(shè)計(jì)模式,它將請(qǐng)求封裝為一個(gè)對(duì)象,從而允許用戶使用不同的請(qǐng)求、隊(duì)列或日志請(qǐng)求來參數(shù)化其他對(duì)象。命令模式也支持可撤銷的操作。錯(cuò)誤使用命令模式可能會(huì)導(dǎo)致系統(tǒng)復(fù)雜性增加、代碼冗余或者違反開閉原則。

業(yè)務(wù)場(chǎng)景

假設(shè)我們有一個(gè)簡(jiǎn)單的文本編輯器,用戶可以對(duì)文本執(zhí)行一些操作,如插入文本、刪除文本等。如果我們錯(cuò)誤地將所有操作都封裝為命令對(duì)象,即使這些操作可以通過更簡(jiǎn)單的方法實(shí)現(xiàn),這就是錯(cuò)誤使用命令模式。

錯(cuò)誤使用命令模式的代碼示例

  1. // 命令接口
  2. public interface Command {
  3. void execute();
  4. }
  5. // 簡(jiǎn)單的文本編輯器類
  6. public class TextEditor {
  7. private String content = "";
  8. public void type(String text) {
  9. content += text;
  10. }
  11. public void removeLastWord() {
  12. content = content.replaceAll("\\s+\\S+$", "");
  13. }
  14. public String getContent() {
  15. return content;
  16. }
  17. }
  18. // 插入文本命令類
  19. public class TypeCommand implements Command {
  20. private TextEditor editor;
  21. private String text;
  22. public TypeCommand(TextEditor editor, String text) {
  23. this.editor = editor;
  24. this.text = text;
  25. }
  26. @Override
  27. public void execute() {
  28. editor.type(text);
  29. }
  30. }
  31. // 刪除文本命令類
  32. public class RemoveCommand implements Command {
  33. private TextEditor editor;
  34. public RemoveCommand(TextEditor editor) {
  35. this.editor = editor;
  36. }
  37. @Override
  38. public void execute() {
  39. editor.removeLastWord();
  40. }
  41. }
  42. // 客戶端代碼
  43. public class EditorClient {
  44. public static void main(String[] args) {
  45. TextEditor editor = new TextEditor();
  46. Command typeCommand = new TypeCommand(editor, "Hello World");
  47. Command removeCommand = new RemoveCommand(editor);
  48. typeCommand.execute();
  49. System.out.println(editor.getContent()); // 輸出: Hello World
  50. removeCommand.execute();
  51. System.out.println(editor.getContent()); // 輸出: (empty string)
  52. }
  53. }

在這個(gè)例子中,我們?yōu)?code>TextEditor的每個(gè)操作都創(chuàng)建了一個(gè)命令對(duì)象。然而,對(duì)于這樣簡(jiǎn)單的操作,使用命令模式可能是過度設(shè)計(jì)。這不僅增加了系統(tǒng)的復(fù)雜性,而且也增加了代碼的冗余。

具體說明

  1. 過度設(shè)計(jì):對(duì)于簡(jiǎn)單的操作,使用命令模式可能會(huì)引入不必要的復(fù)雜性。
  2. 違反開閉原則:如果新操作需要添加,我們不得不為每個(gè)新操作創(chuàng)建新的命令類,這違反了對(duì)擴(kuò)展開放,對(duì)修改封閉的原則。
  3. 增加學(xué)習(xí)成本:新開發(fā)人員可能需要花費(fèi)額外的時(shí)間去理解命令模式的使用,而不是直接使用簡(jiǎn)單的方法調(diào)用。
  4. 性能考慮:對(duì)于性能敏感的應(yīng)用,命令對(duì)象的創(chuàng)建和管理可能會(huì)帶來額外的性能開銷。

正確的使用命令模式

正確的做法是將命令模式用于確實(shí)需要它的場(chǎng)合,比如操作的撤銷/重做功能、操作的排隊(duì)執(zhí)行等。

  1. // 撤銷命令接口
  2. public interface Command {
  3. void execute();
  4. void undo();
  5. }
  6. // 插入文本命令類
  7. public class TypeCommand implements Command {
  8. private TextEditor editor;
  9. private String text;
  10. private String previousContent;
  11. public TypeCommand(TextEditor editor, String text) {
  12. this.editor = editor;
  13. this.text = text;
  14. }
  15. @Override
  16. public void execute() {
  17. previousContent = editor.getContent();
  18. editor.type(text);
  19. }
  20. @Override
  21. public void undo() {
  22. editor.setContent(previousContent);
  23. }
  24. }
  25. // 客戶端代碼
  26. public class EditorClient {
  27. public static void main(String[] args) {
  28. TextEditor editor = new TextEditor();
  29. Command typeCommand = new TypeCommand(editor, "Hello World");
  30. typeCommand.execute();
  31. System.out.println(editor.getContent()); // 輸出: Hello World
  32. typeCommand.undo();
  33. System.out.println(editor.getContent()); // 輸出: (empty string)
  34. }
  35. }

在這個(gè)修正后的示例中,我們?yōu)樾枰蜂N功能的命令實(shí)現(xiàn)了undo方法。這樣,命令模式的使用就變得更加合理,因?yàn)樗峁┝祟~外的價(jià)值,即操作的撤銷功能。

7. 不恰當(dāng)?shù)臓顟B(tài)模式使用

不恰當(dāng)?shù)臓顟B(tài)模式使用案例

狀態(tài)模式是一種行為設(shè)計(jì)模式,它允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變時(shí)改變其行為。這種模式非常適合于那些具有多個(gè)狀態(tài),并且在不同狀態(tài)下行為有顯著差異的對(duì)象。不恰當(dāng)?shù)氖褂脿顟B(tài)模式可能會(huì)導(dǎo)致設(shè)計(jì)復(fù)雜、難以維護(hù)和理解。

業(yè)務(wù)場(chǎng)景

假設(shè)我們有一個(gè)簡(jiǎn)單的交通信號(hào)燈系統(tǒng),它有三個(gè)狀態(tài):紅燈、綠燈和黃燈。每個(gè)狀態(tài)持續(xù)一定時(shí)間后會(huì)轉(zhuǎn)換到下一個(gè)狀態(tài)。如果我們錯(cuò)誤地使用狀態(tài)模式來處理這個(gè)簡(jiǎn)單的順序邏輯,就可能導(dǎo)致不恰當(dāng)?shù)脑O(shè)計(jì)。

不恰當(dāng)?shù)臓顟B(tài)模式使用代碼示例

  1. // 狀態(tài)接口
  2. public interface TrafficLightState {
  3. void change(TrafficLight light);
  4. }
  5. // 紅燈狀態(tài)類
  6. public class RedLight implements TrafficLightState {
  7. @Override
  8. public void change(TrafficLight light) {
  9. System.out.println("Red light on");
  10. // 假設(shè)紅燈持續(xù)一段時(shí)間后自動(dòng)切換到綠燈
  11. light.setState(new GreenLight());
  12. }
  13. }
  14. // 綠燈狀態(tài)類
  15. public class GreenLight implements TrafficLightState {
  16. @Override
  17. public void change(TrafficLight light) {
  18. System.out.println("Green light on");
  19. // 假設(shè)綠燈持續(xù)一段時(shí)間后自動(dòng)切換到黃燈
  20. light.setState(new YellowLight());
  21. }
  22. }
  23. // 黃燈狀態(tài)類
  24. public class YellowLight implements TrafficLightState {
  25. @Override
  26. public void change(TrafficLight light) {
  27. System.out.println("Yellow light on");
  28. // 假設(shè)黃燈持續(xù)一段時(shí)間后自動(dòng)切換到紅燈
  29. light.setState(new RedLight());
  30. }
  31. }
  32. // 交通信號(hào)燈類
  33. public class TrafficLight {
  34. private TrafficLightState state;
  35. public TrafficLight() {
  36. this.state = new RedLight(); // 初始狀態(tài)為紅燈
  37. }
  38. public void setState(TrafficLightState state) {
  39. this.state = state;
  40. }
  41. public void change() {
  42. state.change(this);
  43. }
  44. }
  45. // 客戶端代碼
  46. public class TrafficLightSystem {
  47. public static void main(String[] args) {
  48. TrafficLight light = new TrafficLight();
  49. // 模擬信號(hào)燈變化
  50. light.change(); // 紅燈
  51. light.change(); // 綠燈
  52. light.change(); // 黃燈
  53. light.change(); // 再次紅燈
  54. }
  55. }

在這個(gè)例子中,我們?yōu)榻煌ㄐ盘?hào)燈的每個(gè)狀態(tài)都創(chuàng)建了一個(gè)狀態(tài)類。然而,對(duì)于這樣一個(gè)簡(jiǎn)單的順序邏輯,使用狀態(tài)模式可能是過度設(shè)計(jì)。這不僅增加了系統(tǒng)的復(fù)雜性,而且也增加了代碼的冗余。

具體說明

  1. 過度設(shè)計(jì):對(duì)于簡(jiǎn)單的狀態(tài)轉(zhuǎn)換邏輯,使用狀態(tài)模式可能會(huì)引入不必要的復(fù)雜性。
  2. 增加學(xué)習(xí)成本:新開發(fā)人員可能需要花費(fèi)額外的時(shí)間去理解狀態(tài)模式的使用,而不是直接使用簡(jiǎn)單的狀態(tài)管理邏輯。
  3. 代碼冗余:每個(gè)狀態(tài)類都需要實(shí)現(xiàn)相同的change方法,這可能導(dǎo)致代碼冗余。
  4. 難以維護(hù):隨著系統(tǒng)的發(fā)展,維護(hù)和擴(kuò)展?fàn)顟B(tài)模式可能會(huì)變得復(fù)雜,特別是當(dāng)狀態(tài)和轉(zhuǎn)換邏輯變得更加復(fù)雜時(shí)。

正確的使用狀態(tài)模式

正確的做法是將狀態(tài)模式用于確實(shí)需要它的場(chǎng)合,比如狀態(tài)轉(zhuǎn)換邏輯復(fù)雜、狀態(tài)之間有顯著不同的行為或者需要記錄狀態(tài)歷史等。

  1. // 交通信號(hào)燈類(簡(jiǎn)化版)
  2. public class TrafficLight {
  3. private String[] lights = {"Red", "Green", "Yellow"};
  4. private int index = 0; // 初始狀態(tài)為紅燈
  5. public void change() {
  6. System.out.println(lights[index] + " light on");
  7. index = (index + 1) % lights.length; // 循環(huán)切換狀態(tài)
  8. }
  9. }
  10. // 客戶端代碼
  11. public class TrafficLightSystem {
  12. public static void main(String[] args) {
  13. TrafficLight light = new TrafficLight();
  14. // 模擬信號(hào)燈變化
  15. light.change(); // 紅燈
  16. light.change(); // 綠燈
  17. light.change(); // 黃燈
  18. light.change(); // 再次紅燈
  19. }
  20. }

在這個(gè)修正后的示例中,我們使用一個(gè)簡(jiǎn)單的索引和數(shù)組來管理信號(hào)燈的狀態(tài),而不是使用狀態(tài)模式。這種設(shè)計(jì)更加簡(jiǎn)潔和直觀,適合處理簡(jiǎn)單的狀態(tài)轉(zhuǎn)換邏輯。

8. 錯(cuò)誤使用代理模式

錯(cuò)誤使用代理模式的案例

代理模式是一種結(jié)構(gòu)型設(shè)計(jì)模式,它提供了對(duì)另一個(gè)對(duì)象的代理以控制對(duì)這個(gè)對(duì)象的訪問。代理模式有多種類型,包括虛擬代理、遠(yuǎn)程代理、保護(hù)代理和智能引用代理等。錯(cuò)誤使用代理模式可能會(huì)導(dǎo)致系統(tǒng)設(shè)計(jì)過于復(fù)雜、性能降低或者違反設(shè)計(jì)原則。

業(yè)務(wù)場(chǎng)景

假設(shè)我們有一個(gè)圖像加載系統(tǒng),需要從網(wǎng)絡(luò)加載圖像。如果我們錯(cuò)誤地為每個(gè)圖像對(duì)象創(chuàng)建一個(gè)代理,即使這些圖像對(duì)象不需要代理提供的額外功能,這就是錯(cuò)誤使用代理模式。

錯(cuò)誤使用代理模式的代碼示例

  1. // 目標(biāo)接口
  2. public interface Image {
  3. void display();
  4. }
  5. // 目標(biāo)類
  6. public class RealImage implements Image {
  7. private String fileName;
  8. public RealImage(String fileName) {
  9. this.fileName = fileName;
  10. loadFromDisk(fileName);
  11. }
  12. @Override
  13. public void display() {
  14. System.out.println("Displaying " + fileName);
  15. }
  16. private void loadFromDisk(String fileName) {
  17. System.out.println("Loading " + fileName + " from disk");
  18. }
  19. }
  20. // 代理類
  21. public class ImageProxy implements Image {
  22. private RealImage realImage;
  23. private String fileName;
  24. public ImageProxy(String fileName) {
  25. this.fileName = fileName;
  26. }
  27. @Override
  28. public void display() {
  29. if (realImage == null) {
  30. realImage = new RealImage(fileName);
  31. }
  32. realImage.display();
  33. }
  34. }
  35. // 客戶端代碼
  36. public class ProxyExample {
  37. public static void main(String[] args) {
  38. Image image = new ImageProxy("image1.jpg");
  39. image.display();
  40. }
  41. }

在這個(gè)例子中,ImageProxy 代理類包裝了 RealImage 目標(biāo)類。然而,如果 RealImage 類的實(shí)例化和使用非常簡(jiǎn)單,并且不需要延遲加載或其他代理功能,那么使用代理模式就是不恰當(dāng)?shù)摹_@可能會(huì)導(dǎo)致不必要的性能開銷和設(shè)計(jì)復(fù)雜性。

具體說明

  1. 不必要的復(fù)雜性:如果目標(biāo)對(duì)象不需要代理提供的控制或延遲加載等功能,使用代理模式會(huì)增加系統(tǒng)的復(fù)雜性。
  2. 性能開銷:代理對(duì)象的創(chuàng)建和使用可能會(huì)引入額外的性能開銷,尤其是在不需要代理的情況下。
  3. 違反開閉原則:如果未來需要添加新的代理功能,可能需要修改代理類,這違反了對(duì)擴(kuò)展開放,對(duì)修改封閉的原則。
  4. 增加維護(hù)成本:代理模式可能會(huì)導(dǎo)致代碼難以理解和維護(hù),尤其是當(dāng)代理類和目標(biāo)類之間的關(guān)系不明確時(shí)。

正確的使用代理模式

正確的做法是將代理模式用于確實(shí)需要它的場(chǎng)合,比如需要控制對(duì)資源的訪問、延遲加載資源或者提供額外的安全控制等。

  1. // 虛擬代理類
  2. public class VirtualImageProxy implements Image {
  3. private RealImage realImage;
  4. private String fileName;
  5. public VirtualImageProxy(String fileName) {
  6. this.fileName = fileName;
  7. }
  8. @Override
  9. public void display() {
  10. if (realImage == null) {
  11. realImage = new RealImage(fileName);
  12. }
  13. realImage.display();
  14. }
  15. public void loadImage() {
  16. System.out.println("Loading " + fileName + " from network");
  17. }
  18. }
  19. // 客戶端代碼
  20. public class ProxyExample {
  21. public static void main(String[] args) {
  22. Image image = new VirtualImageProxy("image1.jpg");
  23. image.loadImage(); // 模擬從網(wǎng)絡(luò)加載圖像
  24. image.display();
  25. }
  26. }

在這個(gè)修正后的示例中,VirtualImageProxy 類提供了延遲加載的功能,只有在實(shí)際需要顯示圖像時(shí)才從網(wǎng)絡(luò)加載。這種設(shè)計(jì)更加合理,因?yàn)樗峁┝舜砟J降膶?shí)際價(jià)值,即延遲加載和優(yōu)化性能。

9. 濫用策略模式

濫用策略模式的案例

策略模式是一種行為設(shè)計(jì)模式,它定義了一系列的算法,并將每一個(gè)算法封裝起來,使它們可以互換使用。策略模式的用意是使算法的變化不會(huì)影響到使用算法的用戶。濫用策略模式可能會(huì)導(dǎo)致設(shè)計(jì)過于復(fù)雜,增加不必要的類數(shù)量,以及使得代碼難以理解和維護(hù)。

業(yè)務(wù)場(chǎng)景

假設(shè)我們有一個(gè)簡(jiǎn)單的計(jì)算器程序,它可以執(zhí)行加、減、乘、除四種基本運(yùn)算。如果我們?yōu)槊恳环N操作都創(chuàng)建一個(gè)策略類,即使這些操作可以通過更簡(jiǎn)單的方法實(shí)現(xiàn),這就是濫用策略模式。

濫用策略模式的代碼示例

  1. // 策略接口
  2. public interface CalculationStrategy {
  3. int calculate(int a, int b);
  4. }
  5. // 加法策略類
  6. public class AddStrategy implements CalculationStrategy {
  7. @Override
  8. public int calculate(int a, int b) {
  9. return a + b;
  10. }
  11. }
  12. // 減法策略類
  13. public class SubtractStrategy implements CalculationStrategy {
  14. @Override
  15. public int calculate(int a, int b) {
  16. return a - b;
  17. }
  18. }
  19. // 乘法策略類
  20. public class MultiplyStrategy implements CalculationStrategy {
  21. @Override
  22. public int calculate(int a, int b) {
  23. return a * b;
  24. }
  25. }
  26. // 除法策略類
  27. public class DivideStrategy implements CalculationStrategy {
  28. @Override
  29. public int calculate(int a, int b) {
  30. if (b != 0) {
  31. return a / b;
  32. } else {
  33. throw new IllegalArgumentException("Divider cannot be zero.");
  34. }
  35. }
  36. }
  37. // 計(jì)算器上下文
  38. public class Calculator {
  39. private CalculationStrategy strategy;
  40. public Calculator(CalculationStrategy strategy) {
  41. this.strategy = strategy;
  42. }
  43. public void setStrategy(CalculationStrategy strategy) {
  44. this.strategy = strategy;
  45. }
  46. public int performCalculation(int a, int b) {
  47. return strategy.calculate(a, b);
  48. }
  49. }
  50. // 客戶端代碼
  51. public class StrategyPatternExample {
  52. public static void main(String[] args) {
  53. Calculator calculator = new Calculator(new AddStrategy());
  54. System.out.println("10 + 5 = " + calculator.performCalculation(10, 5));
  55. calculator.setStrategy(new SubtractStrategy());
  56. System.out.println("10 - 5 = " + calculator.performCalculation(10, 5));
  57. calculator.setStrategy(new MultiplyStrategy());
  58. System.out.println("10 * 5 = " + calculator.performCalculation(10, 5));
  59. calculator.setStrategy(new DivideStrategy());
  60. System.out.println("10 / 5 = " + calculator.performCalculation(10, 5));
  61. }
  62. }

在這個(gè)例子中,我們?yōu)橛?jì)算器的每一種操作都創(chuàng)建了一個(gè)策略類。然而,對(duì)于這樣簡(jiǎn)單的操作,使用策略模式可能是過度設(shè)計(jì)。這不僅增加了系統(tǒng)的復(fù)雜性,而且也增加了代碼的冗余。

具體說明

  1. 過度設(shè)計(jì):對(duì)于簡(jiǎn)單的操作,使用策略模式可能會(huì)引入不必要的復(fù)雜性。
  2. 增加學(xué)習(xí)成本:新開發(fā)人員可能需要花費(fèi)額外的時(shí)間去理解策略模式的使用,而不是直接使用簡(jiǎn)單的方法調(diào)用。
  3. 代碼冗余:每個(gè)策略類都需要實(shí)現(xiàn)相同的接口,這可能導(dǎo)致代碼冗余。
  4. 難以維護(hù):隨著系統(tǒng)的發(fā)展,維護(hù)和擴(kuò)展策略模式可能會(huì)變得復(fù)雜,特別是當(dāng)策略類的數(shù)量增加時(shí)。

正確的使用策略模式

正確的做法是將策略模式用于確實(shí)需要它的場(chǎng)合,比如算法的選擇會(huì)頻繁變化,或者需要在運(yùn)行時(shí)根據(jù)不同的條件選擇不同的算法。

  1. // 計(jì)算器類(簡(jiǎn)化版)
  2. public class SimpleCalculator {
  3. public int add(int a, int b) {
  4. return a + b;
  5. }
  6. public int subtract(int a, int b) {
  7. return a - b;
  8. }
  9. public int multiply(int a, int b) {
  10. return a * b;
  11. }
  12. public int divide(int a, int b) {
  13. if (b != 0) {
  14. return a / b;
  15. } else {
  16. throw new IllegalArgumentException("Divider cannot be zero.");
  17. }
  18. }
  19. }
  20. // 客戶端代碼
  21. public class SimpleCalculatorExample {
  22. public static void main(String[] args) {
  23. SimpleCalculator calculator = new SimpleCalculator();
  24. System.out.println("10 + 5 = " + calculator.add(10, 5));
  25. System.out.println("10 - 5 = " + calculator.subtract(10, 5));
  26. System.out.println("10 * 5 = " + calculator.multiply(10, 5));
  27. System.out.println("10 / 5 = " + calculator.divide(10, 5));
  28. }
  29. }

修正后的代碼,我們直接在計(jì)算器類中實(shí)現(xiàn)了所有的操作,而不是使用策略模式。這種設(shè)計(jì)更加簡(jiǎn)潔和直觀,適合處理簡(jiǎn)單的操作。如果未來需要添加新的算法或者改變算法的行為,可以考慮使用策略模式來提高靈活性。

10. 不恰當(dāng)?shù)慕M合/聚合使用

不恰當(dāng)?shù)慕M合/聚合使用案例

組合(Composition)和聚合(Aggregation)是表示整體與部分關(guān)系的方式,它們都是關(guān)聯(lián)的特殊形式。不恰當(dāng)?shù)厥褂媒M合或聚合可能會(huì)導(dǎo)致對(duì)象的生命周期管理混亂、職責(zé)不明確或者設(shè)計(jì)過于復(fù)雜。

業(yè)務(wù)場(chǎng)景

假設(shè)我們有一個(gè)公司管理系統(tǒng),其中包含公司和員工的關(guān)系。如果錯(cuò)誤地使用組合或聚合,可能會(huì)導(dǎo)致管理混亂,比如錯(cuò)誤地刪除公司時(shí)也刪除了員工。

不恰當(dāng)?shù)慕M合/聚合使用代碼示例

  1. // 員工類
  2. public class Employee {
  3. private String name;
  4. private Company company; // 員工所屬的公司
  5. public Employee(String name, Company company) {
  6. this.name = name;
  7. this.company = company;
  8. }
  9. // ... 員工的其他方法
  10. }
  11. // 公司類
  12. public class Company {
  13. private String name;
  14. private List<Employee> employees; // 公司的員工列表
  15. public Company(String name) {
  16. this.name = name;
  17. this.employees = new ArrayList<>();
  18. }
  19. public void addEmployee(Employee employee) {
  20. employees.add(employee);
  21. employee.setCompany(this); // 錯(cuò)誤地在添加員工時(shí)設(shè)置公司
  22. }
  23. public void removeEmployee(Employee employee) {
  24. employees.remove(employee);
  25. employee.setCompany(null); // 錯(cuò)誤地在刪除員工時(shí)取消設(shè)置公司
  26. }
  27. // ... 公司的其他方法
  28. }
  29. // 客戶端代碼
  30. public class CompanyManagementSystem {
  31. public static void main(String[] args) {
  32. Company company = new Company("Moonshot AI");
  33. Employee employee1 = new Employee("Kimi", company);
  34. Employee employee2 = new Employee("Alex", company);
  35. company.addEmployee(employee1);
  36. company.addEmployee(employee2);
  37. // 錯(cuò)誤地刪除公司,同時(shí)刪除了所有員工
  38. company = null; // 這將導(dǎo)致員工對(duì)象也被垃圾回收
  39. }
  40. }

在這個(gè)例子中,Company 類通過 employees 列表與 Employee 類建立了一種關(guān)系。然而,當(dāng)公司被設(shè)置為 null 時(shí),由于員工對(duì)象仍然持有對(duì)公司的引用,這可能會(huì)導(dǎo)致不一致的狀態(tài),因?yàn)閱T工對(duì)象仍然認(rèn)為它們屬于一個(gè)已經(jīng)不存在的公司。

具體說明

  1. 生命周期管理混亂:如果組合或聚合的對(duì)象生命周期沒有正確管理,可能會(huì)導(dǎo)致對(duì)象狀態(tài)不一致。
  2. 職責(zé)不明確:在不恰當(dāng)?shù)慕M合/聚合關(guān)系中,對(duì)象的職責(zé)可能不明確,比如員工對(duì)象應(yīng)該只關(guān)心自己的數(shù)據(jù),而不是關(guān)心所屬公司的狀態(tài)。
  3. 設(shè)計(jì)過于復(fù)雜:不恰當(dāng)?shù)年P(guān)系可能會(huì)導(dǎo)致設(shè)計(jì)過于復(fù)雜,難以理解和維護(hù)。

正確的使用組合/聚合

正確的做法是確保組合/聚合關(guān)系反映了實(shí)際的業(yè)務(wù)邏輯,并且對(duì)象的生命周期得到了正確的管理。

  1. // 員工類
  2. public class Employee {
  3. private String name;
  4. private Company company; // 員工所屬的公司
  5. public Employee(String name) {
  6. this.name = name;
  7. }
  8. // ... 員工的其他方法
  9. public void setCompany(Company company) {
  10. this.company = company;
  11. }
  12. public Company getCompany() {
  13. return company;
  14. }
  15. }
  16. // 公司類
  17. public class Company {
  18. private String name;
  19. private List<Employee> employees; // 公司的員工列表
  20. public Company(String name) {
  21. this.name = name;
  22. this.employees = new ArrayList<>();
  23. }
  24. public void addEmployee(Employee employee) {
  25. employees.add(employee);
  26. employee.setCompany(this); // 正確地在添加員工時(shí)設(shè)置公司
  27. }
  28. public void removeEmployee(Employee employee) {
  29. employees.remove(employee);
  30. // 不取消設(shè)置公司,因?yàn)閱T工可能仍然需要知道他們之前屬于哪個(gè)公司
  31. }
  32. // ... 公司的其他方法
  33. }
  34. // 客戶端代碼
  35. public class CompanyManagementSystem {
  36. public static void main(String[] args) {
  37. Company company = new Company("Moonshot AI");
  38. List<Employee> employees = new ArrayList<>();
  39. employees.add(new Employee("Kimi"));
  40. employees.add(new Employee("Alex"));
  41. for (Employee employee : employees) {
  42. company.addEmployee(employee);
  43. }
  44. // 刪除公司時(shí),員工列表被清空,但員工對(duì)象仍然存在
  45. company = null;
  46. // 員工對(duì)象不會(huì)被垃圾回收,因?yàn)樗鼈冊(cè)?employees 列表中仍然有引用
  47. }
  48. }

修正后的代碼,我們確保了員工對(duì)象的生命周期獨(dú)立于公司對(duì)象。即使公司對(duì)象被刪除,員工對(duì)象仍然存在,并且可以通過其他方式進(jìn)行管理。這種設(shè)計(jì)更加符合實(shí)際的業(yè)務(wù)邏輯,并且使得對(duì)象的生命周期管理更加清晰。

最后

濫用設(shè)計(jì)模式,就像在沒有正確診斷的情況下給病人開藥一樣,可能會(huì)帶來一系列的問題和后果。

  1. 增加復(fù)雜性: 設(shè)計(jì)模式應(yīng)該用來簡(jiǎn)化設(shè)計(jì),但如果濫用,它們會(huì)使系統(tǒng)變得更加復(fù)雜和難以理解。這就像給一個(gè)簡(jiǎn)單的房子增加了不必要的裝飾,最終導(dǎo)致維護(hù)成本上升。
  2. 性能問題: 一些設(shè)計(jì)模式,如代理模式或裝飾器模式,可能會(huì)引入額外的間接層,這可能會(huì)影響系統(tǒng)的性能。就像在賽車上增加不必要的重量,會(huì)降低它的速度。
  3. 代碼膨脹: 濫用設(shè)計(jì)模式可能導(dǎo)致項(xiàng)目中存在大量不必要的類和接口,從而導(dǎo)致代碼膨脹。這就像是在圖書館里增加了過多的書架,而書籍卻寥寥無幾。
  4. 維護(hù)困難: 當(dāng)設(shè)計(jì)模式被不恰當(dāng)?shù)貞?yīng)用時(shí),新來的開發(fā)者可能需要花費(fèi)更多的時(shí)間來理解和維護(hù)代碼。這就像是在沒有地圖的情況下探索一個(gè)迷宮,既費(fèi)時(shí)又費(fèi)力。
  5. 違反開閉原則: 設(shè)計(jì)模式應(yīng)該幫助我們構(gòu)建可擴(kuò)展的系統(tǒng),但如果濫用,每次需要改變時(shí)都可能需要修改現(xiàn)有代碼,這違反了開閉原則。這就像是每次需要增加房間時(shí),都必須拆除現(xiàn)有房屋的一部分。
  6. 過度耦合: 不恰當(dāng)?shù)脑O(shè)計(jì)模式使用可能導(dǎo)致類和對(duì)象之間的過度耦合,使得代碼難以重用和測(cè)試。這就像是把所有的家具都固定在房間里,無法移動(dòng)或重新布置。
  7. 資源浪費(fèi): 濫用設(shè)計(jì)模式可能會(huì)導(dǎo)致資源的浪費(fèi),因?yàn)殚_發(fā)者可能花費(fèi)大量時(shí)間在不必要的設(shè)計(jì)上,而不是解決實(shí)際問題。這就像是在不需要空調(diào)的地方安裝了昂貴的空調(diào)系統(tǒng)。
  8. 項(xiàng)目延期: 由于上述所有問題,濫用設(shè)計(jì)模式可能會(huì)導(dǎo)致項(xiàng)目延期。這就像是在建造橋梁時(shí)不斷改變?cè)O(shè)計(jì),導(dǎo)致工程無法按時(shí)完成。

設(shè)計(jì)模式是工具箱中的工具,它們應(yīng)該根據(jù)項(xiàng)目的具體需求謹(jǐn)慎選擇和使用。正確的設(shè)計(jì)模式應(yīng)用可以提高代碼質(zhì)量、可維護(hù)性和可擴(kuò)展性,而濫用則可能導(dǎo)致項(xiàng)目陷入混亂和災(zāi)難。所以咱們應(yīng)該深入理解每種設(shè)計(jì)模式的適用場(chǎng)景,并在實(shí)際開發(fā)中做出明智的選擇。原創(chuàng)不易,如果文章對(duì)你有幫助,歡迎點(diǎn)贊關(guān)注威哥愛編程,學(xué)習(xí)路上我們不孤單。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)