面向對象設計有一個原則「優先使用對象組合,而不是繼承」。學習
下面是二者優缺點的比較:spa
組 合 關 系設計 |
繼 承 關 系code |
優勢:不破壞封裝,總體類與局部類之間鬆耦合,彼此相對獨立對象 |
缺點:破壞封裝,子類與父類之間緊密耦合,子類依賴於父類的實現,子類缺少獨立性blog |
優勢:具備較好的可擴展性繼承 |
缺點:支持擴展,可是每每以增長系統結構的複雜度爲代價接口 |
優勢:支持動態組合。在運行時,總體對象能夠選擇不一樣類型的局部對象ci |
缺點:不支持動態繼承。在運行時,子類沒法選擇不一樣的父類get |
優勢:總體類能夠對局部類進行包裝,封裝局部類的接口,提供新的接口 |
缺點:子類不能改變父類的接口 |
缺點:總體類不能自動得到和局部類一樣的接口 |
優勢:子類能自動繼承父類的接口 |
缺點:建立總體類的對象時,須要建立全部局部類的對象 |
優勢:建立子類的對象時,無須建立父類的對象 |
咱們能夠發現繼承的缺點遠遠多於優勢,儘管繼承在學習OOP的過程當中獲得了大量的強調,但並不意味着應該儘量地處處使用它。
相反,使用它時要特別慎重。
只有在清楚知道繼承在全部方法中最有效的前提下,纔可考慮它。
繼承最大的優勢就是擴展簡單,但大多數缺點都很致命,可是由於這個擴展簡單的優勢太明顯了,不少人並不深刻思考,因此形成了太多問題。
雖然筆者的平常工做已經遭遇了太多的Legacy Code由於繼承而致使的問題,可是在寫新代碼時,仍然容易陷入繼承而沒法自拔。
最近在寫得一分新代碼:
1 public interface IEntity{} 5 6 public interface IEntityContainer 7 { 8 string Name { get;} 9 int EntityId { get;} 10 IList<IEntity> EntityContainer{ get;} 11 } 12 13 public class XEntityContainer : IEntityContainer 14 { 15 public XEntityContainer() 16 { 17 //Name = ...; 18 //EntityId = ...; 19 //EntityContainer = ...; 20 } 21 22 public string Name{get; private set;} 23 public int EntityId{get; private set;} 24 public IList<IEntity> EntityContainer{ get; private set;} 25 } 26 27 public class YEntityContainer : IEntityContainer 28 { 29 public YEntityContainer() 30 { 31 //Name = ...; 32 //EntityId = ...; 33 //EntityContainer = ...; 34 } 35 36 public string Name { get;private set;} 37 public int EntityId { get;private set;} 38 public IList<IEntity> EntityContainer{ get; private set;} 39 }
Code Review時以爲若是全部的子類都會包含這三行的話,實際上是有些duplication的:
1 public string Name { get;private set;} 2 public int EntityId { get;private set;} 3 public IList<IEntity> EntityContainer{ get; private set;}
因此就建議使用組合的方式引入一個新的類型來提供這幾項信息。
但,跟着直覺就把Code寫成了這樣子:
1 public interface IEntity{} 5 6 public interface IEntityContainer 7 { 8 string Name { get;} 9 int EntityId { get;} 10 IList<IEntity> EntityContainer{ get;} 11 } 12 13 public class EntityContainerBase : IEntityContainer 14 { 15 public string Name{get; protected set;} 16 public int EntityId{get; protected set;} 17 public IList<IEntity> EntityContainer{ get; protected set;} 18 } 19 20 public class XEntityContainer : EntityContainerBase 21 { 22 public XEntityContainer() 23 { 24 //Name = ...; 25 //EntityId = ...; 26 //EntityContainer = ...; 27 } 28 } 29 30 public class YEntityContainer : EntityContainerBase 31 { 32 public YEntityContainer() 33 { 34 //Name = ...; 35 //EntityId = ...; 36 //EntityContainer = ...; 37 } 38 }
就這樣一個好好的二層繼承,好好的interface繼承,被掰成了三層結構。
比較「壞」的是這種潛意識裏依然把繼承依然當成了第一選擇。
根據同一項目裏已有的一段新Code,能夠知道未來有些utility方法確定會不斷地往EntityContainerBase里加,直到有一天把它變成Legacy...
我更加傾向於咱們應該這樣改:
1 public interface IEntity{} 5 6 public struct Container 7 { 8 public string Name{get;set;}; 9 public int EntityId{get;set;}; 10 public IList<IEntity> EntityList{get;set;}; 11 } 12 13 public interface IEntityContainer 14 { 15 string Name { get;} 16 int EntityId { get;} 17 IList<IEntity> EntityContainer{ get;} 18 } 19 20 public class XEntityContainer : IEntityContainer 21 { 22 public XEntityContainer() 23 { 24 //m_Container.Name = ...; 25 //m_Container.EntityId = ...; 26 //m_Container.EntityList = ...; 27 } 28 29 public string Name { get{return m_Container.Name;}} 30 public int EntityId { get{return m_Container.EntityId;};} 31 public IList<IEntity> EntityContainer{ get{return m_Container.EntityList;};} 32 33 private Container m_Container; 34 } 35 36 public class YEntityContainer : IEntityContainer 37 { 38 public YEntityContainer() 39 { 40 //m_Container.Name = ...; 41 //m_Container.EntityId = ...; 42 //m_Container.EntityList = ...; 43 } 44 45 public string Name { get{return m_Container.Name;}} 46 public int EntityId { get{return m_Container.EntityId;};} 47 public IList<IEntity> EntityContainer{ get{return m_Container.EntityList;};} 48 49 private Container m_Container; 50 }
多麼漂亮的二層結構,沒有任何Base類,未來的公共方法,Utility方法不會「無腦」的往Base裏塞。