寫這麼一個簡單的例子出於兩個目的:
1、很多程式員不喜歡看到泛型的身影,他們看到“<>”這樣的符號就會頭痛並難受。受雇於人的我就只能竭盡我所能發起一場消除“<>”的行動,事實上這一點又變得有一些extend意義。
2、extend意義:賦予集合類更強的業務意義,搭配其自身所擁有的更多的自我描述解釋操作的Action,類的行為將更加地具體,這樣的類是OO中比較受歡迎的。
通常我們使用List<T>集合來操作,假設我們有一堆的類型為class Duck,我們可能就需要List<Duck>來對其進行描述了,這樣看來,問題貌似還不那麼棘手。但是當我們有一堆的ValueType或者string類型的值要被組成集合的時候我們會發現問題開始複雜了,我們可以定義List<string> studentNames來裝載當前要裝載的學生姓名,再用List<string> studentFatherNames來裝載學生的父親姓名,這樣的結果將導致我們在某些情況比如有return value的時候,一個GetName()的方法將都被定義為List<string> GetName(),因此這裡的List<string>將不那麼“強型別”(事實上是強型別,但是這裡我指的是那種沒法鑒別的情況),也許你馬上要指出我的錯誤,你會告訴我GetName()的方法名不好,方法的命名必須遵循“表文達意”之基本規則,但我們不能保證每個人都能有良好的素養和認真的態度。一旦這樣的命名被使用著,我們就必須得擔心著這個結果的正確性,而它們將不會在編譯時間被Checked,當我們的客戶拿著程式來問你為什麼學生的成績單上總是印著他們父親的考試分數的時候,我們是應該笑還是應該哭呢?
但如果我們換一個思路來做這件事情,會不會更好呢?
interface IName
{ string Name{get;} }
class Student:IName
class StudentParent:IName
//Example:
List<Student> students;
List<StudentParent> studentParents;
foreach(Student item in students)
{
item.Name;//deal with the students
}
foreach(StudentParent item in studentParents)
{
item.Name;//deal with the studentParents
}
這樣我們將我們的類型更強烈化了
但有人覺得我的這個集合不會是一個複雜到需要用一個IName來指定一個string的欄位,或者也不需要這麼麻煩,我只想簡單地將我的List<Student>表現地像List<string>一樣,而又讓它區別於List<StudentParent>所代表的List<string>(注意,這裡的兩個List<string>被要求有不同的抽象意義並表示相同的物理意義),我該如何做呢?
事實上這個問題被我們思考地過於複雜,我們不需要靠著前面的例子就可以想到這樣的做法,那是因為我們明白了我們要做的就是上一段最後括弧裡的那句話,不同的抽象意義並表示相同的物理意義。這裡的相同可以由您來指定,但它至少都表示了List<string>,我的意思是你可以實現相同意外的不同方法,比如單獨為你的Student和StudentParent賦予不同的方法。
下面的例子正描述著這樣的一般過程:
using System;
using System.Collections.Generic;
using System.Text;
namespace CA_CollectionBase
{
//
//The name of the Collection class should be defined like "BranchCollection/EmployeeCollection"
//
public sealed class SampleCollection : List<Sample> { }
public sealed class Sample
{
private string value;
public string Value
{
get { return this.value; }
}
public Sample(string item)
{
this.value = item;
}
//TODO:Describe other logic code
}
public sealed class StringCollection : List<string> { /*TODO:Describe other logic code*/ }
public sealed class Int32Collection : List<Int32> { /*TODO:Describe other logic code*/ }
public sealed class DoubleCollection : List<double> { /*TODO:Describe other logic code*/ }
class Program
{
static void Main(string[] args)
{
//Object Type
SampleCollection sc = new SampleCollection();
sc.Add(new Sample("a"));
sc.Add(new Sample("b"));
foreach (Sample item in sc)
{
Console.WriteLine(item.Value);
}
//Special reference type
StringCollection strs = new StringCollection();
strs.Add("c");
strs.Add("d");
foreach (string item in strs)
{
Console.WriteLine(item);
}
//Simple ValueType
Int32Collection ints = new Int32Collection();
ints.Add(3);
ints.Add(6);
foreach (Int32 item in ints)
{
Console.WriteLine(item);
}
//Simple ValueType
DoubleCollection doubles = new DoubleCollection();
doubles.Add(3.6);
doubles.Add(6.3);
foreach (Double item in doubles)
{
Console.WriteLine(item);
}
}
}
}
這些代碼在Asp.net中同樣能夠得到廣泛的應用,比如說我們綁定顯示資料將會變得很簡單
實作類別ExampleClass並對其返回不同的類型,這些類型包括我們定義的集合本身,也包括通用的IList介面
public class ExampleClass
{
public StringCollection GetStrings()
{
StringCollection strs = new StringCollection();
strs.Add("string1");
strs.Add("string2");
strs.Add("string3");
strs.Add("string4");
return strs;
}
public SampleCollection GetSamplesAsCollection()
{
SampleCollection sc = new SampleCollection();
sc.Add(new Sample("a"));
sc.Add(new Sample("b"));
sc.Add(new Sample("c"));
sc.Add(new Sample("d"));
return sc;
}
public IList<Sample> GetSamplesAsIList()
{
SampleCollection sc = new SampleCollection();
sc.Add(new Sample("aList"));
sc.Add(new Sample("bList"));
sc.Add(new Sample("cList"));
sc.Add(new Sample("dList"));
return sc;
}
}
namespace WebAppCollectionBaseTest
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
ExampleClass exp = new ExampleClass();
//Step1
this.GridView1.DataSource = exp.GetStrings();
//Step2
this.GridView1.DataSource = exp.GetSamplesAsCollection();
//Step3
this.GridView1.DataSource = exp.GetSamplesAsIList();
this.GridView1.DataBind();
}
}
}
上面這段程式是不是就完美了呢?其實不然,當你真正去實現我注釋中的TODO,去寫自己的商務邏輯的時候會發現,我們並沒有在期間建立起合理的串連,或者說上面那些只能做一個沒有附加商務邏輯的類。我們既然是集合類,那麼所有的商務邏輯都必然會操作到我們集合,但是按上面的Code我們只能在集合類的外部進行一些額外操作,至於集合類的內部,我們無法獲得集合的執行個體(控制代碼Handler),因此我們也無法對它們進行操作,下面我用一個演化的過程來改進我們的類。
我們知道我們的類是繼承了List<SampleClass>的,那麼我們利用關鍵字base是否可以得到控制代碼呢?事實上是不可以的,因為我們的List類並沒有傳遞相關的控制代碼給我們。本能的我們想構造一個類,並通過它來傳遞這個控制代碼給我們。另外一種想法,就是利用原來的類通過base.MemberwiseClone()不斷提供舊有成員的複製,並對它進行操作。不過很快後一種想法就因為居然是淺拷貝,而且只有在所有的資料被載入完之後MemberwiseClone才能返回全部的資料,否則期間將獲得的資料將會是一個不完整(或者空)的資料集合。我們知道List<T>類實現了IList<T>介面,因此我們也需要模仿List<T>實現一個IList<T>介面,並返回List<T>控制代碼。
//集合基類:
/// <summary>
/// 集合基類,只需繼承該基類即可實現集合封裝
/// </summary>
/// <typeparam name="T">類型</typeparam>
public class CollectionBase<T> : IList<T>
{
private IList<T> list;
public List<T> Instance
{
get { return (List<T>)this.list; }
}
/// <summary>
/// 添加項,如果已經存在該項,則不再添加
/// </summary>
/// <param name="item"></param>
/// <returns>添加成功返回true,否則false</returns>
public bool AddNoRepeat(T item)
{
if (!this.list.Contains(item))
{
this.list.Add(item);
return true;
}
return false;
}
public CollectionBase()
{
list = new List<T>();
}
#region IList<T> 成員
public int IndexOf(T item)
{
return this.list.IndexOf(item);
}
public void Insert(int index, T item)
{
this.list.Insert(index, item);
}
public void RemoveAt(int index)
{
this.list.RemoveAt(index);
}
public T this[int index]
{
get
{
return this.list[index];
}
set
{
this.list[index] = value;
}
}
#endregion
#region ICollection<T> 成員
public void Add(T item)
{
this.list.Add(item);
}
public void Clear()
{
this.list.Clear();
}
public bool Contains(T item)
{
return this.list.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
this.list.CopyTo(array, arrayIndex);
}
public int Count
{
get { return this.list.Count; }
}
public bool IsReadOnly
{
get { return this.list.IsReadOnly; }
}
public bool Remove(T item)
{
return this.list.Remove(item);
}
#endregion
#region IEnumerable<T> 成員
public IEnumerator<T> GetEnumerator()
{
return this.list.GetEnumerator();
}
#endregion
#region IEnumerable 成員
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)GetEnumerator();
}
#endregion
}
我們在使用這個類的時候就必須遵循一定的遊戲規則,也就是我們在子類中所有可能用到集合本身的時候,我們必須用base.Instance來獲得集合的控制代碼,這一點尤為重要,其實這很簡單,就像我們的在一個類中定義了一個List<T>的對象,並對它進行操作一樣,只不過這個過程被我們集中起來了。
//例子:
public sealed class SampleCollectionFromCollectionBase : CollectionBase<Sample>
{
//Other logic code which you will implement
public override string ToString()
{
string result = string.Empty;
foreach (Sample s in base.Instance)
{
result = result + s.Value + " ";
}
return result;
}
}
我們剛才一直在談到的相同控制代碼的問題是否開始有些擔心“串音”的現象發生,因為我們知道一個泛型類的執行個體決定於它的類型T,相同的T類型將只會有一個靜態副本的類。不過你應該注意到只是靜態類副本,而不是靜態對象對象副本,每個類執行個體將擁有自己的對象,並在堆中有著自己的引用,這些引用是毫無交叉的。因此,即使兩個類都繼承自一個父類,各自的base也會是不同的,而不是有所關聯。不過為了消除您的顧慮,我還是儘可能為你提供看得見的“說明”,我們用一個簡單的測試來證實我們分析的結果:
在上面的例子類中,我們補充一個InstanceTest的方法:
/// <summary>
/// Only a Test
/// </summary>
public void InstanceTest()
{
base.Add(new Sample("(new SampleCollectionFromCollectionBase).base.Add()"));
base.Instance.Add(new Sample("(new SampleCollectionFromCollectionBase).base.Instance.Add()"));
}
並且再寫一個類,也用base去調用它的Instance,其實說白了,寫一個和上面的SampleCollectionFromCollectionBase 相同的類,並讓類型有所不同就可以了。如果我們的Instance會“串音”的話,我們就會看到在類似下面的代碼執行後會是兩套完全一樣的結果:
SampleCollectionFromCollectionBase sc3 = new SampleCollectionFromCollectionBase();
SampleCollectionFromCollectionBase sc3_1 = new SampleCollectionFromCollectionBase();
SampleCollection2FromCollectionBase sc4 = new SampleCollection2FromCollectionBase();
sc3.InstanceTest();
sc3_1.InstanceTest();
sc4.InstanceTest();
foreach (Sample s in sc3.Instance)
{
Console.WriteLine(s.Value);
}
foreach (Sample s in sc3_1.Instance)
{
Console.WriteLine(s.Value);
}
foreach (Sample s in sc4.Instance)
{
Console.WriteLine(s.Value);
}
理由很簡單,因為不論sc3、sc3_1或者sc4,都引用自同一個Instance?!?!?
當然了,事實不是這樣的,下面就是這段程式的執行結果:
(new SampleCollectionFromCollectionBase).base.Add()
(new SampleCollectionFromCollectionBase).base.Instance.Add()
(new SampleCollectionFromCollectionBase).base.Add()
(new SampleCollectionFromCollectionBase).base.Instance.Add()
(new SampleCollection2FromCollectionBase).base.Add()
(new SampleCollection2FromCollectionBase).base.Instance.Add()
很容易看出第1、2行是sc3,第3、4行是sc3_1,第5、6行是sc4的結果。(您別像我這樣寫測試的代碼,因為我的測試代碼貌似管的範圍有點多了,測試應該盡量執行單一的功能。)
原始碼下載:CA_CollectionBase.rar