惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
有赞技术团队
有赞技术团队
J
Java Code Geeks
H
Hackread – Cybersecurity News, Data Breaches, AI and More
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Hugging Face - Blog
Hugging Face - Blog
人人都是产品经理
人人都是产品经理
酷 壳 – CoolShell
酷 壳 – CoolShell
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
Check Point Blog
博客园 - 【当耐特】
The GitHub Blog
The GitHub Blog
Recent Announcements
Recent Announcements
The Cloudflare Blog
Microsoft Azure Blog
Microsoft Azure Blog
腾讯CDC
Vercel News
Vercel News
IT之家
IT之家
MyScale Blog
MyScale Blog
博客园_首页
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
罗磊的独立博客

博客园 - kings

项目团队成长日志--聆听客户声音,沟通无所不在 TOAD使用筆記 使电脑鼠标右键相应快的办法 非常好用的对日面试资料(转) IT技術者日本語面接(ぎじゅつしゃにほんごめんせつ)によく出(で)る100質問(しつもん) 获取应用程序路径 再议 构造方法(转自Q.yuhen) 基元类型、值类型和引用类型(转自Q.yuhen) C# 2.0 - 泛型(Generics)(转自Q.yuhen) new 和 override 的区别(转自Q.yuhen) “多态”一个需要注意的问题(转自Q.yuhen) C# 方法参数 ref 详述(转自Q.yuhen) 浅析Family Show 2.0的动态换肤实现(转tonyqus) 浅析Family Show 2.0的子窗体实现(转tonyqus) webservice 遇到的小问题 WPF_Markup的几种写法 When I tab into a toolbar in WPF I can't tab out again? What can I do to change this tab behaviour? Windows Presentation Foundation(WPF)中的数据绑定(使用XmlDataProvider作控件绑定之二:使用外部URL的XML文件)(转-大可山) Windows Presentation Foundation(WPF)中的数据绑定(使用XmlDataProvider作控件绑定)(转-大可山)
泛型集合类型,赋予集合业务意义,增强集合的抽象使用(转-liz...
kings · 2007-09-08 · via 博客园 - kings

写这么一个简单的例子出于两个目的:

  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>句柄。

//集合基类:

public abstract class CollectionBase<T> : IList<T>
{
private IList<T> list;
public List<T> Instance
{
get { return (List<T>)this.list; }
}

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的结果。(您别像我这样写测试的代码,因为我的测试代码貌似管的范围有点多了,测试应该尽量执行单一的功能。)