代码的艺术架构设计与重构互联网科技

并不是一切皆对象(clean code阅读笔记之五)

2016-05-15  本文已影响496人  TheAlchemist
Star Trek中的机器人Data

注:正文中的引用是直接引用作者Bob大叔的话,两条横线中间的段落的是我自己的观点,其他大约都可以算是笔记了。

本文中的函数方法是一个概念

本文读起来可能比较晦涩,其实通篇只是讲了一件事情:在面向对象的环境里有两种方法去定义一个类,面向对象(本文中一直谈到的对象)和面向过程(本文中谈到的数据结构),它们各有优劣,在开发的时候要合适地做出选择。

由于「Clean Code」整本书都有很浓厚的Java的色彩,所以大部分代码和概念都是Java中比较常见的,不过在面向对象的语言中大致都能找到相应的东西


数据抽象

我们在设计对象的结构时,应该尽可能地使用数据抽象。如代码5-1中所示的两种对于笛卡尔平面中的一个点的数据结构定义,从这里可以看到,使用抽象的类定义,这个类就不仅仅是一个数据结构了。通过暴露出来的方法强制了对于数据的设置必须x轴和y轴同时设置,它可以代表一个平面坐标系中的一个点,也可以代表极坐标系中的一个点。

//代码5-1
//具象类定义
public class Point { 
    public double x; 
    public double y;
}

//抽象类定义
public interface Point {
    double getX();
    double getY();
    void setCartesian(double x, double y); 
    double getR();
    double getTheta();
    void setPolar(double r, double theta); 
}

面向对象概念中的「隐藏实现」的真实意义不应该只是在变量中增加了一层函数,而是「数据抽象」。数据抽象也不仅仅是使用一些interfacegettersetter就可以的,它需要你认真仔细去思考「如何才能更好地表示一个对象所包含的数据」。

「数据结构」和「对象」的反对称性


Bob大叔在这一小节讲得很玄乎,阐述了一个道理:过程式编程和面向对象编程是互补的两个概念。「一切皆对象」只是一个神话,我们有时候不可避免的要用到过程式的代码(也就是本文一直提到的数据结构)来补充。


数据结构对象是两个相反的概念,对象隐藏了数据并暴露了对数据操作的函数,而数据结构暴露了它的数据但并没有有意义的函数。这两个概念互为相反,但又相辅相成的。

过程式代码(使用数据结构的代码)的优势在于「当你想要添加新的函数时,不需要修改已存在的数据结构」,而面向对象的代码的优势在于「当你添加新的类时,不需要修改已存在的函数」。

下面通过一个例子解释它们的反对称性。

//代码5-2
//过程式的Shape类
public class Square { 
    public Point topLeft; 
    public double side;
}
public class Rectangle { 
    public Point topLeft; 
    public double height; 
    public double width;
}
public class Circle { 
    public Point center; 
    public double radius;
}
public class Geometry {
    public final double PI = 3.141592653589793;
    
    public double area(Object shape) throws NoSuchShapeException {
        if (shape instanceof Square) { 
            Square s = (Square)shape; 
            return s.side * s.side;
        }
        else if (shape instanceof Rectangle) { 
            Rectangle r = (Rectangle)shape; 
            return r.height * r.width;
        }
        else if (shape instanceof Circle) {
            Circle c = (Circle)shape;
            return PI * c.radius * c.radius; 
        }
        throw new NoSuchShapeException(); 

    }
}

//多态版本的Shape类
public class Square implements Shape { 
    private Point topLeft;
    private double side;
    public double area() { 
        return side*side;
    } 
}

public class Rectangle implements Shape { 
    private Point topLeft;
    private double height;
    private double width;
    public double area() { 
        return height * width;
    } 
}

public class Circle implements Shape { 
    private Point center;
    private double radius;
    public final double PI = 3.141592653589793;
    
    public double area() {
        return PI * radius * radius;
    } 
}

代码5-2中展示了两种Shape类的实现方法:

迪米特法则


迪米特法则可以概括为「不要和陌生人说话」。


精确的讲,迪米特法则规定一个类C中的方法f应该只调用以下几种方法:

1. 火车事故

如代码5-3中代码段1这样的链式的耦合性极强的调用方法,我们通常称之为火车事故「train wrecks」,这类链式的调用应该禁止,应该将其重构为代码2这种形式。


Build模式和JQuery中的链式调用和并不适用这条规则,因为他们从头到尾所有方法的调用者都是同一个对象。


代码5-3中ctxt这个对象操作了多个层级的函数,违反了迪米特原则。但是,如果ctxtOptionsScratchDir这三个类都是简单的数据结构(如代码5-2中过程式的类定义),并不包含任何的行为的话,这样的调用并不违反迪米特法则。

我们平常使用的很多编程框架中要求所有的「实体类」都要添加getter和setter,这使得判断一个调用是否违反迪米特法则变得很困惑。作者认为作为简单的数据结构,比如实体类Pojo,就应该只包含public属性。

//代码5-3
//代码段1   这种强耦合的链式调用应该被禁止使用
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

//代码段2
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();

2. 数据结构和对象的杂交

有时候我们会创建这样的杂交类,其中有包含复杂逻辑的方法,同时又包含public属性或者public的getter和setter,应该避免创建这样的类。

3. 隐藏结构

对象应该隐藏自己的内部结构。

具体到代码5-3中所说的例子,作者认为从ctxt这个对象获取一个文件的绝对路径的本身就是不对的,绝对路径可能是ctxt的内部结构,我们可能是想使用这个绝对路径来构造一个对象,那么如果代码是这样子的:

String outFile = outputDir + "/" + className.replace('.', '/') + ".class";
FileOutputStream fout = new FileOutputStream(outFile);
BufferedOutputStream bos = new BufferedOutputStream(fout);

我们可以把它封装成函数,这样来调用:

BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);

传输数据的对象(Data Transfer Objects)

DTO是指那些只包含公共变量且没有函数的类,这是一类很有用的数据结构。但是现在更广为使用的则是Bean(类的变量为私有,但是含有公共的getter和setter),它起到的作用其实和DTO相同。

Active Record

这是DTO的一种特殊形式,在上边DTO讨论的基础之上,还包括一些save或find这样一些浏览方法。
它和DTO一样都属于数据结构而非对象,所以不要在其中添加复杂的逻辑。

结论

如果未来很有可能需要不断地往一个类中添加新的对象,那么就选择对象结构;如果未来可能需要不断的改变类的行为,就选择数据结构

上一篇下一篇

猜你喜欢

热点阅读