設計原則:不要為了復用而使用繼承


背景

今天上午和以為朋友聊了一個設計問題:如何消除倉庫相關的單據的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,鴨子類型天生具備泛型的特點,比泛型強大,元編程更是牛叉(本文沒有體現)。

備注

 做一件事如果只有一個選擇,就說明有問題了,多思考幾個方案,折中后考慮一個方案。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM