惊人之语

    FireLong在他的Blog中写到:将所谓常用的设计模式变成语言构造的一部分,是C#设计思想里面又一个的严重错误。

谁为谁服务?

    在FireLong的观点中,编程语言是为设计模式服务的,因为有了这些设计模式,所以,语言不应该有这个代替这些设计模式的语法,语言要做的仅仅是实现这些设计模式的基础(Design pattern first)。

    而我的观点却恰恰相反,设计模式仅仅是为了补足语言本身没有原生支持的,但又是经常会遇到的问题,换句话说,就是语言是原则,模式只是条例(Language first)。

设计模式的前提是什么?

    GoF的23种设计模式的前提是什么?如果看了这么久设计模式,却没思考过这个问题,那真的是白学了。

    先说说通用的前提,这23种设计模式都需要一门静态类型的面向对象的语言(c++,c#,java都可以),需要有语言层面支持的静态单分派(overload)和动态多分派(虚方法+override,所以vb[不是vb.net]用设计模式就不是特别舒服)。

    那么在一个不是静态类型的面向对象的语言里面,设计模式的作用就没有这么大了(虽然有些思想还是可以借鉴的)。想一下在Haskell(静态类型函数式编程语言)里面,怎么使用观察者模式?Haskell不需要观察者,因为它有高阶函数。

    GoF的23种设计模式在一定程度上,可以说是写给Java的开发人员看的,c++有指针和函数指针,c#有委托和事件,不使用观察者模式照样能把问题搞定。而Java哪?指针不让用,委托和事件也没有,问题还是摆在那里,怎么办?这时候,GoF站了出来,指出一条明路,用观察者模式吧。

    原因在于Java的语言设计目标

There were five primary goals in the creation of the Java language:[19]
It should be "simple, object oriented, and familiar".
It should be "robust and secure".
It should be "architecture neutral and portable".
It should execute with "high performance".
It should be "interpreted, threaded, and dynamic".

    所以,Java没有指针之类的,只有面向对象的部分,所有问题必须依靠面向对象的方式去解决问题。而GoF的23种设计模式也正是建立于一个相对较纯的面向对象语言。

面向对象不是万灵药

    软件业对面向对象的迷恋以及有好几年了,面向对象可以解决一切问题,没错,但是面向过程一样可以,函数式编程也可以;静态类型可以,动态类型也可以。因此,能不能解决一切问题并不是这个方法论好不好的核心问题。

    在大家都能解决问题的前提下,谁能最简单、最合理的解决问题才是核心问题。

    来看看如何打印出Fibonacci数列的前6个,首先是纯粹用Iterator模式的来实现:

public class FibonacciBasicIterator : IEnumerable<int>
{

    public IEnumerator<int> GetEnumerator()
    {
        return new FibonacciEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public static void Print()
    {
        int count = 0;
        IEnumerator<int> etor = (new FibonacciBasicIterator()).GetEnumerator();
        while (etor.MoveNext())
        {
            if (count++ >= 6)
                break;
            Console.WriteLine(etor.Current);
        }
    }

    private class FibonacciEnumerator : IEnumerator<int>
    {

        private int a;
        private int b = 1;
        private int c = 1;

        public int Current { get; private set; }

        public void Dispose() { }

        object IEnumerator.Current { get { return Current; } }

        public bool MoveNext()
        {
            Current = b;
            a = b;
            b = c;
            c = a + b;
            return true;
        }

        public void Reset()
        {
            throw new NotImplementedException();
        }

    }

}

    然后看看在c#的foreach+yield下如何实现:

public class FibonacciInCSharp : IEnumerable<int>
{

    public IEnumerator<int> GetEnumerator()
    {
        int a = 0;
        int b = 1;
        while (true)
        {
            int c = a + b;
            yield return c;
            a = b;
            b = c;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public static void Print()
    {
        foreach (int item in (new FibonacciInCSharp()).Take(6))
            Console.WriteLine(item);
    }

}

    看起来是不是简单多了,别高兴的太早,这个问题在函数式编程领域中绝对是小儿科级别的,以Haskell为例(by GHC 6.10),可以这么定义Fibonacci数列:

fib 1 = 1
fib 2 = 1
fib n = fib (n-1) + fib (n-2)

    或者用1行:

fib = 1 : 1 : zipWith (+) fib (tail fib)

    仔细看的话,发现这个就是数学上的定义。

    而打印出前6个只需要:

putStr $ concat $ map (\x –> (show x) ++ “\n”) $ take 6 fib

    现在还认为面向对象是万灵药吗?

    对于不同领域的问题,需要找到最接近这个领域的语言才是最合理的,最省力的。

GoF不是God

    最后,要特别声明的一点是GoF不是God,不要相信GoF能帮你解决所有的问题,也不要相信GoF的23种设计模式都是“真理”,“真理”只有出现了适用边界才能变成真正的真理。

    20世纪以前的经典力学是“真理”,人们认为它能运用于万事万物,直到20世纪出现了量子力学和相对论,人们才认识到经典力学的范围,于是在宏观低速领域,经典力学才变成了真正的真理。

    同样,23种设计模式各自都有自己的适用范围,看清楚适用范围,有时候比了解模式本身更重要(不知道模式,最多没有理论指导,知道模式却不知道什么时候能用,什么时候不能用,那是对模式的曲解,在某些情况下比不知道模式更可怕)。

posted on 2010-07-04 15:30  Zhenway  阅读(2413)  评论(11编辑  收藏  举报