对象和数据结构在使用场景上有不同的侧重点,要做到代码整洁,需要明确区分两者,并灵活运用
6.1 数据抽象
- 我们习惯性熟知的数据封装方式:变量都是私有,通过 Getter / Setter 对变量进行取值赋值
- 但其实这么做之后,变量依旧是暴露的,隐藏实现并非只是在变量之前放上一个函数层这么简单
- 隐藏的关键点是 抽象 ,类并不是简单的通过 Getter / Setter 将变量推向外层
- 而是曝露抽象接口,以方便用户无需了解数据的实现就能操作数据本体
6.2 数据、对象的反对称性
- 对象把数据隐藏在抽象之后,曝露操作数据的函数
- 数据结构曝露自己的数据,没有提供有意义的函数
- 在如今面向对象盛行的时期,其实我们在决定具体时候什么方式实现代码时,并不是只有单一的选择
- 有时候根据场景需要,使用数据结构做一些过程式的操作也是可行的
6.2.1 过程式代码
- 就是使用数据结构的代码,可以再不改动既有数据结构的前提下添加新函数
- 难以添加新数据结构,因为每次添加都需要所有所有调用的函数结构
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.1415926;
public double area(Object shape) throws NoSuchShapeException {
if (shape instanceOf Square) {
Square square = (Square) shape;
return square.side * square.side;
} else if (shape instanceof Rectangle) {
Rectangle rectangle = (Rectangle) shape;
return rectangle.height * rectangle.width;
} else if (shape instanceof Circle) {
Circle circle = (Circle) shape;
return PI * cicle.radius * circle.radius;
}
throw new NoSuchShapeException();
}
}
6.2.2 面向对象代码
- 可以在不改动既有函数的前提下添加新类
- 难以添加新函数,因为每次添加都需要修改所有实现的类
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;
private final double PI = 3.1415926;
public double area() {
return PI * raduis * raduis;
}
}
6.3 得墨忒耳率( The Law of Demeter )
- 核心观念:模块不应该了解它所操作对象的内部情形
6.3.1 火车失事
ctxt.getOptions().getScratchDir().getAbsolutePath()
这种链式语句就像是一列火车- 这句代码显然是违反了得墨忒耳率,但事实告诉我们,由于使用场景的限制,没有绝对的规则
- 例如这串代码优化成以下格式,依旧违反了得墨忒耳率,但在语义理解上要直观的多
Options options = ctxt.getOptions();
File scratchDir = options.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();
6.3.2 混杂
- 在一个类中,一半是对象,一半是数据结构,导致这个类即拥有 执行操作的函数 ,也拥有 公共变量或 Getter / Setter 函数
6.3.3 隐藏结构
- 对上述那串代码追根溯源找到最终使用的位置,并将整个逻辑进行封装,直接返回最后结果
- 例如
BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName)
,就比较好的隐藏了内部实现
6.4 数据传送对象
- 最为精炼的数据结构,是一个只有公共变量、没有函数的类
- 这种类通常被称为 数据传送对象 DTO( Data Transfer Objects )
- 但现在 DTO 的使用场景更像是一个 Bean ,拥有私有变量的同时,也拥有共有的 Getter / Setter 函数
6.5 小结
- 对象曝露行为,隐藏数据,便于添加新对象类型而不需要修改既有行为
- 但对象难以在既有对象中添加新行为
- 数据结构曝露数据,没有明显的行为,便于向既有数据结构添加新行为
- 但数据结构难以向既有函数添加新数据结构