背景
今天上午和以為朋友聊了一個設計問題:如何消除倉庫相關的單據的Repository中的重復邏輯?如:入庫單Repository和出庫單Repository之間的重復。可以有很多方式消除重復,在不同級別消除重復,如:繼承、組合、摻入、幫助類、幫助方法。本文只說出我的觀點:不要為了復用而使用繼承。
為什么要得出這個結論:在單實現繼承模型下,你復用了一個基類的實現,就不能復用其它基類的實現了,接口繼承 + 擴展類型(Mixin)可以很好的解決這個問題。
設計的演化
下面我會演示:待重構的重復代碼-》用繼承消除重復-》用擴展類(Mixin)消除重復-》Ruby的鴨子類型 + Mixin的實現(元編程可以更牛叉,有機會再說)。
待重構的代碼
注意:出庫單倉儲和入庫單倉儲的“根據編號獲取單據”重復了。
類圖
代碼
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Linq.Expressions; 7 8 namespace CSharpStudy.MixinStudy.V1 9 { 10 class Aggregate { } 11 12 interface IRepository<T> where T : Aggregate 13 { 14 IEnumerable<T> Where(Expression<Func<T, bool>> condition); 15 } 16 17 class Repository<T> : IRepository<T> 18 where T : Aggregate 19 { 20 public IEnumerable<T> Where(Expression<Func<T, bool>> condition) 21 { 22 throw new NotImplementedException(); 23 } 24 } 25 26 class 入庫單 : Aggregate 27 { 28 public string 單據編號 { get; set; } 29 } 30 31 class 出庫單 : Aggregate 32 {
用繼承消除重復
當我看到上面的重復代碼的時候,第一印象是引入兩個基類:倉庫單據基類和倉庫單據倉儲基類。
類圖
代碼
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Linq.Expressions; 7 8 namespace CSharpStudy.MixinStudy.V2 9 { 10 class Aggregate { } 11 12 interface IRepository<T> where T : Aggregate 13 { 14 IEnumerable<T> Where(Expression<Func<T, bool>> condition); 15 } 16 17 class Repository<T> : IRepository<T> 18 where T : Aggregate 19 { 20 public IEnumerable<T> Where(Expression<Func<T, bool>> condition) 21 { 22 throw new NotImplementedException(); 23 } 24 } 25 26 class 倉庫單據基類 : Aggregate 27 { 28 public string 單據編號 { get; set; } 29 } 30 31 class 入庫單 : 倉庫單據基類 { } 32 33 class 出庫單 : 倉庫單據基類 { } 34 35 class 倉庫單據倉儲基類<T> : Repository<T> 36 where T : 倉庫單據基類 37 { 38 public IEnumerable<T> 根據編號獲取單據(string 單據編號) 39 { 40 return this.Where(x => x.單據編號 == 單據編號); 41 } 42 } 43 44 class 入庫單倉儲 : 倉庫單據倉儲基類<入庫單> { } 45 46 class 出庫單倉儲 : 倉庫單據倉儲基類<出庫單> { } 47 }
用擴展類(Mixin)消除重復
我對了嗎?沒有多態,只是為了復用就引入繼承,是否合理呢?
類圖
代碼
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Linq.Expressions; 7 8 namespace CSharpStudy.MixinStudy.V3 9 { 10 class Aggregate { } 11 12 interface IRepository<T> where T : Aggregate 13 { 14 IEnumerable<T> Where(Expression<Func<T, bool>> condition); 15 } 16 17 class Repository<T> : IRepository<T> 18 where T : Aggregate 19 { 20 public IEnumerable<T> Where(Expression<Func<T, bool>> condition) 21 { 22 throw new NotImplementedException(); 23 } 24 } 25 26 class 倉庫單據基類 : Aggregate 27 { 28 public string 單據編號 { get; set; } 29 } 30 31 class 入庫單 : 倉庫單據基類 { } 32 33 class 出庫單 : 倉庫單據基類 { } 34 35 static class 倉庫單據基類倉儲擴展 36 { 37 public static IEnumerable<T> 根據編號獲取單據<T>(this IRepository<T> that, string 單據編號) 38 where T : 倉庫單據基類 39 { 40 return that.Where(x => x.單據編號 == 單據編號); 41 } 42 } 43 }
Ruby的鴨子類型 + Mixin的實現
代碼
1 # coding: utf-8 2 3 class Aggregate 4 end 5 6 class Repository 7 def Where(condition) 8 end 9 end 10 11 class C倉庫單據基類 < Repository 12 attr_accessor :單據編號 13 end 14 15 class C入庫單 < C倉庫單據基類 16 end 17 18 class C出庫單 < C倉庫單據基類 19 end 20 21 module C倉庫單據基類倉儲擴展 22 def 根據編號獲取單據(單據編號) 23 return self.Where({:單據編號 => 單據編號}) 24 end 25 end 26 27 class C入庫單倉儲 < Repository 28 include C倉庫單據基類倉儲擴展 29 end 30 31 class C出庫單倉儲 < Repository 32 include C倉庫單據基類倉儲擴展 33 end
Ruby正統的支持了Mixin,鴨子類型天生具備泛型的特點,比泛型強大,元編程更是牛叉(本文沒有體現)。
備注
做一件事如果只有一個選擇,就說明有問題了,多思考幾個方案,折中后考慮一個方案。