对象和数据结构在使用场景上有不同的侧重点,要做到代码整洁,需要明确区分两者,并灵活运用
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 小结
对象曝露行为,隐藏数据,便于添加新对象类型而不需要修改既有行为但对象难以在既有对象中添加新行为数据结构曝露数据,没有明显的行为,便于向既有数据结构添加新行为但数据结构难以向既有函数添加新数据结构