C#軟件架構(gòu)設(shè)計(jì)原則
軟件架構(gòu)設(shè)計(jì)原則
學(xué)習(xí)設(shè)計(jì)原則是學(xué)習(xí)設(shè)計(jì)模式的基礎(chǔ)。在實(shí)際的開(kāi)發(fā)過(guò)程中,并不是一定要求所有的代碼都遵循設(shè)計(jì)原則,而是要綜合考慮人力、成本、時(shí)間、質(zhì)量,不刻意追求完美,要在適當(dāng)?shù)膱?chǎng)景遵循設(shè)計(jì)原則。
這體現(xiàn)的是一種平衡取舍,可以幫助我們?cè)O(shè)計(jì)出更加優(yōu)雅的代碼結(jié)構(gòu)。
分別用一句話歸納總結(jié)軟件設(shè)計(jì)七大原則,如下表所示。
| 設(shè)計(jì)原則 | 一句話歸納 | 目的 |
|---|---|---|
| 開(kāi)閉原則 | 對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉 | 降低對(duì)維護(hù)帶來(lái)的新風(fēng)險(xiǎn) |
| 依賴(lài)倒置原則 | 高層不應(yīng)該依賴(lài)底層 | 更利于代碼結(jié)構(gòu)的升級(jí)擴(kuò)展 |
| 單一職責(zé)原則 | 一個(gè)類(lèi)只干一件事 | 便于理解,提高代碼的可讀性 |
| 接口隔離原則 | 一個(gè)接口只干一件事 | 功能解耦,高聚合、低耦合 |
| 迪米特法則 | 不該知道的不要知道 | 只和朋友交流,不和陌生人說(shuō)話,減少代碼臃腫 |
| 里氏替換原則 | 子類(lèi)重寫(xiě)方式功能發(fā)生改變,不應(yīng)該影響父類(lèi)方法的含義 | 防止繼承泛濫 |
| 合成復(fù)用原則 | 盡量使用組合實(shí)現(xiàn)代碼復(fù)用,而不使用繼承 | 降低代碼耦合 |
當(dāng)使用C#編程語(yǔ)言時(shí),可以通過(guò)以下示例來(lái)說(shuō)明開(kāi)閉原則的應(yīng)用:
假設(shè)我們正在設(shè)計(jì)一個(gè)圖形繪制應(yīng)用程序,其中包含不同類(lèi)型的圖形(如圓形、矩形、三角形等)。
我們希望能夠根據(jù)需要輕松地添加新的圖形類(lèi)型,同時(shí)保持現(xiàn)有代碼的穩(wěn)定性。
首先,我們定義一個(gè)抽象基類(lèi) Shape 來(lái)表示所有圖形的通用屬性和行為:
public abstract class Shape
{
public abstract void Draw();
}
然后,我們創(chuàng)建具體的圖形類(lèi),如 Circle、Rectangle 和 Triangle,它們都繼承自 Shape 基類(lèi),并實(shí)現(xiàn)了 Draw() 方法:
public class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle");
}
}
public class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a rectangle");
}
}
public class Triangle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a triangle");
}
}
現(xiàn)在,如果我們需要添加新的圖形類(lèi)型(例如橢圓),只需創(chuàng)建一個(gè)新的類(lèi)并繼承自 Shape 類(lèi)即可。這樣做不會(huì)影響現(xiàn)有代碼,并且可以輕松地?cái)U(kuò)展應(yīng)用程序。
public class Ellipse : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing an ellipse");
}
}
在應(yīng)用程序的其他部分,我們可以使用 Shape 類(lèi)型的對(duì)象來(lái)繪制不同的圖形,而無(wú)需關(guān)心具體的圖形類(lèi)型。這樣,我們遵循了開(kāi)閉原則,對(duì)擴(kuò)展開(kāi)放(通過(guò)添加新的圖形類(lèi)型),對(duì)修改關(guān)閉(不需要修改現(xiàn)有代碼)。
public class DrawingProgram
{
public void DrawShapes(List<Shape> shapes)
{
foreach (var shape in shapes)
{
shape.Draw();
}
}
}
使用示例:
var shapes = new List<Shape>
{
new Circle(),
new Rectangle(),
new Triangle(),
new Ellipse()
};
var drawingProgram = new DrawingProgram();
drawingProgram.DrawShapes(shapes);
輸出結(jié)果:
Drawing a circle
Drawing a rectangle
Drawing a triangle
Drawing an ellipse
通過(guò)遵循開(kāi)閉原則,我們可以輕松地?cái)U(kuò)展應(yīng)用程序并添加新的圖形類(lèi)型,而無(wú)需修改現(xiàn)有代碼。這樣可以提高代碼的可維護(hù)性和可擴(kuò)展性,并支持軟件系統(tǒng)的演化和變化。
單一職責(zé)示例
單一職責(zé)原則(Single Responsibility Principle,SRP)要求一個(gè)類(lèi)應(yīng)該只有一個(gè)引起它變化的原因。換句話說(shuō),一個(gè)類(lèi)應(yīng)該只負(fù)責(zé)一項(xiàng)職責(zé)或功能。
下面是一個(gè)使用C#示例來(lái)說(shuō)明單一職責(zé)原則的應(yīng)用:
假設(shè)我們正在開(kāi)發(fā)一個(gè)學(xué)生管理系統(tǒng),其中包含學(xué)生信息的錄入和展示功能。我們可以將這個(gè)系統(tǒng)分為兩個(gè)類(lèi):Student 和 StudentManager。
首先,定義 Student 類(lèi)來(lái)表示學(xué)生對(duì)象,并包含與學(xué)生相關(guān)的屬性和方法:
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
// 其他與學(xué)生相關(guān)的屬性和方法...
}
然后,創(chuàng)建 StudentManager 類(lèi)來(lái)處理與學(xué)生信息管理相關(guān)的操作,如錄入、查詢和展示等:
public class StudentManager
{
private List<Student> students;
public StudentManager()
{
students = new List<Student>();
}
public void AddStudent(Student student)
{
// 將學(xué)生信息添加到列表中...
students.Add(student);
Console.WriteLine("Student added successfully.");
}
public void DisplayStudents()
{
// 展示所有學(xué)生信息...
foreach (var student in students)
{
Console.WriteLine($"Name: {student.Name}, Age: {student.Age}");
}
}
}
在這個(gè)例子中,Student 類(lèi)負(fù)責(zé)表示單個(gè)學(xué)生對(duì)象,并封裝了與學(xué)生相關(guān)的屬性。而 StudentManager 類(lèi)負(fù)責(zé)處理學(xué)生信息的管理操作,如添加學(xué)生和展示學(xué)生信息。
使用示例:
var student1 = new Student { Name = "Alice", Age = 20 };
var student2 = new Student { Name = "Bob", Age = 22 };
var studentManager = new StudentManager();
studentManager.AddStudent(student1);
studentManager.AddStudent(student2);
studentManager.DisplayStudents();
輸出結(jié)果:
Student added successfully.
Student added successfully.
Name: Alice, Age: 20
Name: Bob, Age: 22
通過(guò)將學(xué)生對(duì)象的表示和管理操作分別封裝在不同的類(lèi)中,我們遵循了單一職責(zé)原則。Student 類(lèi)只負(fù)責(zé)表示學(xué)生對(duì)象的屬性,而 StudentManager 類(lèi)只負(fù)責(zé)處理與學(xué)生信息管理相關(guān)的操作。這樣可以提高代碼的可維護(hù)性和可擴(kuò)展性,并使每個(gè)類(lèi)都具有清晰明確的職責(zé)。
里式替換
里氏替換原則(Liskov Substitution Principle,LSP)要求子類(lèi)型必須能夠替換其基類(lèi)型,并且不會(huì)破壞程序的正確性。也就是說(shuō),子類(lèi)可以在不影響程序正確性和預(yù)期行為的情況下替代父類(lèi)。
下面是一個(gè)使用C#示例來(lái)說(shuō)明里式替換原則的應(yīng)用:
假設(shè)我們正在開(kāi)發(fā)一個(gè)圖形繪制應(yīng)用程序,其中包含多種形狀(如圓形、矩形等)。我們希望能夠根據(jù)用戶選擇的形狀類(lèi)型進(jìn)行繪制操作。
首先,定義一個(gè)抽象基類(lèi) Shape 來(lái)表示所有形狀對(duì)象,并聲明一個(gè)抽象方法 Draw 用于繪制該形狀:
public abstract class Shape
{
public abstract void Draw();
}
然后,創(chuàng)建具體的子類(lèi)來(lái)表示不同的形狀。例如,創(chuàng)建 Circle 類(lèi)和 Rectangle 類(lèi)分別表示圓形和矩形,并實(shí)現(xiàn)它們自己特定的繪制邏輯:
public class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle...");
}
}
public class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a rectangle...");
}
}
在這個(gè)例子中,每個(gè)具體的子類(lèi)都可以替代其基類(lèi) Shape 并實(shí)現(xiàn)自己特定的繪制邏輯。這符合里式替換原則,因?yàn)闊o(wú)論是 Circle 還是 Rectangle 都可以在不破壞程序正確性和預(yù)期行為的情況下替代 Shape。
使用示例:
Shape circle = new Circle();
circle.Draw(); // 輸出 "Drawing a circle..."
Shape rectangle = new Rectangle();
rectangle.Draw(); // 輸出 "Drawing a rectangle..."
通過(guò)將具體的子類(lèi)對(duì)象賦值給基類(lèi)引用變量,并調(diào)用相同的方法,我們可以看到不同形狀的繪制操作被正確地執(zhí)行。這證明了里式替換原則的有效性。
總結(jié):里式替換原則要求子類(lèi)型必須能夠替代其基類(lèi)型,并且不會(huì)破壞程序正確性。在C#中,我們可以通過(guò)創(chuàng)建具體的子類(lèi)來(lái)表示不同形狀,并確保它們能夠在繼承自抽象基類(lèi)時(shí)正確地實(shí)現(xiàn)自己特定的行為。這樣可以提高代碼的可擴(kuò)展性和靈活性,并使得代碼更易于維護(hù)和重用。
依賴(lài)倒置
依賴(lài)倒置原則(Dependency Inversion Principle,DIP)要求高層模塊不應(yīng)該依賴(lài)于低層模塊的具體實(shí)現(xiàn),而是應(yīng)該依賴(lài)于抽象。同時(shí),抽象不應(yīng)該依賴(lài)于具體實(shí)現(xiàn)細(xì)節(jié),而是應(yīng)該由高層模塊定義。
下面是一個(gè)使用C#示例來(lái)說(shuō)明依賴(lài)倒置原則的應(yīng)用:
假設(shè)我們正在開(kāi)發(fā)一個(gè)電子商務(wù)系統(tǒng),其中包含訂單處理和支付功能。我們希望能夠根據(jù)用戶選擇的支付方式進(jìn)行訂單支付操作。
首先,定義一個(gè)抽象接口 IPaymentProcessor 來(lái)表示支付處理器,并聲明一個(gè)方法 ProcessPayment 用于執(zhí)行訂單支付:
public interface IPaymentProcessor
{
void ProcessPayment(decimal amount);
}
然后,在具體的實(shí)現(xiàn)類(lèi)中分別實(shí)現(xiàn)不同的支付方式。例如,創(chuàng)建 CreditCardPaymentProcessor 類(lèi)和 PayPalPaymentProcessor 類(lèi)分別表示信用卡和PayPal支付,并實(shí)現(xiàn)它們自己特定的支付邏輯:
public class CreditCardPaymentProcessor : IPaymentProcessor
{
public void ProcessPayment(decimal amount)
{
Console.WriteLine($"Processing credit card payment of {amount} dollars...");
// 具體信用卡支付邏輯...
}
}
public class PayPalPaymentProcessor : IPaymentProcessor
{
public void ProcessPayment(decimal amount)
{
Console.WriteLine($"Processing PayPal payment of {amount} dollars...");
// 具體PayPal支付邏輯...
}
}
在這個(gè)例子中,每個(gè)具體的支付處理器都實(shí)現(xiàn)了 IPaymentProcessor 接口,并提供了自己特定的支付邏輯。這樣,高層模塊(訂單處理模塊)就可以依賴(lài)于抽象接口 IPaymentProcessor 而不是具體的實(shí)現(xiàn)類(lèi)。
使用示例:
public class OrderProcessor
{
private IPaymentProcessor paymentProcessor;
public OrderProcessor(IPaymentProcessor paymentProcessor)
{
this.paymentProcessor = paymentProcessor;
}
public void ProcessOrder(decimal amount)
{
// 處理訂單邏輯...
// 使用依賴(lài)注入的方式調(diào)用支付處理器
paymentProcessor.ProcessPayment(amount);
// 其他訂單處理邏輯...
}
}
// 在應(yīng)用程序中配置和使用不同的支付方式
var creditCardPayment = new CreditCardPaymentProcessor();
var payPalPayment = new PayPalPaymentProces
接口隔離
接口隔離原則(Interface Segregation Principle,ISP)要求客戶端不應(yīng)該依賴(lài)于它們不使用的接口。一個(gè)類(lèi)應(yīng)該只依賴(lài)于它需要的接口,而不是依賴(lài)于多余的接口。
下面是一個(gè)使用C#示例來(lái)說(shuō)明接口隔離原則的應(yīng)用:
假設(shè)我們正在開(kāi)發(fā)一個(gè)文件管理系統(tǒng),其中包含文件上傳和文件下載功能。我們希望能夠根據(jù)用戶需求提供相應(yīng)的功能。
首先,定義兩個(gè)接口 IFileUploadable 和 IFileDownloadable 來(lái)表示文件上傳和文件下載功能,并分別聲明相應(yīng)的方法:
public interface IFileUploadable
{
void UploadFile(string filePath);
}
public interface IFileDownloadable
{
void DownloadFile(string fileId);
}
然后,在具體的實(shí)現(xiàn)類(lèi)中分別實(shí)現(xiàn)這兩個(gè)功能。例如,創(chuàng)建 LocalFileManager 類(lèi)來(lái)處理本地文件操作,并實(shí)現(xiàn)對(duì)應(yīng)的方法:
public class LocalFileManager : IFileUploadable, IFileDownloadable
{
public void UploadFile(string filePath)
{
Console.WriteLine($"Uploading file from local path: {filePath}");
// 具體本地上傳邏輯...
}
public void DownloadFile(string fileId)
{
Console.WriteLine($"Downloading file with ID: {fileId} to local path");
// 具體本地下載邏輯...
}
}
在這個(gè)例子中,每個(gè)具體的實(shí)現(xiàn)類(lèi)只關(guān)注自己需要用到的接口方法,而不需要實(shí)現(xiàn)多余的方法。這符合接口隔離原則,因?yàn)榭蛻舳丝梢愿鶕?jù)需要依賴(lài)于相應(yīng)的接口。
使用示例:
public class FileManagerClient
{
private IFileUploadable fileUploader;
private IFileDownloadable fileDownloader;
public FileManagerClient(IFileUploadable fileUploader, IFileDownloadable fileDownloader)
{
this.fileUploader = fileUploader;
this.fileDownloader = fileDownloader;
}
public void UploadAndDownloadFiles(string filePath, string fileId)
{
// 使用文件上傳功能
fileUploader.UploadFile(filePath);
// 使用文件下載功能
fileDownloader.DownloadFile(fileId);
// 其他操作...
}
}
// 在應(yīng)用程序中配置和使用具體的文件管理類(lèi)
var localFileManager = new LocalFileManager();
var client = new FileManagerClient(localFileManager, localFileManager);
client.UploadAndDownloadFiles("path/to/file", "123456");
通過(guò)依賴(lài)注入的方式,我們可以將具體的實(shí)現(xiàn)類(lèi)傳遞給客戶端,并根據(jù)需要調(diào)用相應(yīng)的接口方法。這樣就遵循了接口隔離原則,使得客戶端只依賴(lài)于它們所需的接口,并且不會(huì)受到多余方法的影響。這提高了代碼的可維護(hù)性和靈活性,并促進(jìn)了代碼重用和擴(kuò)展。
迪米特
迪米特法則(Law of Demeter,LoD),也稱(chēng)為最少知識(shí)原則(Principle of Least Knowledge),要求一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象有盡可能少的了解。一個(gè)類(lèi)不應(yīng)該直接與其他類(lèi)耦合,而是通過(guò)中間類(lèi)進(jìn)行通信。
下面是一個(gè)使用C#示例來(lái)說(shuō)明迪米特法則的應(yīng)用:
假設(shè)我們正在開(kāi)發(fā)一個(gè)社交網(wǎng)絡(luò)系統(tǒng),其中包含用戶、好友和消息等功能。我們希望能夠?qū)崿F(xiàn)用戶發(fā)送消息給好友的功能。
首先,定義三個(gè)類(lèi) User、Friend 和 Message 來(lái)表示用戶、好友和消息,并在 User 類(lèi)中實(shí)現(xiàn)發(fā)送消息的方法:
public class User
{
private string name;
private List<Friend> friends;
public User(string name)
{
this.name = name;
this.friends = new List<Friend>();
}
public void AddFriend(Friend friend)
{
friends.Add(friend);
}
public void SendMessageToFriends(string messageContent)
{
Message message = new Message(messageContent);
foreach (Friend friend in friends)
{
friend.ReceiveMessage(message);
}
Console.WriteLine($"User {name} sent a message to all friends.");
}
}
public class Friend
{
private string name;
public Friend(string name)
{
this.name = name;
}
public void ReceiveMessage(Message message)
{
Console.WriteLine($"Friend {name} received a message: {message.Content}");
// 處理接收到的消息...
}
}
public class Message
{
public string Content { get; set; }
public Message(string content)
{
Content = content;
}
}
在這個(gè)例子中,User 類(lèi)表示用戶,Friend 類(lèi)表示好友,Message 類(lèi)表示消息。用戶可以添加好友,并通過(guò) SendMessageToFriends 方法向所有好友發(fā)送消息。
使用示例:
User user1 = new User("Alice");
User user2 = new User("Bob");
Friend friend1 = new Friend("Charlie");
Friend friend2 = new Friend("David");
user1.AddFriend(friend1);
user2.AddFriend(friend2);
user1.SendMessageToFriends("Hello, friends!");
在這個(gè)示例中,用戶對(duì)象只與好友對(duì)象進(jìn)行通信,并不直接與消息對(duì)象進(jìn)行通信。這符合迪米特法則的要求,即一個(gè)對(duì)象應(yīng)該盡可能少地了解其他對(duì)象。
通過(guò)將消息發(fā)送的責(zé)任委托給好友對(duì)象,在用戶類(lèi)中只需要調(diào)用 friend.ReceiveMessage(message) 方法來(lái)發(fā)送消息給所有好友。這樣可以降低類(lèi)之間的耦合性,并提高代碼的可維護(hù)性和靈活性。
合成復(fù)用
合成復(fù)用原則(Composite Reuse Principle,CRP)要求盡量使用對(duì)象組合,而不是繼承來(lái)達(dá)到復(fù)用的目的。通過(guò)將現(xiàn)有對(duì)象組合起來(lái)創(chuàng)建新的對(duì)象,可以更靈活地實(shí)現(xiàn)功能的復(fù)用和擴(kuò)展。
下面是一個(gè)使用C#示例來(lái)說(shuō)明合成復(fù)用原則的應(yīng)用:
假設(shè)我們正在開(kāi)發(fā)一個(gè)圖形庫(kù),其中包含各種形狀(如圓形、矩形等)。我們希望能夠?qū)崿F(xiàn)一個(gè)可以繪制多個(gè)形狀的畫(huà)板。
首先,定義一個(gè)抽象基類(lèi) Shape 來(lái)表示圖形,并聲明抽象方法 Draw:
public abstract class Shape
{
public abstract void Draw();
}
然后,在具體的子類(lèi)中分別實(shí)現(xiàn)各種形狀。例如,創(chuàng)建 Circle 類(lèi)和 Rectangle 類(lèi)來(lái)表示圓形和矩形,并重寫(xiě)父類(lèi)中的 Draw 方法:
public class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle");
// 具體繪制圓形邏輯...
}
}
public class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a rectangle");
// 具體繪制矩形邏輯...
}
}
接下來(lái),創(chuàng)建一個(gè)畫(huà)板類(lèi) Canvas 來(lái)管理并繪制多個(gè)圖形。在該類(lèi)中使用對(duì)象組合將多個(gè)圖形組合在一起:
public class Canvas
{
private List<Shape> shapes;
public Canvas()
{
shapes = new List<Shape>();
}
public void AddShape(Shape shape)
{
shapes.Add(shape);
}
public void DrawShapes()
{
foreach (Shape shape in shapes)
{
shape.Draw();
}
Console.WriteLine("All shapes are drawn.");
}
}
在這個(gè)例子中,Canvas 類(lèi)通過(guò)對(duì)象組合的方式將多個(gè)圖形對(duì)象組合在一起,并提供了添加圖形和繪制圖形的方法。
使用示例:
Canvas canvas = new Canvas();
Circle circle = new Circle();
Rectangle rectangle = new Rectangle();
canvas.AddShape(circle);
canvas.AddShape(rectangle);
canvas.DrawShapes();
在這個(gè)示例中,我們創(chuàng)建了一個(gè)畫(huà)板對(duì)象 canvas,并向其中添加了一個(gè)圓形和一個(gè)矩形。然后調(diào)用 DrawShapes 方法來(lái)繪制所有的圖形。
通過(guò)使用對(duì)象組合而不是繼承,我們可以更靈活地實(shí)現(xiàn)功能的復(fù)用和擴(kuò)展。例如,可以輕松地添加新的圖形類(lèi)型或修改現(xiàn)有圖形類(lèi)型的行為,而不會(huì)影響到畫(huà)板類(lèi)。這符合合成復(fù)用原則,并提高了代碼的可維護(hù)性和靈活性。
轉(zhuǎn)自:明志德道
鏈接:cnblogs.com/for-easy-fast/p/17762706.html
