使用簡單的c#示例的堅實的架構原則
內容 固體是什麼?理解“S”- SRP(單一責任原則)理解“O”——開閉原則的理解“L”——太陽能發電(Liskov替換原則)瞭解“我”- ISP(介面隔離原則)理解“D”——依賴性倒置原則。修改堅實的原則 介紹 我知道有1000年的關於這個話題的文章,每個月10新文章在固體會注入更多。我寫這篇文章的目標是理解固體與簡單的c#示例。 任何改進本文評論框下面是開放的。 固體是什麼? 固體有五個基本原則whichhelp建立良好的軟體架構。固體是一個縮寫:- S代表SRP(單一責任原則o代表(OCP(開閉原則)l代表太陽能發電(Liskov替換原則),我代表ISP(介面隔離原則)d代表下降(依賴性倒置原則) 讓我們開始瞭解每個原理簡單的c#示例。 理解“S”- SRP(單一責任原則) 理解固體的最好方法是通過理解它試圖解決什麼問題。看一看下面的程式碼,你能猜出問題是什麼?(你不會讓啤酒猜這J,因為太簡單)。 好吧,讓我給一個提示看catch塊程式碼。 隱藏,複製Code
class Customer { public void Add() { try { // Database code goes here } catch (Exception ex) { System.IO.File.WriteAllText(@"c:\Error.txt", ex.ToString()); } } }
以上客戶類做他不應該做的事。客戶類客戶datavalidations應該做的,叫客戶資料訪問層等,但如果你看到catch塊緊密也做日誌記錄活動。在簡單的單詞含有很多的責任。 所以明天如果新增一個新的日誌事件檢視器我需要去改變“客戶”類,這是非常奇怪的。 就像“約翰”有一個問題,為什麼我需要檢查“BOB”。 這也讓我想起了著名的瑞士刀。如果其中一個需要更改整個集需要被打擾。不進攻我的鐵桿粉絲,瑞士刀。 但如果我們可以有這些物品分離它的簡單、易於維護和改變不影響另一個。同樣的原則也適用於在軟體架構類和物件。 所以SRP說一個類應該只有一個責任,而不是多個。如果我們應用SRP,日誌記錄活動轉移到其他類誰只會照顧日誌活動。 隱藏,複製Code
class FileLogger { public void Handle(string error) { System.IO.File.WriteAllText(@"c:\Error.txt", error); } }
現在客戶類可以幸福的日誌記錄活動委託給“FileLogger”類,他可以專注於客戶相關的活動。 隱藏,複製Code
class Customer { private FileLogger obj = new FileLogger(); publicvirtual void Add() { try { // Database code goes here } catch (Exception ex) { obj.Handle(ex.ToString()); } } }
現在建築的思維過程是一個進化。對於一些老年人的人看著上面SRP例子反駁,甚至可以嘗試捕捉不應該由客戶類,因為這不是他的工作。 是的,我們可以建立一個全域性錯誤處理程式必須在全球。asax檔案,假設您正在使用ASP。淨和處理這些部分的錯誤,使客戶類完全免費。 所以我離開你可以走多遠,會讓這個解決方案更好的但是現在我想保持這個簡單,讓你的思想把它很大程度的自由。 下面是一個偉大的評論,談到如何藉此SRP的例子。 http://www.codeproject.com/Articles/703634/SOLID-architecture-principles-using-simple-Csharp?msg=4729987#xx4729987xx 理解“O”——開閉原則 讓我們繼續我們的客戶類示例相同。我添加了一個簡單的客戶型別屬性的類。這個屬性決定如果這是一個“黃金”奧拉“銀”的客戶。 根據計算相同的折扣。看一看“getDiscount”函式,返回相應的折扣。1金客戶和2銀客戶。 想,有什麼問題下面的程式碼。哈哈哈,看來這篇文章會讓你猜冠軍;-)。 好,還讓我新增一個提示,看這個“如果”條件“getDiscount”功能。 隱藏,複製Code
class Customer { private int _CustType; public int CustType { get { return _CustType; } set { _CustType = value; } } public double getDiscount(double TotalSales) { if (_CustType == 1) { return TotalSales - 100; } else { return TotalSales - 50; } } }
問題是如果我們新增一個新客戶型別,我們需要去新增一個“如果”條件“getDiscount”功能,換句話說,我們需要改變客戶類。 如果我們改變一次又一次的客戶類,我們需要確保前面的條件與新再次測試,現有客戶的引用這個類是正常工作。 換句話說我們是“修改”當前的客戶程式碼對於每一個變化,我們每次修改之前我們需要確保所有的功能和聯絡客戶工作。 而不是“修改”我們如何去“擴充套件”。換句話說每次新增新客戶型別需要我們建立一個新類,如下所示。所以無論當前的程式碼,我們只需要測試和檢查新類。 隱藏,複製Code
class Customer { public virtual double getDiscount(double TotalSales) { return TotalSales; } } class SilverCustomer : Customer { public override double getDiscount(double TotalSales) { return base.getDiscount(TotalSales) - 50; } }
藏e,複製Code
class goldCustomer : SilverCustomer { public override double getDiscount(double TotalSales) { return base.getDiscount(TotalSales) - 100; } }
簡單地說,對於任何新的修改,“Customer”類現在都是關閉的,但是當新的客戶型別被新增到專案中時,它對於擴充套件是開啟的。 理解“L”- LSP (Liskov置換原理) 讓我們繼續同一個客戶。假設我們的系統要計算查詢折扣。現在詢盤不是真正的客戶,他們只是線索。因為它們只是線索,所以我們暫時不希望將它們儲存到資料庫中。 因此,我們建立一個名為Enquiry的新類,它繼承自“Customer”類。我們對查詢提供一些折扣,以便它們可以轉換為實際客戶,我們覆蓋了“新增”方法,但有一個例外,以便沒有人可以向資料庫新增查詢。 隱藏,複製Code
class Enquiry : Customer { public override double getDiscount(double TotalSales) { return base.getDiscount(TotalSales) - 5; } public override void Add() { throw new Exception("Not allowed"); } }
如果您將當前客戶繼承層次結構視覺化,它看起來如下所示。換句話說,“Customer”是父類,“Gold”、“Silver”和“Enquiry”是子類。 因此,根據多型性規則,我的父“客戶”類物件可以指向任何it子類物件,即“金”,“銀”或“查詢”在執行時沒有任何問題。 例如,在下面的程式碼中,您可以看到我建立了一個“Customer”的列表集合,並且多虧了多型性,我可以毫無問題地將“Silver”、“Gold”和“Enquiry”Customer新增到“Customer”集合中。 由於多型,我還可以使用父Customer物件瀏覽“Customer”列表,並呼叫“Add”方法,如下面的程式碼所示。 現在再一次讓我來逗你們,這裡有一個小問題,想,想,想。 提示:-觀察查詢物件在“FOR EACH”迴圈中瀏覽和呼叫的時間。 , 隱藏,複製Code
List<Customer> Customers = new List<Customer>(); Customers.Add(new SilverCustomer()); Customers.Add(new goldCustomer()); Customers.Add(new Enquiry()); foreach (Customer o in Customers) { o.Add(); } }
根據繼承層次結構,“Customer”物件可以指向它的任何子物件,我們不期望出現任何異常行為。 但是當" Enquiry "物件的" Add "方法被呼叫時,它會導致以下錯誤,因為我們的" Equiry "物件將查詢儲存到資料庫中,因為它們不是實際的客戶。 現在正確地閱讀下面的段落來理解這個問題。如果你不明白下面的段落,請讀兩遍。 換句話說,“詢價”有折扣計算,它看起來像一個“客戶”,但它不是一個客戶。因此父物件不能無縫地替換子物件。換句話說,“Customer”不是“Enquiry”類的實際父類。“查詢”是一個完全不同的實體。 所以LISKOV原則說父物件應該很容易替換子物件。因此,要實現LISKOV,我們需要建立兩個介面,一個用於折扣,另一個用於資料庫,如下所示。 隱藏,複製Code
interface IDiscount { double getDiscount(double TotalSales); } interface IDatabase { void Add(); }
現在,“Enquiry”類將只實現“IDiscount”,因為它對“Add”方法不感興趣。 隱藏,複製Code
class Enquiry : IDiscount { public double getDiscount(double TotalSales) { return TotalSales - 5; } }
而“Customer”類將同時實現“IDiscount”和“IDatabase”,因為它還希望將客戶持久化到資料庫中。 隱藏,複製Code
class Customer : IDiscount, IDatabase { private MyException obj = new MyException(); public virtual void Add() { try { // Database code goes here } catch (Exception ex) { obj.Handle(ex.Message.ToString()); } } public virtual double getDiscount(double TotalSales) { return TotalSales; } }
現在沒有混淆了,我們可以建立一個“Idatabase”介面列表,並向其中新增相關的類。如果我們錯誤地將“Enquiry”類新增到列表中,編譯器會報錯,如下面的程式碼片段所示。 , 理解“I”- ISP(介面分離原則) 現在假設我們的customer類已經成為一個超級熱門元件,它在1000個客戶機上使用,它們都非常喜歡使用customer類。 , 現在讓我們假設一些新客戶提出一個要求,說我們也想要一個方法來幫助我們“讀取”客戶資料。因此,高度熱情的開發人員會希望更改“IDatabase”介面,如下所示。 但你能猜到,我們這樣做是做了一件可怕的事情嗎? 提示:-考慮一下這個變化對上面圖片的影響。 隱藏,複製Code
interface IDatabase { void Add(); // old client are happy with these. voidRead(); // Added for new clients. }
如果你設想新的要求已經出現,你有兩種客戶的:- 想要的就用“新增”方法。另一個人想用“新增”+“閱讀”。 現在,通過更改當前介面,您正在做一件可怕的事情,即使當客戶端對“Read”方法不感興趣時,也會干擾1000個滿意的當前客戶端。你強迫他們使用“閱讀”方法。 因此,更好的方法是將現有客戶留在他們自己的甜蜜世界中,並分別為新客戶提供服務。 因此,更好的解決方案是建立一個新介面,而不是更新當前介面。因此,我們可以保持當前介面“IDatabase”不變,並使用“Read”方法新增一個新介面“IDatabaseV1”,“V1”代表版本1。 隱藏,複製Code
interface IDatabaseV1 : IDatabase // Gets the Add method { Void Read(); }
現在,您可以建立實現“Read”方法的新類,並滿足新客戶端的需求,而舊客戶端對沒有“Read”方法的舊介面保持原樣並感到滿意。 隱藏,複製Code
class CustomerwithRead : IDatabase, IDatabaseV1 { public void Add() { Customer obj = new Customer(); Obj.Add(); } Public void Read() { // Implements logic for read } }
所以老客戶端將繼續使用“IDat”當新客戶端可以使用“IDatabaseV1”介面時。 隱藏,複製Code
IDatabase i = new Customer(); // 1000 happy old clients not touched i.Add(); IDatabaseV1 iv1 = new CustomerWithread(); // new clients Iv1.Read();
理解“D”依賴倒置原則 如果您還記得的話,在我們的customer類中,我們建立了一個logger類來滿足SRP。接下來,我們假設建立了新的Logger風味類。 隱藏,複製Code
class Customer { private FileLogger obj = new FileLogger(); public virtual void Add() { try { // Database code goes here } catch (Exception ex) { obj.Handle(ex.ToString()); } } }
為了控制事情,我們建立了一個公共介面,並使用這個公共介面將建立新的日誌程式風格。 隱藏,複製Code
interface ILogger { void Handle(string error); }
下面是三種logger口味,還可以新增更多。 隱藏,複製Code
class FileLogger : ILogger { public void Handle(string error) { System.IO.File.WriteAllText(@"c:\Error.txt", error); } }
隱藏,複製Code
class EverViewerLogger : ILogger { public void Handle(string error) { // log errors to event viewer } }
隱藏,複製Code
class EmailLogger : ILogger { public void Handle(string error) { // send errors in email } }
現在,根據配置設定,不同的日誌記錄器類將在給定時刻使用。因此,為了達到同樣的目的,我們保持了一個簡單的IF條件,它決定使用哪個日誌記錄器類,參見下面的程式碼。 小測驗時間到了,這裡的問題是什麼? 提示:-觀看捕獲塊程式碼。 隱藏,複製Code
class Customer : IDiscount, IDatabase { private IException obj; public virtual void Add(int Exhandle) { try { // Database code goes here } catch (Exception ex) { if (Exhandle == 1) { obj = new MyException(); } else { obj = new EmailException(); } obj.Handle(ex.Message.ToString()); } }
上面的程式碼再次違反了SRP,但這一次方面有所不同,它是關於決定應該建立哪些物件。現在,決定建立哪些例項不是“Customer”物件的工作,他應該只關注與Customer類相關的功能。 如果你仔細觀察,最大的問題是“新的”關鍵字。他對需要建立的物件承擔額外的責任。 因此,如果我們把這個責任倒置或委託給其他人,而不是客戶類來做,這將在一定程度上真正解決問題。 , 下面是實現了反轉的修改程式碼。我們已經打開了建構函式的口,我們希望其他人來傳遞物件,而不是由customer類來做。因此,現在由使用customer物件的客戶機負責決定注入哪個Logger類。 隱藏,複製Code
class Customer : IDiscount, IDatabase { private Ilogger obj; public Customer(ILogger i) { obj = i; } }
因此,現在客戶端將注入Logger物件,並且客戶物件現在不再受那些決定注入哪個Logger類的IF條件的影響。這是實體依賴倒置原則中的最後一個原則。 客戶類將依賴物件的建立委託給使用依賴物件的客戶,從而使客戶類集中於其工作。 隱藏,複製Code
IDatabase i = new Customer(new EmailLogger());
修改堅實的原則 S代表SRP(單一責任原則):-一個類應該只負責一個責任。 O代表OCP(開閉原則):-擴充套件應該優先於修改。 L代表LSP (Liskov substitution principle):-在執行時多型期間,父類物件應該能夠無縫地引用子物件。 I代表ISP(介面隔離原則):——如果客戶端不需要介面,就不應該強迫它使用。 D代表DIP(依賴倒置原則):-高層模組不應該依賴於低層模組,而應該依賴於抽象。 如果您已經完成了這篇文章,那麼接下來的邏輯步驟將是學習GOF設計模式,這裡有一篇同樣的文章,希望您喜歡。 一旦你完全理解了,下一步就是學習設計模式。下面是一個很好的視訊,教你在8小時內設計一個專案的模式。 本文轉載於:http://www.diyabc.com/frontweb/news2082.html