优美代码-正交

Jan 25, 2019 00:00 · 1306 words · 3 minute read 程序设计

在Andrew Hunt 与 David Thomas 的经典著作 <<程序员修炼之道>>中提到一种叫正交的概念, 这是从几何学中借来的术语(如两条直线相交成直角果, 它们就是正交的, 比如图中的坐标轴, 用向量术语说, 这两条直线互不依赖.) 对应于计算机技术就是指某种不相依赖性或是解耦性.

正交性系统的好处非常明显:

  1. 改动得以局部化: 我们可以设计\编写简单的组件, 对其进行单元测试, 然后把它们忘掉—当我们增加新的代码时, 无效不断改动已有的代码.
  2. 增加组件的可复用性: 如果组件具有明确而具体的,良好定义的责任, 就可以用其最初的实现者未曾想象过的方式, 把它们与新组件组合在一起

得益于上面提到的好处, 正交系统可以很大幅度降低系统开发的风险.

一个例子: 公司有一款用于提供内部资源下载软件, 资源服务由公司架设:

function download(resource_info) {
    // 使用内部协议与地址下载资源
}

没过多久, 开发组觉得自己架构的资源服务器无论带宽还是稳定性不如某第三方提供的服务, 但使用第三方服务需要付费, 为了高系统可用性并节约成本, 在软件中增加第三方下载方式:

function downloadFromInner(resource_info) {
    // 使用内部协议与地址下载资源
}

function downloadFromThirdpart(resource_info) {
    // 使用第三方协议与地址下载资源
}

后来发现还有另外一家公司提供的第三方服务也很好, 并考虑以后的可扩展新, 重构了这部分代码

class ResourceDownloader {
    constructor(resource_info) {
        this.resource_info = resource_info;
    }

    _downloadFromInner() { .... }

    _downloadFromThirdpartA() { .... }

    _downloadFromThirdpartB() { .... }

    download(from) {
        switch(from) {
            case 'INNER':
                this._downloadFromInner();
                break;
            case 'THIRDPART_A':
                this._downloadFromThirdpartA();
                break;
            case 'THIRDPART_B':
                this._downloadFromThirdpartB();
                break;
            ....
            default:
                throw Error('未知下载方式');
        }
    }
}

const resourceDownloader = new ResourceDonwloader(xxx);
const from = choiceResourceServer();
resourceDownloader.download(from);

之后过了很长一段时间, 公司要为这款软件添加新的功能, 其中也需要与”ResourceDownloader”类似的功能, 我们很容易想到复用ResourceDownloader这部分代码, 但需要对其进行改动, 这样就很可能影响到之前使用它的地方, 并且如果以后还有类似的功能,是不是每次都要这样修改? ResourceDownloader 以后会越来越庞大负责变得不好维护.

策略模式(Strategy Pattern): 定义一系列的算法, 将每一个算法封装起来, 并让它们可以相互替换. 策略模式让算法独立于使用它的客户而变化, 也成为政策模式(Policy)

使用策略模式重构后:

class Resource {
    setDownloader(downloader) {
        this.downloader = downloader;
    }

    download() {
        this.downloader.download(this);
    }
}

class Downloader {
    download(resource) { throw Error('请在继承类中实现 download() 方法.'); }
}

class InnerDownloader extends Downloader {
    download(resource) { ... }
}

class ThirdPartADownloader extends Downloader {
    download(resource) { ... }
}

class ThirdPartBDownloader extends Downloader {
    download(resource) { ... }
}

....

const resource = new Resource(...);
const downloader = new InnerDownloader(); // ThirdPartADownloader or ThirdPartBDownloader ......
resource.setDownloader(downloader);
resource.download();

这样以后如果有新的下载方式加入, 只要添加新的新的下载类, 无需更改其它下载方法, 代码更具正交新

练习:

1.下面两个Split类(Java), 实现把输入行拆分为字段, 哪一个更符合正交设计?

class SplitA {
    public SplitA(InputStreamReader rdr) { ... }
    public void readNextLine() throws IOExceptio { ... }
    public int numFields() { ... }
    public String getField(int fieldNo) { ... }
}

class SplitB {
    public SplitB(String line) { ... }
    public int numFields() { ... }
    public String getField(int fieldNo) { ... }
}

解答: SplitB 更符合正交设计. 它专注于自己的任务, 拆分输入行, 同时忽略像输入源来自何处这样的细节, 这不仅使代码更容易开发, 也使得代码更为灵活, SplitB 可以拆分的行可以来自文件, 由其它程序生成…

2.过程语言(比如C)与对象技术,哪一个更能产生正交系统?

解答: 第一直观反映是, 对象技术可以将一些密切相关的东西封装在一起减少耦合性, 但实际上对象技术的很多特性如果使用不当更容易产生非正交系统, 比如类的继承,异常…