先说点题外话。最近想做一个Computational Graph Database (https://github.com/vczh/vczh_toys/blob/master/PlayWithLinux/Database/draft),顺便练习一下Linux下写C++程序的技巧,深刻的体会到了一个道理。Linux下有这么多烂工具,每个人做了一个工具,都会有另外一个人觉得这个工具很烂,然后就做了一个更烂的来恶心他。我本着这种开源的精神,写了一个GayMake(误,于是就有了这次的贴图。倘若最后这个项目没有坑,以后开源狗跟我吵架,我就可以糊他一脸,说【在linux瞎搞编程你也搞不过我】,啊哈哈哈哈。
不管是设计模式也好,别的模式也要,他都是为了解决问题而发明的有效的方法。除了我们已经熟悉的23种设计模式以外,还有MVVM、Combinator等其它的东西,都已经是前辈们经过多年的摸爬滚打总结出来的,其有效性不容置疑。我这篇文章也不会用来证明设计模式是有用的,因为在我看来,这就跟1+1=2一样明显(在黑板上写下1+1=2)。
架构的设计也是一样。你要做一个分布式系统,在哪里放gate way,在哪里放database,在哪里放cache,在哪里放计算节点,这些东西都已经是早就总结好的了。类似的东西就叫pattern。一个architecture就是由很多个pattern组合起来的。除此之外,做游戏也好,做编译器也好,设计数据库也好,每一大类的问题都有他们自己的pattern。他们的档次跟设计模式不一样,但是要解决的问题都是一样的,就是让你高效地解决问题。
那为什么我们需要学习设计模式呢?这很明显,这就跟我们看别人的代码来学习一样,是为了学习里面的精髓。每一本设计模式的书都会告诉你,这些都是在讲究如何对修改封闭,对扩展开放的事情。前几天我在看几年前一个数学老师的公开课,叫数学大观(数学大观 - 专辑)。我觉得里面有句话就讲得很好。我们学东西,重要的是学idea,次要的是学technique。翻译成编程的语言就是,我们学设计模式,是为了学习如何合理的组织我们的代码,如何解耦,如何真正的达到对修改封闭对扩展开放的效果,而不是去背诵那些类的继承模式,然后自己记不住,回过头来就骂设计模式把你的代码搞复杂了,要反设计模式。不要见什么都反,有空应该好好读一读毛*东的辩证法,知道设计模式有好的一面也有不好的一面,当然总的来说设计模式好的一面比较大。
设计模式要是真的学会了,你们会发现在写代码的时候,脑子里根本没有什么设计模式,你都已经融会贯通了。代码写完了一看,这里有模式,那里也有模式。这就如同我们讲话不会去考虑语法,但是说出来大部分的话都是符合语法要求的。这也如同我们写程序的时候不会总是去想程序的语法的问题,我们自然而然写出来的东西就是可以编译的。道理都是一样的。
不过为了合理的利用设计模式,我们应该明白一个概念,叫做扩展点。扩展点不是天生就有的,而是设计出来的。我们设计一个软件的架构的时候,我们也要同时设计一下哪些地方以后可以改,哪些地方以后不能改。倘若你的设计不能满足现实世界的需要,那你就要重构,把有用的扩展点加进去,把没用的扩展点去除掉。这跟你用不用设计模式没关系,跟你对具体的行业的理解有关系。
倘若你设计好了每一个扩展点的位置,那你就可以在每一个扩展点上应用设计模式,你就不需要去想到底这个扩展点要怎么实现他才会真正成为一个扩展点,你只需要按照套路写出来就好了。
如果你发现你最后的代码长得跟设计模式不一样,这不一定代表你没有用到设计模式,也不一定代表这个设计模式没有用。设计模式归根结底就是因为你使用的程序语言的抽象能力不足才发明出来的。譬如说那个Listener模式,在C#里面就是一个event关键字搞定,你不需要去写一大堆框架代码来增加这个扩展点。相反,你在Java里面就需要这么做。因此你可能觉得Listener模式在Java有用,在C#没用,其实不是这样的。
真正的情况是Anders Hejlsberg很牛逼,他帮你把这个设计模式做进了语法,你不需要痛苦地写一大堆框架代码就可以用了。这种东西就叫语法糖。有语法糖就不需要写框架代码抄设计模式,没有语法糖你就需要写框架代码抄设计模式。为了解决这一个问题,你所需要放进去的扩展点,无论你用什么语言,他都是这么多。区别只有你到底是怎么写出来的。所以语法糖好不好,当然好。你不想学语法糖就老老实实抄设计模式,人家也没有把语言设计成你学不会语法糖就不能用。这很公平。
我为什么喜欢Haskell、C++、F#这样的语言,因为它们的代码是可以被计算出来的,因此我可以根据需要随时添加我自己的语法糖,而且还不需要改变语言的语法。当然根据这条标准,我本应该喜欢Lisp的,无奈Lisp的括号太多,噪音太大,我不喜欢这样的东西。同样的理由也见GayMake。那么简单的东西要生成一大坨参杂了各种符号的字符的makefile,那个makefile根本看不懂,虽然已经很接近我手写出来的样子了。其实我就是看了一遍makefile的说明,然后手写了几个makefile,然后照着我写出来的makefile把makefile生成器给写出来的。
设计模式的好处还有一点,就是他做出来的样子性能很高。虽然扩展点的意思就是我在编译的时候不知道到底会命中哪个扩展,但是大多数扩展都是O(1)命中的(除了责任链模式) 。与之相关的还有IoC,也就是Inverse of Control,这也是一个好东西。Inverse of Control讲的是,类与类之间的依赖是可扩展的,而且是强类型的,并且你还不需要在类的内部指定(因此跟组合不一样)。当一个类需要用到他的依赖的时候,他不需要主动去获取他,而是可以等别人把依赖塞给他,然后再做事情。类似的事情在GacUI(www.gaclib.net)里面大量的使用。
讲了这么多好处,那到底我们要怎样才能学会设计模式呢?答案只有一个,就是创造条件去使用设计模式。很多人总是觉得,要通过简单的程序和例子来学设计模式。这是不对的。设计模式就是因为情况复杂了所以才会出现的,所以我们只能通过复杂的程序来学习设计模式。你不管看别人的程序也好,自己写程序练习也好,那必须要复杂,复杂到你不用设计模式就做不下去,这才能起到学习设计模式的作用。
为什么我对设计模式那么熟悉,这跟我长期以来造的轮子都很复杂是有关系的。不过我有很多设计模式的知识和体会是在搞各种各样的奇怪的语言,譬如说Haskell啊Prolog这些东西才学到的。为什么呢?有些模式就是从那个语言出来的。你倘若不去搞一搞那个语言,你就不会去看到那个语言的材料,你就不知道那个模式可以被用到你自己喜欢的语言上面。你可能会想,那总有人会这么做啊,譬如说我是吧。但是我是不会把应用到正常语言的过程详细的搞出来给你们听的。原因在于,那个模式,譬如说Combinator,在Haskell下面搞出来很容易,但是在C++里面搞出来就会有大量的框架代码。所以你去看Haskell如何用Combinator,肯定比看C++如何用Combinator难度要低得多。所以倘若你们问我怎么搞定Combinator,我只会告诉你,好好弄Haskell,然后去看Combinator。因为用C++解释太麻烦了,我不会这么做的。
当然,你们有些人肯定不会因为这样就去看Haskell,这只能说明学习Combinator对你来说不是一个迫切的事情。你没了Combinator还能活下去,只是你永远也体会不到你的程序用Combinator可以简化的同时还容易扩展到什么程度。
最后讲一点,Dog fooding是很重要的。微软的东西为什么这么复杂还能继续开发下去,除了代码组织得好以外,质量也好。这跟我们长期自己使用自己的软件给他们积极地爆bug是有关系的。你们自己学东西也一样。譬如说你学了如何自己实现正则表达式,你就把它实现出来,然后以后就用自己的这个实现来干活。东西用的多,爆出来的bug也就多,你对他的理解也就越深刻 。倘若你们只是看一眼文章,自己做个小玩具,甚至连小玩具都不做,就这么过去了,肯定什么也学不会。