继续贴以前的文章,这篇文章是过年时候写的。
最近公司买了4本书,其中三本是《Art of Programming》,还有一本就是这里要说到的《Design Patterns》。公司里面其他人拜Donald为神人,于是三下五除二就拿走了那三本。等我过来选的时候,就只剩下《Design Patterns》了。有人问我,你不可以去抢啊?呵呵,我一生不会拜什么人为偶像,就算他再强我也不会拜,我只会去学。拜了有什么好处呢?除了一种狂热就没什么了,而这狂热很可能不能够让你以冷静、公正、辩证的眼光去分析这个人的一切。所以我不以为《Art of Programming》是什么圣经或者红宝书白宝书之类的,有机会一定会看,但不一定现在。由于我觉得英文版的东西读起来比较慢,而这三本书随便哪一本都是一个大块头,放假就那么几天时间肯定读不完,带来带去的多累啊。权衡之下,还是觉得带本中文版的Design Patterns比较合算。
之前在网上面也看到一些关于Design Patterns的讨论,对什么是Pattern和有什么Pattern虽然不是很清楚,但也算是略知一二。在回家的火车上我就开始看这本书了,看的时候有一种激动——不是说“噢!原来可以这样子的!”,而是说“噢!原来我以前的各种想发确实就是这里面的各种Patterns!”。甚至有时候觉得这里面的部分Patterns我有基于.NET的更好的解决方案,可以说是一个超集。当然,这里面也有许多的东西我真的一点也不了解,也从来没有想过可以这样做,但是却没有超出我的想象范围。读这本书的时候真的很佩服这本书的作者,写的真的很好,好在哪里呢?其实不是里面说的各种设计模式很好,或者描述的很清晰——确实如此,最好的地方应该是里面每一章都用到一个例子,并且这个例子的分析还有应用的设计模式真的让你觉得这个设计工作真的做得很清晰,清晰到你发现按照他的分析可以把一个有很复杂功能和很好的扩展性的程序写得如此清晰简单高效,清晰到你有一种冲动去把这个例子按照他的分析用某种语言把他写出来,去亲身感受这种极致这种完美。如果你在心里有一种类似于设计模式这样的想法,那么看着这些例子很容易就能把整个模式的细节都看明白。
目前我没看完,仅仅是看了前两章还有第三章的一半,却也发现了一些缺陷。其中比较大的问题就是“类”、“类型”、“对象”、“实例”、“接口”这几个概念有点点互相混淆,最为严重的就是“类”、“类型”和“接口”里面是讲的那个混乱啊!举个例子,在1.6.4.1这里是这么写的:
1. 类继承与接口继承的比较
理解对象的类(class)与对象的类型(type)之间的差别非常重要。
……
当然,对象的类和类形是有紧密关系的。因为类定义了对象所能执行的操作,也定义了对象的类型。当我们说一个对象是一个类的实例时,即指该对象支持类所定义的接口。
……
理解类继承和接口继承(或子类型化)之间的差别也十分重要。类继承根据一个对象的实现定义了另一个对象的实现。简而言之,他是代码和表示的共享机制。然而,接口继承(或子类型化)描述了一个对象什么时候能被用来代替另一个对象。
大家能够看到标题里面写道“类继承与接口继承……”,你是否会觉得他这里的“类”就是class,“接口”就是interface呢?当时我想应该是的,我想这里所用到的“类”和“接口”等概念就应该是现代的这种概念。然而当我看到紧跟在后面的第一句话,我就把眉头皱的用两头牛的拉不开了。class和type?看到了半天我终于明白过来了,这兴许是类和接口的概念刚刚出来没多久,应用还不是非常之成熟的时候的说法,这种说法比较接近于“人类本地语言(Human's Native Code)”的理解方式。这里所谓的类实际上的意思是“对应着一堆有着具体代码的函数方法的某个东西”,他不代表那种抽象的“能干什么”某种机能,而所谓的类型是“指出他能干什么”。实际上我是不太喜欢的,毕竟研究什么东西就要用那种东西所用的语言,比如研究数学我们就得说数学语言,研究英语就得用英语,你要用汉语写小说的方式去说,那是无法非常清晰的传达出来的。当然,对于什么是“类”什么是“接口”,我们也是一直在摸索,现在关于这些概念我认为应该是非常成熟的了。我来说说我对于“类”和“接口”的理解,不对的话也欢迎来说评说评:
什么是类呢?类,class,就是指一样比较完整的“组织”,可以是“器官”也可以是“生物”,总之他是一个完整的“实体”,虽然可能会被“抽象”的比较虚无。什么是类的继承呢? 类的继承,inherits a class,意味着某种“组织”是另外一种“组织”的抽象称呼或者实体进化,属于同一个基类(base class)的派生类(derived class or subclass)很可能有着部分相同的处理方式,或者只是在共同的处理方式的基础上分别作出了不同的调整。什么是接口呢?接口,interface,就是指某种特定的“功能”,这个“功能”也许指需要一个特定的“机制”,也需要通过一套特定的“机制”来达成,但至于具体怎么完成这个“功能”、“机制”里面用到什么化学成分那就由具体的那个“组织”决定了。什么是接口的继承呢?接口的继承,inherits an interface,意味着某种“功能”是对另一种特定“功能”的特殊化描述,同属一个基本接口(base interface)的派生接口(derived interface)都拥有基本接口的功能,只不过不同的派生接口描述了这个功能更加细节的部分,只不过从不同的方面去描述细节。打个比方吧:
“生物”就是一种class,“生物”下面可以派生出“动物”、“植物”、“微生物”,下面再派生出各种“纲、目、属、科”。“繁殖”可以说是一种接口,为了能够“繁衍”需要“获取足够的能量”、“生成下一代的基因”、“生产下一代”、“照顾下一代”等等,“繁殖”仔细分有“有性繁殖”、“群体照顾下一代繁殖方式”等。
嗯,也许这么说太复杂了,也许会迷糊。算了,就看个人悟性了。
好了,让我们回过头来接着看看上面书上的那段话,“当然,……接口。”,这一段话应该说比较正确的,但就是那个“紧密”需要斟酌。我觉得这个“紧密”也就仅仅体现在:如果要求一个类符合某个接口,那么必须在定义这个类的时候就定义好这个类符合这个接口,换句话说就是我们必然知道某个类它实现了哪些接口。但是从另外一个角度讲,类和接口是不紧密的,因为某个接口不可能指定只有那些类才能够实现,或者说任何类都可能实现某一个特定的接口,哪怕它们之间没有任何必然的联系。简单说就是接口和类之间是没有必然的(预期)联系。
如果说这一段只是没有用辩证的方法看问题,把事物的另一个方面表述出来,那么下面的那一段就是把问题搞错了。也许会有人觉得没有错,但我是坚持认为是错了。他这里说的“类继承和接口继承(或子类型化)之间的差别”实际上说的是通过“类继承和通过接口(注意,这里没有接口继承什么事)进行代码重用的差别”。诶,对了,这里讲的根本就没有继承什么事,而是代码重用的事。说到这里,我也来说一下这两种代码重用方式的差别,权当凑个热闹吧:
通过类继承进行代码重用,我们能够很方便的继承了整个的处理方式,能够体现出一种进化,很多东西我们可以一来一种基本的、省缺的方式去处理而不必为此费心,或者只要在这个基础上做出一点调整就行了,只有一小部分我们需要重新写。但是通过类继承进行代码重用也会带来很多非常恶劣的副作用,比如“类爆炸”还有“版本控制问题”等等。平时你或许不会感觉到,但当你对一个基类进行结构修改的时候你就会发现所有的派生类,以及使用这些类的程序都需要进行修改。也许这些修改的每一部分的工作量都不是很大,但是需要修改的地方太多了,甚至很可能带来一些隐藏的问题,并且还会出现错误扩散。你很难对这些东西进行调试和修改,究其原因,是因为当需要增加一些新东西的时候,你需要对原有的代码进行修改,因为这种代码重用方式是硬的、死的,一旦修改得不好就会出现“硬伤”。
相反,通过类接口进行代码重用,我们很多时候不需要为了增加新东西而对原有的结构进行调整,你只需要按照既定的接口写出新的东西就完了。对接口调用虽然是死的,但是接口背后的东西却是活的,因此可以说整个设计会比纯粹用类基呈显得灵活多了。由于不需要对原有的东西进行修改,所以基本上不会出现“硬伤”。但是我也要说这种方式也有它的局限性的:首先是为了能够兼容各种不同的具体实体,接口的设计需要比类的设计更加费心细思量,需要提取共有的东西而忽略部分的特性。对于什么是特性什么是共性,判断起来可不是一件容易的事情。判断共性过多了,可能大部分实体都不会真正去实现某些“共性”,成为了一种不必要的负担;判断特性过多了,可能现在设计的实体是能够设计出来,但是未来需要用新的方法去实现某种功能的时候却发现缺少了所需要的东西了。这里需要特别说明一句,共性与特性之间并被一个界限,判断的方法也没有一个固定的标准,主要是看你对于未来和现在是怎么取舍的,怎么才是合理的。其次,接口本身仍然是死的东西,一旦出现新的需要而原有的接口不能够满足这些要求的时候,我们仍然不得不面对要修改原有的东西的问题。这个时候可能比类继承还要麻烦,因为现在我没有好的办法去解决这一类的问题(也许我还没有看到),可能会采用“继承接口”这样的方法吧?但是这样不仅会面对“类爆炸”、“类版本控制问题”,还会面对“接口爆炸”、“接口版本问题”、“类与接口的版本协调问题”。或许这些是留给我们去解决的问题吧,保不准哪天这个问题就被看官当中的哪位给解决了,还出一本叫做《接口模式》("Interface patterns -- the nexgen of design patterns")的书呢!
既然说到这两种代码重用方式,我就不得不提一下这本书里面所说的:“很多设计者都过分依赖于类继承这种代码重用方式,而忽略了或者过少的使用了通过组合进行代码重用的方式。实际上通过对不同的东西进行组合能够令你的设计更加灵活,因为修改它们之间的关系远远比修改他们里面的具体实现来的方便,副作用也更小。”上面引号当中的不是原话,原意确实是这样的。这里面说到“通过组合进行代码重用”,我们可以认为这种方式的含义比“通过接口”还要广泛,因为我们不一定组合接口,也许仅仅是组合不同的类。但是一般来说,组合的时候我们很可能会需要用到接口,用接口会比你直接用类更加灵活。为了对这两种方式进行区分,我更加原意称后者是“通过接口进行代码重用”,尽管有时候实际上没接口什么事。当然,代码重用的方式还有几种,大部分在《设计模式》里面都提到了,比如说委托和参数化类型,但是我还想到了一些别的方式。如果日后有时间,并且我还记得住是什么方式,我会写出来给大家评论一下的。
今天就到此为止吧我看,这篇文章也写得够累得了——足足写了两年啊:羊年和猴年!后半篇我可是边看着中央电视台的春节联欢晚会边写出来的哦!下一篇,我想我将会谈一下对于我所看到的部分模式的个人理解,以及对某些使用了这些模式的具体东西进行分析和评述,也可能说一下现在很多人的做法和这本书里面例子的水平相差是多么远,或许会写一些我个人总结出来的模式。看来只有天晓得我会写些什么了……(KAO,这里面有5kCC啊!注:CC = Chinese Character)
文章来源:http://dotnet.blogger.cn/sumtec/articles/178.aspx
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器