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ò)展或測試。
在實(shí)際開發(fā)中,設(shè)計(jì)模式的誤用可能會(huì)導(dǎo)致軟件設(shè)計(jì)的問題,下面 V 哥整理了10種常見的設(shè)計(jì)模式誤用案例,來看一下:
下面我們來一一介紹這10種濫用設(shè)計(jì)模式的場景案例和分析。
濫用工廠模式通常指的是創(chuàng)建一個(gè)過于龐大和復(fù)雜的工廠類,它試圖創(chuàng)建和管理系統(tǒng)中所有不同類型的對象。這種做法違反了設(shè)計(jì)模式的意圖,即應(yīng)該保持代碼的簡潔性和可維護(hù)性。
假設(shè)我們有一個(gè)應(yīng)用程序,其中包含多個(gè)不同類型的產(chǎn)品,每個(gè)產(chǎn)品都有其特定的創(chuàng)建邏輯。在濫用工廠模式的情況下,我們可能會(huì)創(chuàng)建一個(gè)“萬能工廠”來處理所有產(chǎn)品的創(chuàng)建。
public class UniversalFactory {
// 這是一個(gè)濫用工廠模式的例子,工廠類過于龐大和復(fù)雜
public ProductA createProductA() {
// 復(fù)雜的創(chuàng)建邏輯
return new ProductA();
}
public ProductB createProductB() {
// 復(fù)雜的創(chuàng)建邏輯
return new ProductB();
}
public ProductC createProductC() {
// 復(fù)雜的創(chuàng)建邏輯
return new ProductC();
}
// ... 更多的產(chǎn)品創(chuàng)建方法
public static class ProductA {
// ProductA 的實(shí)現(xiàn)
}
public static class ProductB {
// ProductB 的實(shí)現(xiàn)
}
public static class ProductC {
// ProductC 的實(shí)現(xiàn)
}
// ... 更多的產(chǎn)品類
}
在上面的代碼中,UniversalFactory
包含了創(chuàng)建所有產(chǎn)品的方法。隨著應(yīng)用程序的發(fā)展,這個(gè)工廠類可能會(huì)變得越來越龐大,難以維護(hù)。每次添加新產(chǎn)品時(shí),都需要修改工廠類,這違反了開閉原則(對擴(kuò)展開放,對修改封閉)。
正確的做法是為每個(gè)產(chǎn)品系列創(chuàng)建專門的工廠類,這樣可以更好地組織代碼,并且每個(gè)工廠類只關(guān)注其特定的產(chǎn)品。
public class ProductAFactory {
public ProductA create() {
// 創(chuàng)建 ProductA 的邏輯
return new ProductA();
}
}
public class ProductBFactory {
public ProductB create() {
// 創(chuàng)建 ProductB 的邏輯
return new ProductB();
}
}
// ... 為其他產(chǎn)品創(chuàng)建更多的工廠類
在這個(gè)例子中,每個(gè)工廠類只負(fù)責(zé)創(chuàng)建一種類型的產(chǎn)品,這樣代碼更加清晰,也更容易維護(hù)和擴(kuò)展。如果需要添加新產(chǎn)品,只需添加一個(gè)新的工廠類,而不需要修改現(xiàn)有的工廠類。
單例模式確保一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)全局訪問點(diǎn)。然而,在一些業(yè)務(wù)場景中,過度使用單例模式可能會(huì)導(dǎo)致問題,尤其是當(dāng)單例對象的狀態(tài)需要在多個(gè)用戶或線程之間共享時(shí)。
假設(shè)我們正在開發(fā)一個(gè)多用戶的博客平臺(tái),每個(gè)用戶都可以發(fā)布博客文章。我們有一個(gè)BlogPostManager
類,負(fù)責(zé)管理博客文章的創(chuàng)建、編輯和刪除。
public class BlogPostManager {
private static BlogPostManager instance;
private List<BlogPost> blogPosts;
private BlogPostManager() {
blogPosts = new ArrayList<>();
}
public static synchronized BlogPostManager getInstance() {
if (instance == null) {
instance = new BlogPostManager();
}
return instance;
}
public void addBlogPost(BlogPost blogPost) {
blogPosts.add(blogPost);
}
public List<BlogPost> getBlogPosts() {
return blogPosts;
}
// ... 其他管理博客文章的方法
}
public class BlogPost {
private String title;
private String content;
// ... 其他博客文章屬性和方法
}
在這個(gè)例子中,BlogPostManager
被設(shè)計(jì)為單例,這意味著所有用戶共享同一個(gè)BlogPostManager
實(shí)例和它管理的博客文章列表。這在多用戶環(huán)境中是不安全的,因?yàn)橐粋€(gè)用戶的操作可能會(huì)影響其他用戶看到的數(shù)據(jù)。
正確的做法是確保每個(gè)用戶都有自己的BlogPostManager
實(shí)例,或者使用依賴注入來管理BlogPostManager
的生命周期。
public class BlogPostManager {
private List<BlogPost> blogPosts;
public BlogPostManager() {
blogPosts = new ArrayList<>();
}
public void addBlogPost(BlogPost blogPost) {
blogPosts.add(blogPost);
}
public List<BlogPost> getBlogPosts() {
return blogPosts;
}
// ... 其他管理博客文章的方法
}
// 在用戶類中
public class User {
private BlogPostManager blogPostManager;
public User(BlogPostManager blogPostManager) {
this.blogPostManager = blogPostManager;
}
// ... 用戶的方法
}
在這個(gè)例子中,每個(gè)User
對象都有一個(gè)自己的BlogPostManager
實(shí)例,這樣可以確保用戶之間的數(shù)據(jù)隔離。這種方式更適合多用戶環(huán)境,并且使得單元測試更加容易。
所以啊,單例模式應(yīng)該謹(jǐn)慎使用,特別是在需要數(shù)據(jù)隔離的多用戶系統(tǒng)中。在這些情況下,考慮使用依賴注入或其他設(shè)計(jì)模式來替代單例模式。
裝飾器模式是一種結(jié)構(gòu)型設(shè)計(jì)模式,它允許向一個(gè)現(xiàn)有的對象添加新的功能,同時(shí)又不改變其結(jié)構(gòu)。這種設(shè)計(jì)模式通過創(chuàng)建一個(gè)包裝對象,即裝飾器,來封裝實(shí)際對象。
假設(shè)我們有一個(gè)在線商店,其中有一個(gè)Product
類,表示商店中的商品。我們希望通過裝飾器模式為商品添加不同的包裝選項(xiàng),如禮品包裝。
public interface Product {
double getCost();
String getDescription();
}
public class Book implements Product {
private String title;
public Book(String title) {
this.title = title;
}
@Override
public double getCost() {
return 15.00;
}
@Override
public String getDescription() {
return "Book: " + title;
}
}
public abstract class ProductDecorator implements Product {
protected Product decoratedProduct;
public ProductDecorator(Product decoratedProduct) {
this.decoratedProduct = decoratedProduct;
}
public double getCost() {
return decoratedProduct.getCost();
}
public String getDescription() {
return decoratedProduct.getDescription();
}
}
public class GiftWrapDecorator extends ProductDecorator {
private double giftWrapCost = 3.00;
public GiftWrapDecorator(Product decoratedProduct) {
super(decoratedProduct);
}
@Override
public double getCost() {
return decoratedProduct.getCost() + giftWrapCost;
}
@Override
public String getDescription() {
return decoratedProduct.getDescription() + ", with gift wrap";
}
}
// 錯(cuò)誤使用裝飾器模式的客戶端代碼
public class Main {
public static void main(String[] args) {
Product book = new Book("Java Design Patterns");
Product giftWrappedBook = new GiftWrapDecorator(book);
System.out.println("Cost: " + giftWrappedBook.getCost()); // Cost: 18.0
System.out.println("Description: " + giftWrappedBook.getDescription()); // Description: Book: Java Design Patterns, with gift wrap
}
}
在這個(gè)例子中,GiftWrapDecorator
裝飾器正確地為Book
添加了禮品包裝功能。然而,如果我們錯(cuò)誤地將裝飾器模式應(yīng)用于不應(yīng)該被裝飾的對象,或者在裝飾器中引入了過多的邏輯,就可能導(dǎo)致問題。
正確的做法是確保裝飾器只添加必要的功能,并且裝飾器的邏輯應(yīng)該盡量簡單。
// 正確的客戶端代碼
public class Main {
public static void main(String[] args) {
Product book = new Book("Java Design Patterns");
Product giftWrappedBook = new GiftWrapDecorator(book);
System.out.println("Cost: " + giftWrappedBook.getCost()); // Cost: 18.0
System.out.println("Description: " + giftWrappedBook.getDescription()); // Description: Book: Java Design Patterns, with gift wrap
}
}
修改后的例子中,我們只添加了必要的裝飾器。如果需要更多的裝飾功能,我們應(yīng)該引入新的裝飾器類,而不是在現(xiàn)有裝飾器中添加更多的邏輯。
因此我們?nèi)苏J(rèn)識(shí)到,裝飾器模式是一種強(qiáng)大的設(shè)計(jì)模式,但應(yīng)該謹(jǐn)慎使用,確保它不會(huì)使系統(tǒng)變得過于復(fù)雜或難以維護(hù)。
繼承是一種強(qiáng)大的工具,但它應(yīng)該謹(jǐn)慎使用。不恰當(dāng)?shù)睦^承通常是由于錯(cuò)誤地將“是一個(gè)(is-a)”關(guān)系應(yīng)用于代碼中,而不是基于功能的共享。
假設(shè)我們有一個(gè)電子商務(wù)平臺(tái),需要處理不同類型的支付方式。我們可能會(huì)錯(cuò)誤地使用繼承來實(shí)現(xiàn)這些支付方式。
// 基類 PaymentMethod 錯(cuò)誤地被用作所有支付方式的父類
public abstract class PaymentMethod {
protected String name;
public PaymentMethod(String name) {
this.name = name;
}
public abstract boolean processPayment(double amount);
}
// 信用卡支付類錯(cuò)誤地繼承了 PaymentMethod
public class CreditCardPayment extends PaymentMethod {
private String cardNumber;
private String cardHolderName;
private String expirationDate;
private String cvv;
public CreditCardPayment(String name, String cardNumber, String cardHolderName, String expirationDate, String cvv) {
super(name);
this.cardNumber = cardNumber;
this.cardHolderName = cardHolderName;
this.expirationDate = expirationDate;
this.cvv = cvv;
}
@Override
public boolean processPayment(double amount) {
// 處理信用卡支付邏輯
return true;
}
}
// 銀行轉(zhuǎn)賬支付類錯(cuò)誤地繼承了 PaymentMethod
public class BankTransferPayment extends PaymentMethod {
private String accountNumber;
private String bankName;
public BankTransferPayment(String name, String accountNumber, String bankName) {
super(name);
this.accountNumber = accountNumber;
this.bankName = bankName;
}
@Override
public boolean processPayment(double amount) {
// 處理銀行轉(zhuǎn)賬支付邏輯
return true;
}
}
在這個(gè)例子中,PaymentMethod
被用作所有支付方式的基類。然而,這并不是一個(gè)恰當(dāng)?shù)氖褂美^承的情況,因?yàn)椤靶庞每ㄖЦ丁焙汀般y行轉(zhuǎn)賬支付”并不是“支付方式”的一種類型,它們是具體的支付實(shí)現(xiàn)。這種繼承關(guān)系違反了“是一個(gè)(is-a)”原則,因?yàn)樾庞每ㄖЦ恫⒉皇侵Ц斗绞降囊环N類型,而是一種具體的支付行為。
正確的做法是使用組合而不是繼承來實(shí)現(xiàn)支付方式。
// 支付方式接口
public interface PaymentMethod {
boolean processPayment(double amount);
}
// 信用卡支付類實(shí)現(xiàn)支付方式接口
public class CreditCardPayment implements PaymentMethod {
private String cardNumber;
private String cardHolderName;
private String expirationDate;
private String cvv;
public CreditCardPayment(String cardNumber, String cardHolderName, String expirationDate, String cvv) {
this.cardNumber = cardNumber;
this.cardHolderName = cardHolderName;
this.expirationDate = expirationDate;
this.cvv = cvv;
}
@Override
public boolean processPayment(double amount) {
// 處理信用卡支付邏輯
return true;
}
}
// 銀行轉(zhuǎn)賬支付類實(shí)現(xiàn)支付方式接口
public class BankTransferPayment implements PaymentMethod {
private String accountNumber;
private String bankName;
public BankTransferPayment(String accountNumber, String bankName) {
this.accountNumber = accountNumber;
this.bankName = bankName;
}
@Override
public boolean processPayment(double amount) {
// 處理銀行轉(zhuǎn)賬支付邏輯
return true;
}
}
改進(jìn)后,我們使用接口PaymentMethod
來定義支付行為,而不是使用繼承。這樣,不同的支付方式可以自由地實(shí)現(xiàn)這個(gè)接口,而不需要從特定的基類繼承。這種設(shè)計(jì)更加靈活,每個(gè)支付方式的具體實(shí)現(xiàn)都是獨(dú)立的,不會(huì)受到其他實(shí)現(xiàn)的影響。
觀察者模式是一種行為設(shè)計(jì)模式,它定義了對象之間的一對多依賴關(guān)系,當(dāng)一個(gè)對象狀態(tài)改變時(shí),所有依賴于它的對象都會(huì)得到通知。濫用觀察者模式可能會(huì)導(dǎo)致性能問題、代碼復(fù)雜性增加以及難以維護(hù)的代碼。
假設(shè)我們有一個(gè)新聞發(fā)布平臺(tái),每當(dāng)有新的新聞發(fā)布時(shí),所有訂閱者都應(yīng)該收到通知。如果系統(tǒng)中有大量的訂閱者或者通知邏輯非常復(fù)雜,濫用觀察者模式可能會(huì)導(dǎo)致問題。
import java.util.ArrayList;
import java.util.List;
// 主題接口
public interface Observer {
void update(String news);
}
// 具體主題
public class NewsPublisher {
private List<Observer> observers;
private String news;
public NewsPublisher() {
observers = new ArrayList<>();
}
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(news);
}
}
public void setNews(String news) {
this.news = news;
notifyObservers();
}
public String getNews() {
return news;
}
}
// 具體觀察者
public class NewsSubscriber implements Observer {
private String name;
public NewsSubscriber(String name) {
this.name = name;
}
@Override
public void update(String news) {
System.out.println(name + " received news: " + news);
}
}
// 客戶端代碼
public class NewsPlatform {
public static void main(String[] args) {
NewsPublisher publisher = new NewsPublisher();
publisher.addObserver(new NewsSubscriber("Subscriber 1"));
publisher.addObserver(new NewsSubscriber("Subscriber 2"));
// ... 添加更多訂閱者
// 發(fā)布新聞
publisher.setNews("Breaking news!");
}
}
在這個(gè)例子中,NewsPublisher
是一個(gè)具體主題,它維護(hù)了一個(gè)觀察者列表,并在新聞更新時(shí)通知所有觀察者。NewsSubscriber
是一個(gè)具體觀察者,它實(shí)現(xiàn)了Observer
接口,并在接收到新聞時(shí)打印出來。
正確的做法是確保觀察者模式的使用場景適合,并且觀察者的數(shù)量和通知邏輯都得到了合理控制。
// 客戶端代碼
public class NewsPlatform {
public static void main(String[] args) {
NewsPublisher publisher = new NewsPublisher();
publisher.addObserver(new NewsSubscriber("Subscriber 1"));
// 添加適量的訂閱者,而不是無限制地添加
// 發(fā)布新聞
publisher.setNews("Breaking news!");
}
}
在這個(gè)修正后的客戶端代碼中,我們只添加了適量的訂閱者。此外,我們應(yīng)該確保在不再需要接收通知時(shí),從主題中移除觀察者,以避免內(nèi)存泄漏。
最后強(qiáng)調(diào)一下,觀察者模式是一種有用的設(shè)計(jì)模式,但應(yīng)該在適當(dāng)?shù)膱鼍爸惺褂?,并且要注意控制觀察者的數(shù)量和復(fù)雜性。在設(shè)計(jì)系統(tǒng)時(shí),應(yīng)該考慮到性能和可維護(hù)性。
命令模式是一種行為設(shè)計(jì)模式,它將請求封裝為一個(gè)對象,從而允許用戶使用不同的請求、隊(duì)列或日志請求來參數(shù)化其他對象。命令模式也支持可撤銷的操作。錯(cuò)誤使用命令模式可能會(huì)導(dǎo)致系統(tǒng)復(fù)雜性增加、代碼冗余或者違反開閉原則。
假設(shè)我們有一個(gè)簡單的文本編輯器,用戶可以對文本執(zhí)行一些操作,如插入文本、刪除文本等。如果我們錯(cuò)誤地將所有操作都封裝為命令對象,即使這些操作可以通過更簡單的方法實(shí)現(xiàn),這就是錯(cuò)誤使用命令模式。
// 命令接口
public interface Command {
void execute();
}
// 簡單的文本編輯器類
public class TextEditor {
private String content = "";
public void type(String text) {
content += text;
}
public void removeLastWord() {
content = content.replaceAll("\\s+\\S+$", "");
}
public String getContent() {
return content;
}
}
// 插入文本命令類
public class TypeCommand implements Command {
private TextEditor editor;
private String text;
public TypeCommand(TextEditor editor, String text) {
this.editor = editor;
this.text = text;
}
@Override
public void execute() {
editor.type(text);
}
}
// 刪除文本命令類
public class RemoveCommand implements Command {
private TextEditor editor;
public RemoveCommand(TextEditor editor) {
this.editor = editor;
}
@Override
public void execute() {
editor.removeLastWord();
}
}
// 客戶端代碼
public class EditorClient {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
Command typeCommand = new TypeCommand(editor, "Hello World");
Command removeCommand = new RemoveCommand(editor);
typeCommand.execute();
System.out.println(editor.getContent()); // 輸出: Hello World
removeCommand.execute();
System.out.println(editor.getContent()); // 輸出: (empty string)
}
}
在這個(gè)例子中,我們?yōu)?code>TextEditor的每個(gè)操作都創(chuàng)建了一個(gè)命令對象。然而,對于這樣簡單的操作,使用命令模式可能是過度設(shè)計(jì)。這不僅增加了系統(tǒng)的復(fù)雜性,而且也增加了代碼的冗余。
正確的做法是將命令模式用于確實(shí)需要它的場合,比如操作的撤銷/重做功能、操作的排隊(duì)執(zhí)行等。
// 撤銷命令接口
public interface Command {
void execute();
void undo();
}
// 插入文本命令類
public class TypeCommand implements Command {
private TextEditor editor;
private String text;
private String previousContent;
public TypeCommand(TextEditor editor, String text) {
this.editor = editor;
this.text = text;
}
@Override
public void execute() {
previousContent = editor.getContent();
editor.type(text);
}
@Override
public void undo() {
editor.setContent(previousContent);
}
}
// 客戶端代碼
public class EditorClient {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
Command typeCommand = new TypeCommand(editor, "Hello World");
typeCommand.execute();
System.out.println(editor.getContent()); // 輸出: Hello World
typeCommand.undo();
System.out.println(editor.getContent()); // 輸出: (empty string)
}
}
在這個(gè)修正后的示例中,我們?yōu)樾枰蜂N功能的命令實(shí)現(xiàn)了undo
方法。這樣,命令模式的使用就變得更加合理,因?yàn)樗峁┝祟~外的價(jià)值,即操作的撤銷功能。
狀態(tài)模式是一種行為設(shè)計(jì)模式,它允許一個(gè)對象在其內(nèi)部狀態(tài)改變時(shí)改變其行為。這種模式非常適合于那些具有多個(gè)狀態(tài),并且在不同狀態(tài)下行為有顯著差異的對象。不恰當(dāng)?shù)氖褂脿顟B(tài)模式可能會(huì)導(dǎo)致設(shè)計(jì)復(fù)雜、難以維護(hù)和理解。
假設(shè)我們有一個(gè)簡單的交通信號(hào)燈系統(tǒng),它有三個(gè)狀態(tài):紅燈、綠燈和黃燈。每個(gè)狀態(tài)持續(xù)一定時(shí)間后會(huì)轉(zhuǎn)換到下一個(gè)狀態(tài)。如果我們錯(cuò)誤地使用狀態(tài)模式來處理這個(gè)簡單的順序邏輯,就可能導(dǎo)致不恰當(dāng)?shù)脑O(shè)計(jì)。
// 狀態(tài)接口
public interface TrafficLightState {
void change(TrafficLight light);
}
// 紅燈狀態(tài)類
public class RedLight implements TrafficLightState {
@Override
public void change(TrafficLight light) {
System.out.println("Red light on");
// 假設(shè)紅燈持續(xù)一段時(shí)間后自動(dòng)切換到綠燈
light.setState(new GreenLight());
}
}
// 綠燈狀態(tài)類
public class GreenLight implements TrafficLightState {
@Override
public void change(TrafficLight light) {
System.out.println("Green light on");
// 假設(shè)綠燈持續(xù)一段時(shí)間后自動(dòng)切換到黃燈
light.setState(new YellowLight());
}
}
// 黃燈狀態(tài)類
public class YellowLight implements TrafficLightState {
@Override
public void change(TrafficLight light) {
System.out.println("Yellow light on");
// 假設(shè)黃燈持續(xù)一段時(shí)間后自動(dòng)切換到紅燈
light.setState(new RedLight());
}
}
// 交通信號(hào)燈類
public class TrafficLight {
private TrafficLightState state;
public TrafficLight() {
this.state = new RedLight(); // 初始狀態(tài)為紅燈
}
public void setState(TrafficLightState state) {
this.state = state;
}
public void change() {
state.change(this);
}
}
// 客戶端代碼
public class TrafficLightSystem {
public static void main(String[] args) {
TrafficLight light = new TrafficLight();
// 模擬信號(hào)燈變化
light.change(); // 紅燈
light.change(); // 綠燈
light.change(); // 黃燈
light.change(); // 再次紅燈
}
}
在這個(gè)例子中,我們?yōu)榻煌ㄐ盘?hào)燈的每個(gè)狀態(tài)都創(chuàng)建了一個(gè)狀態(tài)類。然而,對于這樣一個(gè)簡單的順序邏輯,使用狀態(tài)模式可能是過度設(shè)計(jì)。這不僅增加了系統(tǒng)的復(fù)雜性,而且也增加了代碼的冗余。
change
方法,這可能導(dǎo)致代碼冗余。正確的做法是將狀態(tài)模式用于確實(shí)需要它的場合,比如狀態(tài)轉(zhuǎn)換邏輯復(fù)雜、狀態(tài)之間有顯著不同的行為或者需要記錄狀態(tài)歷史等。
// 交通信號(hào)燈類(簡化版)
public class TrafficLight {
private String[] lights = {"Red", "Green", "Yellow"};
private int index = 0; // 初始狀態(tài)為紅燈
public void change() {
System.out.println(lights[index] + " light on");
index = (index + 1) % lights.length; // 循環(huán)切換狀態(tài)
}
}
// 客戶端代碼
public class TrafficLightSystem {
public static void main(String[] args) {
TrafficLight light = new TrafficLight();
// 模擬信號(hào)燈變化
light.change(); // 紅燈
light.change(); // 綠燈
light.change(); // 黃燈
light.change(); // 再次紅燈
}
}
在這個(gè)修正后的示例中,我們使用一個(gè)簡單的索引和數(shù)組來管理信號(hào)燈的狀態(tài),而不是使用狀態(tài)模式。這種設(shè)計(jì)更加簡潔和直觀,適合處理簡單的狀態(tài)轉(zhuǎn)換邏輯。
代理模式是一種結(jié)構(gòu)型設(shè)計(jì)模式,它提供了對另一個(gè)對象的代理以控制對這個(gè)對象的訪問。代理模式有多種類型,包括虛擬代理、遠(yuǎn)程代理、保護(hù)代理和智能引用代理等。錯(cuò)誤使用代理模式可能會(huì)導(dǎo)致系統(tǒng)設(shè)計(jì)過于復(fù)雜、性能降低或者違反設(shè)計(jì)原則。
假設(shè)我們有一個(gè)圖像加載系統(tǒng),需要從網(wǎng)絡(luò)加載圖像。如果我們錯(cuò)誤地為每個(gè)圖像對象創(chuàng)建一個(gè)代理,即使這些圖像對象不需要代理提供的額外功能,這就是錯(cuò)誤使用代理模式。
// 目標(biāo)接口
public interface Image {
void display();
}
// 目標(biāo)類
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName) {
System.out.println("Loading " + fileName + " from disk");
}
}
// 代理類
public class ImageProxy implements Image {
private RealImage realImage;
private String fileName;
public ImageProxy(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
}
// 客戶端代碼
public class ProxyExample {
public static void main(String[] args) {
Image image = new ImageProxy("image1.jpg");
image.display();
}
}
在這個(gè)例子中,ImageProxy
代理類包裝了 RealImage
目標(biāo)類。然而,如果 RealImage
類的實(shí)例化和使用非常簡單,并且不需要延遲加載或其他代理功能,那么使用代理模式就是不恰當(dāng)?shù)?。這可能會(huì)導(dǎo)致不必要的性能開銷和設(shè)計(jì)復(fù)雜性。
正確的做法是將代理模式用于確實(shí)需要它的場合,比如需要控制對資源的訪問、延遲加載資源或者提供額外的安全控制等。
// 虛擬代理類
public class VirtualImageProxy implements Image {
private RealImage realImage;
private String fileName;
public VirtualImageProxy(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
public void loadImage() {
System.out.println("Loading " + fileName + " from network");
}
}
// 客戶端代碼
public class ProxyExample {
public static void main(String[] args) {
Image image = new VirtualImageProxy("image1.jpg");
image.loadImage(); // 模擬從網(wǎng)絡(luò)加載圖像
image.display();
}
}
在這個(gè)修正后的示例中,VirtualImageProxy
類提供了延遲加載的功能,只有在實(shí)際需要顯示圖像時(shí)才從網(wǎng)絡(luò)加載。這種設(shè)計(jì)更加合理,因?yàn)樗峁┝舜砟J降膶?shí)際價(jià)值,即延遲加載和優(yōu)化性能。
策略模式是一種行為設(shè)計(jì)模式,它定義了一系列的算法,并將每一個(gè)算法封裝起來,使它們可以互換使用。策略模式的用意是使算法的變化不會(huì)影響到使用算法的用戶。濫用策略模式可能會(huì)導(dǎo)致設(shè)計(jì)過于復(fù)雜,增加不必要的類數(shù)量,以及使得代碼難以理解和維護(hù)。
假設(shè)我們有一個(gè)簡單的計(jì)算器程序,它可以執(zhí)行加、減、乘、除四種基本運(yùn)算。如果我們?yōu)槊恳环N操作都創(chuàng)建一個(gè)策略類,即使這些操作可以通過更簡單的方法實(shí)現(xiàn),這就是濫用策略模式。
// 策略接口
public interface CalculationStrategy {
int calculate(int a, int b);
}
// 加法策略類
public class AddStrategy implements CalculationStrategy {
@Override
public int calculate(int a, int b) {
return a + b;
}
}
// 減法策略類
public class SubtractStrategy implements CalculationStrategy {
@Override
public int calculate(int a, int b) {
return a - b;
}
}
// 乘法策略類
public class MultiplyStrategy implements CalculationStrategy {
@Override
public int calculate(int a, int b) {
return a * b;
}
}
// 除法策略類
public class DivideStrategy implements CalculationStrategy {
@Override
public int calculate(int a, int b) {
if (b != 0) {
return a / b;
} else {
throw new IllegalArgumentException("Divider cannot be zero.");
}
}
}
// 計(jì)算器上下文
public class Calculator {
private CalculationStrategy strategy;
public Calculator(CalculationStrategy strategy) {
this.strategy = strategy;
}
public void setStrategy(CalculationStrategy strategy) {
this.strategy = strategy;
}
public int performCalculation(int a, int b) {
return strategy.calculate(a, b);
}
}
// 客戶端代碼
public class StrategyPatternExample {
public static void main(String[] args) {
Calculator calculator = new Calculator(new AddStrategy());
System.out.println("10 + 5 = " + calculator.performCalculation(10, 5));
calculator.setStrategy(new SubtractStrategy());
System.out.println("10 - 5 = " + calculator.performCalculation(10, 5));
calculator.setStrategy(new MultiplyStrategy());
System.out.println("10 * 5 = " + calculator.performCalculation(10, 5));
calculator.setStrategy(new DivideStrategy());
System.out.println("10 / 5 = " + calculator.performCalculation(10, 5));
}
}
在這個(gè)例子中,我們?yōu)橛?jì)算器的每一種操作都創(chuàng)建了一個(gè)策略類。然而,對于這樣簡單的操作,使用策略模式可能是過度設(shè)計(jì)。這不僅增加了系統(tǒng)的復(fù)雜性,而且也增加了代碼的冗余。
正確的做法是將策略模式用于確實(shí)需要它的場合,比如算法的選擇會(huì)頻繁變化,或者需要在運(yùn)行時(shí)根據(jù)不同的條件選擇不同的算法。
// 計(jì)算器類(簡化版)
public class SimpleCalculator {
public int add(int a, int b) {
return a + b;
}
public int subtract(int a, int b) {
return a - b;
}
public int multiply(int a, int b) {
return a * b;
}
public int divide(int a, int b) {
if (b != 0) {
return a / b;
} else {
throw new IllegalArgumentException("Divider cannot be zero.");
}
}
}
// 客戶端代碼
public class SimpleCalculatorExample {
public static void main(String[] args) {
SimpleCalculator calculator = new SimpleCalculator();
System.out.println("10 + 5 = " + calculator.add(10, 5));
System.out.println("10 - 5 = " + calculator.subtract(10, 5));
System.out.println("10 * 5 = " + calculator.multiply(10, 5));
System.out.println("10 / 5 = " + calculator.divide(10, 5));
}
}
修正后的代碼,我們直接在計(jì)算器類中實(shí)現(xiàn)了所有的操作,而不是使用策略模式。這種設(shè)計(jì)更加簡潔和直觀,適合處理簡單的操作。如果未來需要添加新的算法或者改變算法的行為,可以考慮使用策略模式來提高靈活性。
組合(Composition)和聚合(Aggregation)是表示整體與部分關(guān)系的方式,它們都是關(guān)聯(lián)的特殊形式。不恰當(dāng)?shù)厥褂媒M合或聚合可能會(huì)導(dǎo)致對象的生命周期管理混亂、職責(zé)不明確或者設(shè)計(jì)過于復(fù)雜。
假設(shè)我們有一個(gè)公司管理系統(tǒng),其中包含公司和員工的關(guān)系。如果錯(cuò)誤地使用組合或聚合,可能會(huì)導(dǎo)致管理混亂,比如錯(cuò)誤地刪除公司時(shí)也刪除了員工。
// 員工類
public class Employee {
private String name;
private Company company; // 員工所屬的公司
public Employee(String name, Company company) {
this.name = name;
this.company = company;
}
// ... 員工的其他方法
}
// 公司類
public class Company {
private String name;
private List<Employee> employees; // 公司的員工列表
public Company(String name) {
this.name = name;
this.employees = new ArrayList<>();
}
public void addEmployee(Employee employee) {
employees.add(employee);
employee.setCompany(this); // 錯(cuò)誤地在添加員工時(shí)設(shè)置公司
}
public void removeEmployee(Employee employee) {
employees.remove(employee);
employee.setCompany(null); // 錯(cuò)誤地在刪除員工時(shí)取消設(shè)置公司
}
// ... 公司的其他方法
}
// 客戶端代碼
public class CompanyManagementSystem {
public static void main(String[] args) {
Company company = new Company("Moonshot AI");
Employee employee1 = new Employee("Kimi", company);
Employee employee2 = new Employee("Alex", company);
company.addEmployee(employee1);
company.addEmployee(employee2);
// 錯(cuò)誤地刪除公司,同時(shí)刪除了所有員工
company = null; // 這將導(dǎo)致員工對象也被垃圾回收
}
}
在這個(gè)例子中,Company
類通過 employees
列表與 Employee
類建立了一種關(guān)系。然而,當(dāng)公司被設(shè)置為 null
時(shí),由于員工對象仍然持有對公司的引用,這可能會(huì)導(dǎo)致不一致的狀態(tài),因?yàn)閱T工對象仍然認(rèn)為它們屬于一個(gè)已經(jīng)不存在的公司。
正確的做法是確保組合/聚合關(guān)系反映了實(shí)際的業(yè)務(wù)邏輯,并且對象的生命周期得到了正確的管理。
// 員工類
public class Employee {
private String name;
private Company company; // 員工所屬的公司
public Employee(String name) {
this.name = name;
}
// ... 員工的其他方法
public void setCompany(Company company) {
this.company = company;
}
public Company getCompany() {
return company;
}
}
// 公司類
public class Company {
private String name;
private List<Employee> employees; // 公司的員工列表
public Company(String name) {
this.name = name;
this.employees = new ArrayList<>();
}
public void addEmployee(Employee employee) {
employees.add(employee);
employee.setCompany(this); // 正確地在添加員工時(shí)設(shè)置公司
}
public void removeEmployee(Employee employee) {
employees.remove(employee);
// 不取消設(shè)置公司,因?yàn)閱T工可能仍然需要知道他們之前屬于哪個(gè)公司
}
// ... 公司的其他方法
}
// 客戶端代碼
public class CompanyManagementSystem {
public static void main(String[] args) {
Company company = new Company("Moonshot AI");
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("Kimi"));
employees.add(new Employee("Alex"));
for (Employee employee : employees) {
company.addEmployee(employee);
}
// 刪除公司時(shí),員工列表被清空,但員工對象仍然存在
company = null;
// 員工對象不會(huì)被垃圾回收,因?yàn)樗鼈冊?employees 列表中仍然有引用
}
}
修正后的代碼,我們確保了員工對象的生命周期獨(dú)立于公司對象。即使公司對象被刪除,員工對象仍然存在,并且可以通過其他方式進(jìn)行管理。這種設(shè)計(jì)更加符合實(shí)際的業(yè)務(wù)邏輯,并且使得對象的生命周期管理更加清晰。
濫用設(shè)計(jì)模式,就像在沒有正確診斷的情況下給病人開藥一樣,可能會(huì)帶來一系列的問題和后果。
設(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ì)模式的適用場景,并在實(shí)際開發(fā)中做出明智的選擇。原創(chuàng)不易,如果文章對你有幫助,歡迎點(diǎn)贊關(guān)注威哥愛編程,學(xué)習(xí)路上我們不孤單。
更多建議: