拥抱新变化

拥抱新变化

那天写的《突破旧思维》,后来发现得到了不少认可。恰好前几天又看到了这条微博,我也粗粗写了点自己的看法:

但总觉得微博字数限制,没法把自己的一些想法完整阐述出来,于是就想再叨叨下。

我在《突破旧思维》中提到:“......找出最优方式而坚持并形成约定......”、“......要不断尝试突破旧思维,突破思维禁锢......”,其实突破旧思维的一个潜在对应就是“拥抱新变化”!

对于UML,记得十几年前OOD/OOP盛极一时的时候,一套“根正苗红”的UML工具就要好几万——比如宝兰的Together。那时候大家还是觉得Java是比较方便的,主要是在跟C/C++的比较之下。而且那时候比较“单纯”,大家觉得Java的OOP确实清晰(当然现在也是),于是就认为这就是“简化”的实质,而后续程序员们接触多了以后(比如C#的语言发展),对Java语言啰嗦笨重的吐槽也就自然而然越来越多了。为了“扩展性”、“前瞻性”、“鲁棒性”、“协作性”等等各式各样的这个性那个性,同时又受限于Java的保守语法,设计模式在当时就成了编写大规模Java应用的重要思想武器,而UML自然就成了相配套的重要实现武器。

但随着Java 8的推出,一些语言上的特性如Lambda、方法引用、函数接口等的出现,会给我们带来什么触动呢?

我们仍可以坚持你之前的最佳实践,甚至无视Java 8的存在也可以!但这样你会错失许许多多的窗外风景。

来举个例子,比如大家熟悉的“模板方法模式”——Template method pattern,它将算法的部分延迟到子类,让针对子类的计算更加“具体化”、“本地化”和“多样化”(下图选自WIKI)。

如果采用旧有的方式,我们的一般实现是(Java代码,人民币换算成美元样例):

abstract class AbstractClass {
    protected static final double RATIO = 0.1453;
    protected abstract double calculate(double rmb);

    protected void templateMethod(double rmb)
    {
        double dollar = calculate(rmb) * RATIO;
        System.out.printf(" %.4f 人民币可换算成 %.4f 美元。\n", rmb, dollar);
    }
}

class ConcreteClass1 extends AbstractClass {
    @Override
    protected double calculate(double rmb) {
        double fee = 0.001;
        System.out.printf("扣掉 %.4f 元手续费。\n", rmb * fee);
        return rmb * (1.0 - fee);
    }
}

class ConcreteClass2 extends AbstractClass {
    @Override
    protected double calculate(double rmb) {
        double fee = 0.002;
        System.out.printf("扣掉 %.4f 元手续费。\n", rmb * fee);
        return rmb * (1.0 - fee);
    }
}

// Client
public class TemplateMethodPattern {
    public static void main(String[] args) {
        AbstractClass c1 = new ConcreteClass1();
        AbstractClass c2 = new ConcreteClass2();
        c1.templateMethod(1_000_000.0);
        c2.templateMethod(1_000_000.0);
    }
}

在Java 8之前,无论怎样,设计模式能够帮助我们确保遵循“最佳实践”,但也无可否认其啰嗦笨重的现实。借助Java 8新引入的Lambda,我们就可以这么做了——首先,稍稍改动下模板方法成:

protected void templateMethod2(double rmb, DoubleFunction<Double> calculate)
{
    double dollar = calculate.apply(rmb) * RATIO;
    System.out.printf(" %.4f 人民币可换算成 %.4f 美元。\n", rmb, dollar);
}

然后调用:

c1.templateMethod2(1_000_000.0, rmb -> {
    double fee = 0.001;
    System.out.printf("扣掉 %.4f 元手续费。\n", rmb * fee);
    return rmb * (1.0 - fee);
});

c2.templateMethod2(1_000_000.0, rmb -> {
    double fee = 0.002;
    System.out.printf("扣掉 %.4f 元手续费。\n", rmb * fee);
    return rmb * (1.0 - fee);
});

输出结果都是:

扣掉 1000.0000 元手续费。
 1000000.0000 人民币可换算成 145154.7000 美元。
扣掉 2000.0000 元手续费。
 1000000.0000 人民币可换算成 145009.4000 美元。

由此可见,对于不同的计算逻辑,如果动态性比较强而逻辑不算复杂,通过Lambda方式传入,确实从现实意义层面弱化了传统的开发模式,并会带来实际好处。例如,我们通过Lambda,还可以很方便地将该程序拓展为针对不同外币、不同汇率和不同手续费的兑换,而且此时根本也不需要什么抽象类、模板方法模式,把抽象类中原有的templateMethod方法直接定义为普通类方法就成:

protected void templateMethod3(double rmb,
                               double ratio,
                               double fee,
                               String currency,
                               DoubleBinaryOperator calculate)
{
    double dollar = calculate.applyAsDouble(rmb, fee) * ratio;
    System.out.printf(" %.4f 人民币可换算成 %.4f %s。\n", rmb, dollar, currency);
}

调用时:

c1.templateMethod3(1_000_000.0,
        0.1166,
        0.001,
        "英镑",
        (rmb, fee) -> {
            System.out.printf("扣掉 %.4f 元手续费。\n", rmb * fee);
            return rmb * (1.0 - fee);
        });

或者如果觉得箭头函数(Lambda)略显凌乱,也可以构建个“方法引用”出来:

// Client
public class TemplateMethodPattern {
    public static double calculate(double rmb, double fee) {
        System.out.printf("扣掉 %.4f 元手续费。\n", rmb * fee);
        return rmb * (1.0 - fee);
    }

    public static void main(String[] args) {
        // ...
        c1.templateMethod3(1_000_000.0,
                0.1166,
                0.001,
                "英镑",
                TemplateMethodPattern::calculate);
    }
}

想法

例子说完了,但我想表达的意思是写程序是一件“与时俱进”的事情,如果停滞你的脚步,不对变化保持敏感性,就可能掉队或失去编程的乐趣。

设计模式仍是一件重要工具,但不要总是信奉一些教条,比如小型程序也要一板一眼上各种设计模式,而世上绝大部分程序都不算大型程序。

“隔行如隔山”,像Lambda、高阶(回调)函数在某些语言比如JS、C#、Lisp、Scala中早已司空见惯,而Java语言中则算是新特性。跨界掌握点别的风格的语言,有助你扩展知识面。

编程是创造性的实践,偏要安上规则死搬硬套的话,基本会陷入“东施效颦”的可笑境地中,各种规则和“最佳实践”是参考和引导,绝对不是“模子”。变化的主动性要时刻掌握在开发者手中,才可能对变化应对自如。

编辑于 2017-03-26 08:48