無論書還是博客, 耦合這個詞已被無數人說爛,任何一位程序員都會告訴你設計軟件要注意低耦合,可究竟什么是低耦合?每次去查這個問題,就會牽扯出各種術語和理論,讓人頭暈。最近看了一些英文資料,發現低耦合其實沒那么復雜。
什么是耦合?怎樣的代碼叫高耦合?
“耦合”翻譯自英文(coupling),英文描述是:"when a component has a dependency on something else". 這句話簡單易懂--當一個組件對其他東西有依賴就叫耦合,為方便描述,先給段代碼:
public class EmailService { public void SendMessage() { } } public class NotificationSystem { private EmailService svc; public NotificationSystem() { svc = new EmailService(); } public void InterestingEventHappend() { svc.SendMessage(); } }
代碼的邏輯很簡單:NotificationSystem通過內置的EmailService類發送郵件,即NotificationSystem的功能依賴EmailService類。我相信應有不少人對代碼感覺親切,我參與過的項目基本都這種風格,可惜這就是高耦合的設計。
高耦合翻譯自“tightly coupled”,描述是這樣的:"A class that knows a lot about the other classes it interacts with is said to be tightly coupled".翻譯過來就是---它知道的太多了。^_^
最快的解耦方式
為了讓它知道的不那么多,現在貼一份改良后的代碼:
public interface IMessageService { void SendMessage(); } public class EmailService : IMessageService { public void SendMessage() { } } public class NotificationSystem { private IMessageService svc; public NotificationSystem() { svc = new EmailService(); } public void InterestingEventHappend() { svc.SendMessage(); } }
與之前比較,svc變量類型變成了接口IMessageService ,從而使NotificationSystem依賴IMessageService接口,而不是EmailService類。 但svc通過new 方式賦值,這讓兩個類藕斷絲連,一旦EmailService變化,NotificationSystem也跟着變,違背了開閉原則。
通過控制反轉徹底解耦
想徹底解耦,就要換一種方式對svc賦值,於是想到控制反轉模式,控制反轉翻譯自“inversion of control”簡稱Ioc,一句話描述:“Moving the creation of dependencies outside of the class that consumes those dependencies”,簡單翻譯過來就是:在外面創建這個類。
現在我們先抽象一個接口用於“外部創建”。
public interface IMessageService { void SendMessage(); } public class EmailService : IMessageService { public void SendMessage() { } } public interface IServiceLocator { IMessageService GetMessageService(); } public class NotificationSystem { private IMessageService svc; public NotificationSystem(IServiceLocator locator) { svc = locator.GetMessageService(); } public void InterestingEventHappend() { svc.SendMessage(); } }
從代碼看出,現在svc是通過IServiceLocator接口類創建,從而讓原類之間解耦。IServiceLocator的實現類就像工廠模式,通過參數或配置文件等決定生成哪個類。然而這種做法讓IServiceLocator和IMessageService 的實現類之間增加了耦合,每添加一個IMessageService 的實現類,就要修改IServiceLocator的代碼,可能是switch或連續的if,這樣看似不錯的模式仍然違反開閉原則:
public class ServiceLocator:IServiceLocator { public IMessageService GetMessageService() { string type = "type1"; switch (type) { case "type1":return new EmailService1(); case "type2": return new EmailService2(); case "type3": return new EmailService3(); ………… } } }
用Ioc容器完成高耦合到低耦合的蛻變
完全蛻變,就要求助於依賴注入了,這個詞和控制反轉是好基友,一般都同時出現。實際開發中,我們往往使用IoC容器來實現依賴注入的需求。通過Ioc容器(以Autofac為例)改善代碼如下:
public class NotificationSystem { private IMessageService svc; public NotificationSystem(IMessageService messageService) { svc = messageService; } public void InterestingEventHappend() { svc.SendMessage(); } }
可以看到NotificationSystem的構造函數直接傳入IMessageService接口變量做參數。在全局類的代碼如下:
var builder = new ContainerBuilder(); builder.RegisterType<EmailService>().As<IMessageService>();
通過依賴注入來實例化NotificationSystem類:
IMessageService messageService= container.Resolve<IMessageService>(); NotificationSystem system=new NotificationSystem(messageService);
借助Ioc的幫助,當IMessageService添加新的實現類時,也不用修改其他類的內部代碼,在配置代碼中,僅修改類名便可實現功能的切換。
結語
現在很多的開源代碼都是這種模式,類內部依賴接口,通過Ioc容器靈活配置,熟悉了這種模式,有助於理解別人的設計意圖,我也從中收益良多。但也有讓我不爽的地方就是看別人的代碼時,每次用F12跟蹤源碼,都會跳轉到描述接口的代碼文件中,想看具體實現總要害我找半天。
參考資料:<Professional Asp.Net MVC 3>