首页 > 代码库 > IOC演义 第一回: 重构类步步为营 新框架萌芽胎动
IOC演义 第一回: 重构类步步为营 新框架萌芽胎动
问题探讨
目前的网站在编程文章的表现上,一般都会把代码折叠起来,而文字部分却始终显示,给人代码似乎可有可无的感觉。实际情况往往恰恰相反:代码和插图、文字应该是文章的三个表现手段,许多情况下插图和文字应该是围绕代码展开的,是对代码的说明和原理展示,或者说主角往往应该是代码,文字和插图则是点缀。这样我们为什么不能把文字作为代码的注释呢?一段代码想要表达什么意思,每个人看后的领悟应该不会完全相同,最权威的表达无疑是代码的作者。把代码和文字存放在一块,不就把作者的意图共同保存了吗?
本文就是在vs2015里面编写的,使用/*和 */注释对把文字部分注释掉,然后使用办公软件稍加排版;读者可以选择全部内容,粘贴到IDE里面,添加必要的引用就可以运行。文字则完全就是代码的注释。这样还可以保证不会因为多选、漏选等等意外而导致的错误。
此部分不是文章的一部分,编辑可以去掉。
开篇按语
洪荒少女傅园慧以其丰富的表情包和夸张的语言瞬间征服了全世界,赢得了比大多数冠军还高的关注度。这充分说明了仅仅有实力是不够的,还要有必要的营销手段。
每个人都有不同程度的武侠梦想和江湖情节,下面我们就以宏大的NET Framwork世界为背景,以c#为武器,看一个无名小辈如何炼成了名震江湖的编码大侠。一代IOC神器在大家的见证下即将呱呱坠地……
必备技能
想要在江湖扬名立万,自然要有两把刷子,这里要求你能够看懂c#(java也没问题,二者简直是同卵双胞胎)源代码,知道泛型、反射和弱引用的使用,当然还有单元测试。这是一个设计IOC框架的故事,所以什么是IOC和最佳拍档AOP你最好也了解一些,面向接口编程这样高端大气上档次的东西也需要知道。重构作为码农的基本功,是必须掌握的,你完全可以把这篇文章看做重构的教程,可以根据行文自己重构一下,然后对比看看二者的异同。
行走江湖,不免要展示自己的能力。如果只是欺负一些无名小辈和普通百姓,最多只能成为镇关西和南霸天这样的地痞,不可能成为名震江湖的大侠。而向有头脸的腕主们挑战或者征服、消灭前面的地痞无疑是展示武功的绝佳途径。下面开始我们的征服之旅:
正式开始
作为练手升级的第一站,自然要挑软柿子捏,下面就是我们需要征服的目标,非常简单,表示人相关信息的类:
普通类的属性一般是这样定义的:*/
public interface IPerson {
string Name { get; set; }
int Age { get; set; }
}
internal class Person : IPerson {
public string Name { get; set; }
public int Age { get; set; }
}
internal class Person1 : IPerson {
private string name;
private int age;
public string Name
{
get { return name; }
set { name = value; }
}
public int Age
{
get { return age; }
set { age = value; }
}
}/*
上面两个版本的本质完全一样,前者为后者的“语法糖”;而第二个实现更接近本质,我们就以后者为蓝本开始本次的发现之旅。
开发需求
(需要说明,这里仅仅出于演示目的而没有考虑是否合理,另外在平时如果不需要我们后面添加的功能,而仅仅储存相关数据,使用上面定义已经足够快捷和好用,不需要画蛇添足)。我们看看这个类的实例都有什么能力:*/
[TestClass]
public partial class PersonTest {
[TestMethod]
public void TestSample1() {
IPerson p = new Person1();
Assert.AreEqual(p.Age, default(int));
TestPersion(p);
}
private void TestPersion(IPerson p) {
Assert.AreEqual(p.Name, default(string));
p.Name = "aa";
p.Age = 1000;
Assert.AreEqual(p.Name, "aa");
Assert.AreEqual(p.Age, 1000);
}
}/*
很简单,没有为属性赋值的时候返回缺省值;赋值以后就储存在字段里面,需要则返回储存的新值。这里出头露面的属性Name实际仅仅是个马仔和提线木偶,它是什么值不由自己决定,而是由它后面的大哥name字段决定,因此征服了大哥name(这个大哥也太寒酸了,仅仅控制了一个小弟)也就征服了属性Name;其他属性同理。
一统小孤山
经过血雨腥风、不眠不休、遇神杀神、遇佛杀佛、大战三百合后(……此处省略300字,没办法,功力浅,碰到蚂蚁都要踩三脚才能干掉),我们征服了所有的大哥,把这些字段收降在一个宝袋(集合)里面,这样就可以用这个集合取代这一群字段,因为这些字段的类型和名称五花八门,这个集合的类型自然是它们的共同祖先:object类型了;而原来不同的字段名称也统一在集合的名下,也就是集合成为了这群属性的老大(众属性高呼:老大威武,老大万岁)。当然了,对字段的操作也需要相应转换为对集合的操作。想想秦始皇统一全中国干的不就是类似的事情吗?*/
internal class Person2 : IPerson {
private string a;
private int b;
private object[] backFields = { null, 0 };
public string Name
{
//get { return name; }
//set { name = value; }
get { return (string)backFields[0]; }
set { backFields[0] = value; }
}
public int Age
{
//get { return salay; }
//set { salay = value; }
get { return (int)backFields[1]; }
set { backFields[1] = value; }
}
}
public partial class PersonTest {
[TestMethod]
public void TestSample2() {
TestPersion(new Person2());
}
}/*
需要说明,因为泯灭了原来字段的个性(字段类型统一为object),现在的属性出现了磨洋工的情形(值类型对象转换为object后在读写时需要装箱和拆箱操作,这会导致其性能降低)。这是没有办法的事情,统一也是有代价的,这是我们必须承受的代价。另外和后面的收获相比,这点代价也是微不足道的。
此正是:
昔日秦皇灭六国
是非功过后人说
今天寄出大杀器
只用欧不接可特(object)
初步优化,使用字典
我们比较两个版本中不同属性定义代码的差异,由需要修改两处变为了3处:分别是返回的强制转换类型和两处数组索引。前者的类型和属性的类型一致或兼容,后者由变量名改为了数字。也就是修改的内容都是符合逻辑的,还去掉了一堆的字段定义,就算扯平了或者小输吧。这个集合目前只是暂时寄居在这个类里面,将来一定要成家立业的。在集合里面仅仅保存了属性的值,而没有其他信息,导致我们需要与属性对应的时候必须根据属性里面的索引来推测;另外如果添加属性还需要添加对应的默认值,这太不方便了。怎么办?嗯,属性名称和对应值保存在一个字典里面是个不错的主意。下面我们小小修改一下:*/
internal class Person2A : IPerson {
//private object[] backFields = { null, 0 };
private Dictionary<string, object> backFields = new Dictionary<string, object>();
public string Name
{
get { return (string)backFields[nameof(Name)]; }
set { backFields[nameof(Name)] = value; }
}
public int Age
{
get { return (int)backFields[nameof(Age)]; }
set { backFields[nameof(Age)] = value; }
}
}
public partial class PersonTest {
[TestMethod]
[ExpectedException(typeof(KeyNotFoundException))]
public void TestSample2A() {
TestPersion(new Person2A());
}
}/*
用属性名称代替数字无疑直观了许多,也弥补了收编大哥的部分自尊心:有名字的教师爷或武师,毕竟还是比仅有编号9527的奴才地位高一些的。在以后查询的时候直接查看集合就知道所有属性和对应的值了。
回头审视,修改错误
针对上面存在的问题,继续我们修改和优化旅程:我们把集合修改为字典,初始状态为空,不像上面数组版本里面保存了各个属性对应的缺省值。这样就引入了bug:字典里面没有保存值的时候返回和修改属性值都会引发异常。我们先封装操作字典的方法,然后在这些方法里面修改:*/
internal class Person3 : IPerson {
public string Name
{
get { return (string)Get(nameof(Name)); }
set { Set(nameof(Name), value); }
}
public int Age
{
get { return (int)Get(nameof(Age)); }
set { Set(nameof(Age), value); }
}
private Dictionary<string, object> backFields = new Dictionary<string, object>();
private void Set(string name, object value) {
if (backFields.ContainsKey(name))
backFields[name] = value;
else
backFields.Add(name, value);
}
private object Get(string name) {
if (backFields.ContainsKey(name))
return backFields[name];
return null;
}
}
public partial class PersonTest {
[TestMethod]
public void TestSample3() {
TestPersion(new Person3());
}
}/*
这个版本比上面的多了两个方法,分别用于对集合的读写操作。管理这些属性的老大也仅仅在这里露下面,这样当我们需要添加其他功能(比如通知)或者出问题的时候在这里修改一下就可以了。(注意get的最后一句,又引入了bug,你知道吗?)
每个属性保存两个信息:属性名称和属性值,比起数组版本似乎还浪费了空间。其实不一定,这个字典集合在没有修改属性前是空的,其内容随着修改属性的个数而增加。这在有很多属性(比如常用的控件最少都有几十个属性)的情形下,反而是节省了不少的空间。
修改到这里其实已经与第一个版本有了质的不同。不过这个字典老大只能管理一个实例对象,如果生成了100个对象,就有100个同样的老大,又出现了前面类似管理大哥的问题。怎么办?为了更方便管理,我们请一位总管来管理这些老大,而我们只需要控制这个总管就可以了。按照上面的思路,我们的总管需要管理实例和对应的全部属性,字典那是必须的。
继续优化,初露端倪
提示:到了这里,我们其实有必要把集合与对应的操作方法移到公共类里面,作为所有对象的公共容器。不过还需要一些修改进化,现在总管集合还是暂时寄居在这里,但是修改为了static(静态成员表示此成员不是某个实例所有,而是所有实例公有),也就是有了公共容器的性质:*/
internal class Person4 : IPerson {
public string Name
{
get { return (string)Get(this, nameof(Name)); }
set { Set(this, nameof(Name), value); }
}
public int Age
{
get { return (int)Get(this, nameof(Age)); }
set { Set(this, nameof(Age), value); }
}
public static Dictionary<object, Dictionary<string, object>> Containor =
new Dictionary<object, Dictionary<string, object>>();
public static void Set(object instance, string name, object value) {
var backFields = new Dictionary<string, object>();
if (Containor.ContainsKey(instance))
backFields = Containor[instance];
else
Containor.Add(instance, backFields);
if (backFields.ContainsKey(name))
backFields[name] = value;
else
backFields.Add(name, value);
}
public static object Get(object instance, string propertyName) {
var backFields = new Dictionary<string, object>();
if (Containor.ContainsKey(instance))
backFields = Containor[instance];
if (backFields.ContainsKey(propertyName))
return backFields[propertyName];
return null;
}
}
public partial class PersonTest {
[TestMethod]
public void TestSample4() {
TestPersion(new Person4());
}
}/*
进行到这里,我们获得了一个相对稳定的容器类,而实际的Sample类里面属性的方法体使用传统的强类型方法已经无法修改,也就是到达极限了。这样不同属性代码之间的差异有三处。也就是如果添加新属性,除了名称外,需要修改3个地方。这太繁琐了。为了减少修改量,我们使用反射试试:
反射参与,柳暗花明
(提示:反射是威力巨大的大杀器,与战呼局张局并列4大神器。小伙子,一般人我不告诉他,看你根骨神奇,是编程的奇才,将来编程世界的统一就靠你了。这本反射秘籍10块给你,怎么样?)*/
internal class Person5 : IPerson {
public string Name
{
get
{
var mi = MethodBase.GetCurrentMethod() as MethodInfo;
return (string)Get(this, mi);
}
set
{
var mi = MethodBase.GetCurrentMethod() as MethodInfo;
Set(this, mi, value);
}
}
public int Age
{
get
{
var mi = MethodBase.GetCurrentMethod() as MethodInfo;
return (int)Get(this, mi);
}
set
{
var mi = MethodBase.GetCurrentMethod() as MethodInfo;
Set(this, mi, value);
}
}
public static Dictionary<object, Dictionary<string, object>> Containor =
new Dictionary<object, Dictionary<string, object>>();
public static void Set(object instance, MethodInfo methodInfo, object value) {
var name = GetPropertyName(methodInfo);
var backFields = new Dictionary<string, object>();
if (Containor.ContainsKey(instance))
backFields = Containor[instance];
else
Containor.Add(instance, backFields);
if (backFields.ContainsKey(name))
backFields[name] = value;
else
backFields.Add(name, value);
}
private static string GetPropertyName(MethodInfo methodInfo) {
var name = methodInfo.Name;
name = name.Substring(4, name.Length - 4);
return name;
}
public static object Get(object instance, MethodInfo methodInfo) {
string propertyName = GetPropertyName(methodInfo);
var backFields = new Dictionary<string, object>();
if (Containor.ContainsKey(instance))
backFields = Containor[instance];
if (backFields.ContainsKey(propertyName))
return backFields[propertyName];
return null;
if (methodInfo.ReturnType.IsValueType)
return Activator.CreateInstance(methodInfo.ReturnType);
return null;
}
}
public partial class PersonTest {
[TestMethod]
public void TestSample5() {
TestPersion(new Person5());
}
}/*
这样修改以后类的代码变多了,但是在属性里面代码需要修改的地方却减少了;或者说,不同属性代码的相似度提高了,添加新属性的时候需要修改的地方就少了。我们看看不同属性的代码,除了get代码里面的return语句的强制转换类型不一样,其他完全相同。据我夜观天象,这一处修改是胎里带的缺陷,不管使用什么方法都去不掉的,大罗神仙来了也没用。
另外传入的名称参数改为了传入方法信息,因为它不仅仅携带了方法的名称信息,还携带了定义类型的信息,这样就方便我们顺手消除了一个bug。
修改到这里,基本到达我们的要求,而一个新的类也应该诞生了;同时考虑操作对象的方法:Get和Set,前者的参数要比后者少一个,另外方法信息里面的方法名称也有get和set字符串,因此就没有必要利用名称区分了;再考虑到属性信息不但携带了属性名称,还带有属性类型等等其他信息,使用它做键比属性名称好多了;最后,存放在这个容器里的对象为强引用,当其他地方都不再引用某个对象而应该被回收时,却因为这里的引用而不能被回收显然是不合理的,顺手修改一下上面存在的问题。
小结:
本文使用不同的思路,以实用为导向,用幽默风趣的语言,从一个普通的数据类作为起始点,利用统一、反射等手段,像化学的萃取实验一样,不断把相同元素集中起来;经过一步一步修改,从量变到发生质变,最后形成一个框架的雏形。为后面的演绎拉开了序幕……
此正是:
秦皇汉武功德高
一统天下胆气豪
依赖注入掀巨浪
烦人藕合一扫光
预知今天最终版本如何,请听下回分解:
Spring Net 相关文件下载:
http://files.cnblogs.com/files/mrzhuZone/SpringAOPCORE.zip
IOC演义 第一回: 重构类步步为营 新框架萌芽胎动
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。