軟件架構(gòu)設(shè)計(jì)原則和示例介紹
常見(jiàn)的架構(gòu)設(shè)計(jì)原則包括以下幾個(gè)方面:
1. 單一職責(zé)原則(Single Responsibility Principle,SRP):一個(gè)模塊或者類只負(fù)責(zé)一項(xiàng)功能。
2. 開(kāi)閉原則(Open-Closed Principle,OCP):軟件實(shí)體應(yīng)該對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉。
3. 里氏替換原則(Liskov Substitution Principle,LSP):所有引用基類對(duì)象的代碼都能夠透明地使用其子類對(duì)象。
4. 接口隔離原則(Interface Segregation Principle,ISP):客戶端不應(yīng)該依賴于它不需要的接口,即一個(gè)類對(duì)另一個(gè)類的依賴應(yīng)該建立在最小的接口上。
5. 依賴倒置原則(Dependency Inversion Principle,DIP):高層模塊不應(yīng)該依賴于低層模塊,二者應(yīng)該依賴于抽象接口。同時(shí),抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象。
這些原則可以指導(dǎo)我們?cè)O(shè)計(jì)出更加可擴(kuò)展、可維護(hù)、可測(cè)試、可復(fù)用的架構(gòu)。
為了更加詳細(xì)的說(shuō)明,以下是各個(gè)原則的示例代碼:
1、單一職責(zé)原則(SRP)
// 錯(cuò)誤的示例
public class User {
public void requestBook(string bookName) {
// 做一些請(qǐng)求書(shū)籍的事情
// ...
// 做一些記錄用戶行為的事情
logUserAction("request a book");
}
private void logUserAction(string action) {
// 記錄用戶行為到日志中
// ...
}
}
// 正確的示例
public class User {
public void requestBook(string bookName) {
// 做一些請(qǐng)求書(shū)籍的事情
// ...
}
}
public class UserActionLogger {
public void logUserAction(string action) {
// 記錄用戶行為到日志中
// ...
}
}
在錯(cuò)誤的示例中,`User` 類不僅要負(fù)責(zé)請(qǐng)求書(shū)籍的事情,還要負(fù)責(zé)記錄用戶行為。這不僅讓代碼變得復(fù)雜,而且不符合單一職責(zé)原則。正確的示例分離了不同的職責(zé),把記錄用戶行為的功能獨(dú)立成了一個(gè)新的類。
2、開(kāi)閉原則(OCP)
// 錯(cuò)誤的示例
public class UserManager {
public void addUser(User user) {
// 添加用戶到數(shù)據(jù)庫(kù)中
// ...
}
public void deleteUser(User user) {
// 從數(shù)據(jù)庫(kù)中刪除用戶
// ...
}
}
// 新需求:修改用戶信息
public class UserManager {
public void addUser(User user) {
// 添加用戶到數(shù)據(jù)庫(kù)中
// ...
}
public void deleteUser(User user) {
// 從數(shù)據(jù)庫(kù)中刪除用戶
// ...
}
// 新需求:修改用戶信息
public void updateUser(User user) {
// 修改用戶信息
// ...
}
}
// 正確的示例
public interface IUserManager {
void addUser(User user);
void deleteUser(User user);
}
public class UserManager : IUserManager {
public void addUser(User user) {
// 添加用戶到數(shù)據(jù)庫(kù)中
// ...
}
public void deleteUser(User user) {
// 從數(shù)據(jù)庫(kù)中刪除用戶
// ...
}
}
public class AdvancedUserManager : IUserManager {
public void addUser(User user) {
// 添加用戶到數(shù)據(jù)庫(kù)中
// ...
}
public void deleteUser(User user) {
// 從數(shù)據(jù)庫(kù)中刪除用戶
// ...
}
// 新需求:修改用戶信息
public void updateUser(User user) {
// 修改用戶信息
// ...
}
}
在錯(cuò)誤的示例中,當(dāng)需要添加新的需求(比如修改用戶信息)時(shí),我們需要修改 `UserManager` 類。這顯然不符合開(kāi)閉原則。正確的示例使用了接口和不同的實(shí)現(xiàn)類分離不同的功能,這樣當(dāng)我們需要添加新的需求時(shí),只需要?jiǎng)?chuàng)建一個(gè)新的實(shí)現(xiàn)類即可。
3、里氏替換原則(LSP)
// 錯(cuò)誤的示例
public class Animal {
public virtual void eat() {
Console.WriteLine("Animal eat");
}
}
public class Dog : Animal {
public override void eat() {
Console.WriteLine("Dog eat");
}
}
public class Cat : Animal {
public override void eat() {
Console.WriteLine("Cat eat");
}
}
public class AnimalFeeder {
public void feed(Animal animal) {
animal.eat();
}
}
static void Main(string[] args) {
AnimalFeeder animalFeeder = new AnimalFeeder();
animalFeeder.feed(new Dog());
animalFeeder.feed(new Cat());
}
// 輸出:
// Dog eat
// Cat eat
// 錯(cuò)誤的示例,違反了 LSP
public class WildAnimal : Animal {
}
static void Main(string[] args) {
AnimalFeeder animalFeeder = new AnimalFeeder();
animalFeeder.feed(new Dog());
animalFeeder.feed(new WildAnimal()); // 報(bào)錯(cuò)!
}
在錯(cuò)誤的示例中,我們定義了一個(gè) `WildAnimal` 類繼承自 `Animal` 類,但是這個(gè)類并沒(méi)有重寫(xiě) `eat()` 方法,而是直接繼承了父類的實(shí)現(xiàn)。當(dāng)我們嘗試用 `WildAnimal` 對(duì)象來(lái)調(diào)用 `AnimalFeeder` 的 `feed()` 方法時(shí),會(huì)出現(xiàn)運(yùn)行時(shí)異常,因?yàn)?`WildAnimal` 對(duì)象沒(méi)有正確處理 `eat()` 方法。
4、接口隔離原則(ISP)
// 錯(cuò)誤的示例
public interface IAnimal {
void eat();
void fly();
}
public class Dog : IAnimal {
public void eat() {
Console.WriteLine("Dog eat");
}
public void fly() {
throw new NotImplementedException(); // 錯(cuò)誤的設(shè)計(jì),狗不能飛行
}
}
public class Bird : IAnimal {
public void eat() {
Console.WriteLine("Bird eat");
}
public void fly() {
Console.WriteLine("Bird fly");
}
}
public class AnimalFeeder {
public void feed(IAnimal animal) {
animal.eat();
}
}
static void Main(string[] args) {
AnimalFeeder animalFeeder = new AnimalFeeder();
animalFeeder.feed(new Dog()); // 報(bào)錯(cuò)!
animalFeeder.feed(new Bird());
}
// 正確的示例
public interface IEatable {
void eat();
}
public interface IFlyable {
void fly();
}
public class Dog : IEatable {
public void eat() {
Console.WriteLine("Dog eat");
}
}
public class Bird : IEatable, IFlyable {
public void eat() {
Console.WriteLine("Bird eat");
}
public void fly() {
Console.WriteLine("Bird fly");
}
}
public class AnimalFeeder {
public void feed(IEatable animal) {
animal.eat();
}
}
static void Main(string[] args) {
AnimalFeeder animalFeeder = new AnimalFeeder();
animalFeeder.feed(new Dog());
animalFeeder.feed(new Bird());
}
在錯(cuò)誤的示例中,我們定義了一個(gè) `IAnimal` 接口,其中包含了 `eat()` 和 `fly()` 兩個(gè)方法。但是不是所有的動(dòng)物都可以飛行,例如狗就不能飛行。正確的示例把 `IAnimal` 接口拆分成了 `IEatable` 和 `IFlyable` 兩個(gè)接口,這樣我們可以根據(jù)實(shí)際需要選擇使用哪個(gè)接口來(lái)表示一個(gè)動(dòng)物的能力。
5、依賴倒置原則(DIP)
// 錯(cuò)誤的示例
public class UserService {
private readonly UserDAO userDAO;
public UserService() {
this.userDAO = new UserDAO(); // 依賴了具體的實(shí)現(xiàn)
}
public void addUser(User user) {
userDAO.addUser(user);
}
}
public class UserDAO {
public void addUser(User user) {
// 添加用戶到數(shù)據(jù)庫(kù)中
// ...
}
}
// 正確的示例
public interface IUserDAO {
void addUser(User user);
}
public class UserDAO : IUserDAO {
public void addUser(User user) {
// 添加用戶到數(shù)據(jù)庫(kù)中
// ...
}
}
public class UserService {
private readonly IUserDAO userDAO;
public UserService(IUserDAO userDAO) {
this.userDAO = userDAO; // 依賴抽象接口
}
public void addUser(User user) {
userDAO.addUser(user);
}
}
static void Main(string[] args) {
IUserDAO userDAO = new UserDAO();
UserService userService = new UserService(userDAO);
userService.addUser(new User());
}
在錯(cuò)誤的示例中,`UserService` 類需要訪問(wèn)數(shù)據(jù)庫(kù)添加用戶,但是直接依賴了 `UserDAO` 類。這使得 `UserService` 類不靈活,不能適應(yīng)變化。正確的示例中,我們把 `UserDAO` 類抽象成了 `IUserDAO` 接口,并且通過(guò)構(gòu)造函數(shù)注入了依賴。這樣做不僅遵循了依賴倒置原則,而且還能夠靈活地替換不同的實(shí)現(xiàn)類。