最近两个星期,我使用 plantuml (贝尔实验室出品了一个超级绘图工具 graphviz, 这是一个包装版)把我的绘图项目做了一次全面的接口和类的可视化。使用了很多设计模式,包括:桥接、装饰器、生成器、抽象工厂。绘制完后,图像是很美的,接口之间的交互和参数定义清晰优雅。很漂亮!
然并卵!
这个项目在开发之处已经违反了我的一些感觉,对于程序设计的感觉。从我对数据库和服务器的多年经验,使用基于数据表和数据解释的抽象结构,你总能获得最简单易用可扩展的软件结构。
不过,这个绘图项目真的很复杂,涉及了很多的多态和关联。比如,在一个长的列表中存储种类不同的图形,这些图形存储的绘图数据和相关信息都不同,我需要把这些数据视做同一种类型,然后迭代它们,选出需要的一个并且使用它的相关信息。所以,我尝试使用学术界的设计模式来解决其中的问题。
当项目变得很庞大的时候,我意识到设计模式屁都不是。诸如桥接、装饰器以及其他,都是建立在一种假设,假设你的父组件和子组件总是可以忽略对方的细节,而可以统一的处理它们。比如,面包有奶油味、抹茶味、水果味,面包又有低档材料、高档材料,那么你可以把味道和材料分为两个不同的接口,然后各自抽象,并且组合这两个接口生成更丰富的面包,比如低档材料的抹茶味面包。但是,真实的编程世界中,这样的理想状态非常少。在真实的编程世界中,面包还想要更多的东西,比如奶油味的有糖,抹茶味的没有糖,有糖的面包放在左边柜台上,没有糖的面包放在右边柜台上。看到了吧,复杂度升级了,柜台跟面包有没有糖是绑定的。这意味着,如果你想像前面那样抽象两个接口---味道和材料,那你现在必须考虑柜台。因为低档材料的抹茶味面包是没有糖的,放在右边柜台。现在,你不得不抽象出味道和柜台的关系。在上面的接口之上再增加一层。每当你的需求复杂一点,这种层就会升级。比如,红糖面包和白糖面包。
总之,就算设计模式避免了类继承的爆炸,但是也避免不了抽象层级的复杂。
所以,我觉得我又不会编程了。于是,我尽可能的重新思考这些设计,并且重新在网络上搜寻曾经支持我的设计论调:面向数据结构编程而不是对象。如果不是为了这个绘图项目,我绝对不会冒险再一次使用设计模式和面向对象。
我当然搜到了一大堆 Linus 排斥面向对象和 C++ Java 的话语,从感觉上,这些就是我面临设计困难时候的感觉。我曾经无数次这样解决我的程序设计。
git的设计其实非常的简单,它的数据结构很稳定,并且有丰富的文档描述。事实上,我非常的赞同应该围绕我们的数据结构来设计代码,而不是依据其它的,我认为这也是git之所以成功的原因之一。[...] 依我的观点,好程序员和烂程序员之间的差别就在于他们认为是代码更重要还是数据结构更重要。
在庞大的项目中,人们对不是自己开发的模块并不了解,能快速理解其他模块中函数的确切含义才能提高开发效率。而C++引入的各种抽象则使代码非常依赖上下文,想理解一段代码,需要看多得多的上下文。
面向对象语言以对象为核心,加一些相关联的方法,简直是呓语。重要的东西应该是数据结构,对象本身有啥重要?真正有意思的,是在不同类型的不同对象交互而且有锁规则的时候。但是,即使是这时候,封装什么“对象接口”也绝对错误,因为不再是单一对象的问题了。
有趣的是,这里有一篇另外一位前辈的很早的文字,推在 Google+ 上,来自 Unix 核心创建者之一 Rob Pike:
原文链接
A few years ago I saw this page: http://www.csis.pace.edu/~bergin/patterns/ppoop.htmlLocal discussion focused on figuring out whether this was a joke or not. For a while, we felt it had to be even though we knew it wasn't. Today I'm willing to admit the authors believe what is written there. They are sincere.
But... I'd call myself a hacker, at least in their terminology, yet my solution isn't there. Just search a small table! No objects required. Trivial design, easy to extend, and cleaner than anything they present. Their "hacker solution" is clumsy and verbose. Everything else on this page seems either crazy or willfully obtuse. The lesson drawn at the end feels like misguided epistemology, not technological insight.
It has become clear that OO zealots are afraid of data. They prefer statements or constructors to initialized tables. They won't write table-driven tests. Why is this? What mindset makes a multilevel type hierarchy with layered abstractions better than searching a three-line table? I once heard someone say he felt his job was to remove all while loops from everyone's code, replacing them with object stuff. Wat?
But there's good news. The era of hierarchy-driven, keyword-heavy, colored-ribbons-in-your-textook orthodoxy seems past its peak. More people are talking about composition being a better design principle than inheritance. And there are even some willing to point at the naked emperor; see http://prog21.dadgum.com/156.html for example. There are others. Or perhaps it's just that the old guard is reasserting itself.
Object-oriented programming, whose essence is nothing more than programming using data with associated behaviors, is a powerful idea. It truly is. But it's not always the best idea. And it is not well served by the epistemology heaped upon it.
Sometimes data is just data and functions are just functions.
--- Rob Pike (One of the Unix creators (Ken Thompson, Dennis M. Ritche, and Rob Pike))
几年前我看到了这个网页: http://www.csis.pace.edu/~bergin/patterns/ppoop.html
我真的不知道这篇文章到底是不是在搞笑。读了一下,我虽然很想说这不是一篇搞笑的文章,但是,拜托,它根本就是。让我来跟你们讲讲他们在搞笑什么吧。
e...按照他们的话语,我应该称自己为 hacker (黑客),不管我不关心这些。Hello! 你只需要一个小的不能再小的 table ! 根本不需要什么对象。朴素平凡,容易扩展,容易清除,(比起他们的那种设计)多 TM 简单。他们的 “hacker solution” 真的是又蠢又笨。他们写出来的那堆东西到处透漏着疯狂和愚蠢。他们缺乏技术认知。
很显然,OO 的狂热者们害怕数据。他们喜欢用语句或者构造器来初始化 tables 。他们根本不写 table-driven 的测试。Why is this? 得有多大的心才会选择用多级并且多层的类抽象,而不去用一个小小的三行 table ? 我曾经听说有人用各种 OO 的东西替换掉 while 循环。
不过好消息是,hierarchy-driven, keyword-heavy, colored-ribbons-in-your-textook orthodoxy 这些东东快到头了。更多的人选择组合而不是继承。有些人已经重新开始认识 OO。
面向对象编程语言,其本意是使用数据和相关的行为进行编程,这是一个很好的想法。事实确实如此。但是,这个想法并不总是最好的 idea。 这个想法并没有完全的认知编程的世界。
Sometimes data is just data and functions are just functions.
--- Rob Pike (Unix 创建者之一的 (Ken Thompson, Dennis M. Ritche, and Rob Pike))
没错,我们需要的就是数据的抽象和数据的解释器。用表来存储你需要的各个数据,对于多态,C 语言中简单直接干净:union。使用这么一个简单的结构,你能存储各种不同的类型,而且你只需要存储他们的指针,这意味着你不会浪费多少内存,同时你能获得相同内存段但是数据不同的抽象。
然后,使用一个链表或者数组,把这个 union 装进去,遍历,cast,然后使用你需要的特定数据。
很多语言都有 union 的变体,现代语言中的泛型就是 union 的一种语法糖,但是你往往忘记了这种结构的真正价值和用意。仔细体会下这个全新的设计:
enum ShapeKind {
skLINE, skPORT, skBOARD
}
class Shape {
kind: ShapeKind
value: Line | Port | Board
contains(x: number, y: number): boolean
}
class ShapeContainer {
shapes: Array<Shape>
search(x: number, y: number): [ShapeKind, Shape]
}
type
ShapeKind = enum
skLINE, skPORT, skBOARD
Shape = ref object
case kind: ShapeKind
of skLINE:
line: Line
of skPORT:
port: Port
of skBOARD:
board: Board
contains: (x: number, y: number): bool
ShapeContainer = object
shapes: seq[Shape]
proc search(c: ShapeContainer, x: number, y: number): tuple[kind: ShapeKind, shape: Shape]