重构-以多态取代条件表达式

复杂的条件逻辑是编程中最难理解的东西之一,给条件逻辑添加结构

可以将条件逻辑拆分到不同的场景(或者叫高阶用例),从而拆分条件逻辑。使用类和多态能把逻辑的拆分表述得更清楚,多态是改善复杂条件逻辑的有力工具。

有两种常见场景,

一种是,好几个函数都有基于类型的switch语句,每个类型处理各自的条件逻辑。把switch中每个分支逻辑创建一个类,用多态来承载各个类型特有的行为。 

另一种是:有一个基础逻辑,在其上又有一些变体。基础逻辑放进基类,基础逻辑可能是最常用的,也可能是最简单的。变体放进子类,强调与基类基础逻辑的差异。

一、改造switch

1,动机

例子,朋友有一群鸟,他想知道鸟飞得多快,以及鸟的羽毛是什么样的。

//朋友有一群鸟,他想知道鸟飞得有多快,以及它们的羽毛是什么样的。

//飞的多快
function speeds(birds) {
    return new Map(birds.map(b => [b.name, airSpeedVelocity(b)]));
}
//羽毛
function plumages(birds) {
    return new Map(birds.map(b => [b.name, plumage(b)]))
}


function plumage(bird){
    switch(bird.type){
        case 'EuropeanSwallow':
            return "average";
        case 'AfricanSwallow':
            return (bird.numberOfCoconuts>2)?"tired":"average";
        case 'NorwegianBlueParrot':
            return (bird.voltage>100)?'scorched':'beautiful';
        default:
            return 'unknow';
    }
}

function airSpeedVelocity(bird){
    switch(bird.type){
        case 'EuropeanSwallow':
            return 35;
        case 'AfricanSwallow':
            return 40-2*bird.numberOfCoconuts;
        case 'NorwegianBlueParrot':
            return (bird.isNailed)?0:10+bird.voltage/10;
        default:
            return null;
    }
}
View Code

2,做法

1,如果现有的类不具备多态行为,就用工厂函数创建之,令工厂函数返回恰当的对象实例。

2,在调用方代码中使用工厂函数获得对象实例。

3,将带有条件逻辑的函数移到超类中。

  如果条件逻辑还未提炼至独立的函数,首先对其使用提炼函数。

4,任选一个子类,在其中建立一个函数,使之覆写超类中容纳条件表达式的那个函数。将与该子类相关的条件表达式分支复制到新函数中,并对它进行适当调整。

5,重复上述过程,处理其他条件分支。

6,在超类中保留默认情况的逻辑。或者,如果超类应该是抽象的,就把该函数声明为abstract,或在其中直接抛出异常,表明计算责任都在子类中。

3,实例

对airSpeedVelocity和plumage两个函数使用函数组合成类,这个类会作为基类。

//飞的多快
function speeds(birds) {
    return new Map(birds.map(b => [b.name, airSpeedVelocity(b)]));
}
//羽毛
function plumages(birds) {
    return new Map(birds.map(b => [b.name, plumage(b)]))
}

function plumage(bird) {
    return new Bird(bird).plumage;
}

function airSpeedVeliocity(bird) {
    return new Bird(bird).airSpeedVeliocity;
}

class Bird {
    constructor(birdObject) {
        Object.assign(this, birdObject);
    }

    get plumage() {
        switch (this.type) {
            case 'EuropeanSwallow':
                return "average";
            case 'AfricanSwallow':
                return (this.numberOfCoconuts > 2) ? "tired" : "average";
            case 'NorwegianBlueParrot':
                return (this.voltage > 100) ? 'scorched' : 'beautiful';
            default:
                return 'unknow';
        }
    }

    get airSpeedVelocity() {
        switch (this.type) {
            case 'EuropeanSwallow':
                return 35;
            case 'AfricanSwallow':
                return 40 - 2 * this.numberOfCoconuts;
            case 'NorwegianBlueParrot':
                return (this.isNailed) ? 0 : 10 + this.voltage / 10;
            default:
                return null;
        }
    }
}
View Code

针对每种鸟创建一个子类,用一个工厂函数来实例化合适的子类对象。

class EuropeanSwallow {
}
class AfricanSwallow {
}
class NorwegianBlueParrot {

}

function createBird(bird) {
    switch (bird.type) {
        case 'EuropeanSwallow':
            return new EuropeanSwallow(bird);
        case 'AfricanSwallow':
            return new AfricanSwallow(bird);
        case 'NorwegianBlueParrot':
            return new NorwegianBlueParrot(bird);
        default:
            return new Bird(bird);
    }
}

有了需要的类结构,现在处理两个条件逻辑。

先从plumage函数开始,从switch语句中选一个分支,在适当的子类中覆写这个逻辑。

class EuropeanSwallow {
    get plumage(){
        return "average";
    }
}

超类Bird中EuropeanSwallow逻辑分支改为抛出异常。
get plumage() {
        switch (this.type) {
            case 'EuropeanSwallow':
               throw "oops";
            case 'AfricanSwallow':
                return (this.numberOfCoconuts > 2) ? "tired" : "average";
            case 'NorwegianBlueParrot':
                return (this.voltage > 100) ? 'scorched' : 'beautiful';
            default:
                return 'unknow';
        }
    }

接着处理下一个分支,最后超类Bird从

get plumage() {
        switch (this.type) {
            case 'EuropeanSwallow':
                throw 'oops';
            case 'AfricanSwallow':
                throw 'oops';
            case 'NorwegianBlueParrot':
                throw 'oops';
            default:
                return 'unknow';
        }
    }

变成

get plumage() {
        return 'unknow';
    }

airSpeedVelocity也如法炮制,

然后对顶层的airSpeedVelocity和plumage做了内联处理,

function plumages(birds) {
    return new Map(birds.map(b => [b.name, plumage(b)]))
}

function plumage(bird) {
    return new Bird(bird).plumage;
}

变成

function plumages(birds) {
    return new Map(birds
        .map(b => createBird(b))
        .map(bird => [b.name, bird.airSpeedVelocity))
}

最终完成后的代码如下:

//飞的多快
function speeds(birds) {
    return new Map(birds
        .map(b => createBird(b))
        .map(bird => [b.name, bird.plumage]));
}
//羽毛
function plumages(birds) {
    return new Map(birds
        .map(b => createBird(b))
        .map(bird => [b.name, bird.airSpeedVelocity))
}

function createBird(bird) {
    switch (bird.type) {
        case 'EuropeanSwallow':
            return new EuropeanSwallow(bird);
        case 'AfricanSwallow':
            return new AfricanSwallow(bird);
        case 'NorwegianBlueParrot':
            return new NorwegianBlueParrot(bird);
        default:
            return new Bird(bird);
    }
}
class Bird {
    constructor(birdObject) {
        Object.assign(this, birdObject);
    }

    get plumage() {
        return 'unknow';
    }

    get airSpeedVelocity() {
        return null;
    }
}


class EuropeanSwallow extends Bird {
    get plumage() {
        return "average";
    }
    get airSpeedVelocity() {
        return 35;
    }
}
class AfricanSwallow extends Bird {
    get plumage() {
        return (this.numberOfCoconuts > 2) ? "tired" : "average";
    }
    get airSpeedVelocity() {
        return 40 - 2 * this.numberOfCoconuts;
    }
}
class NorwegianBlueParrot extends Bird {
    get plumage() {
        return (this.voltage > 100) ? 'scorched' : 'beautiful';
    }
    get airSpeedVelocity() {
        return (this.isNailed) ? 0 : 10 + this.voltage / 10;
    }
}
View Code

二、多态处理变体逻辑

 

2021-03-11

如果觉得本文对您有帮助~可以支付宝(左)或微信支持一下:


看到小伙伴打赏时给我写一些鼓励的话,真的非常感动,谢谢你们。


我开了个微信公众号(第三个二维码)用来分享自己的职场英语相关学习经验,感兴趣可以关注,我会不断更新~


微信打赏微信公众号

posted @ 2021-07-08 08:39  starof  阅读(115)  评论(0编辑  收藏  举报