1. 数据抽象
下面代码1和代码2都表示笛卡尔平面上的一个点。
代码1:
public class Point {
public double x;
public double y;
}
代码2:
public interface Point {
double getX();
double getY();
void setCartesian(double x, double y);
double getR();
double getTheta();
void setPolar(double r, double theta);
}
代码1暴露了实现,而代码2隐藏了实现,在代码2中你不知道实现是在矩形坐标系中还是极坐标系中。
类并不简单的通过get/set方法将其变量推向外面,而是暴露抽象接口,以便用户无需了解数据的实现就能操作数据本体。
下面的代码3暴露具体变量给外界,而代码4采用百分比抽象。
代码3:
public interface Vehicle {
double getFuelTankCapacityInGallons();
double getGallonsOfGasoline();
}
代码4:
public interface Vehicle {
double getPercentFuelRemaining();
}
代码4比代码3好,我们不愿意暴露数据细节,更愿意以抽象的形态表述数据。要以最好的方式呈现某个对象包含的数据,需要做严肃的思考。傻乐着乱加get/set方法,是最坏的选择。
2. 数据和对象的反对称性
数据结构暴露其数据,没有提供有意义的函数;对象则是吧数据隐藏于抽象之后,暴露操作数据的函数。
代码5是面向数据编程,所有的行为都在Geometry类中。
代码5:
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();
}
}
面向对象程序员可能会对上面代码嗤之以鼻,说这是过程是代码 — 他们大概是对的,但是这种嘲笑并不完全正确。如果想给Geometry类添加一个计算周长的方法,形状类不会受影响。但是如果想要添加一个新形状,就得修改Geometry中的所有方法来处理它。
下面是面向对象的方案。area()方法是多态的,不需要有Geometry类。所以添加一个新的形状,现有类一个不会受影响;但添加新的函数时,所有形状都得做修改。
public interface Shape {
double area();
}
public class Square implements Shape {
private Point topLeft;
private double side;
@Override
public double area() {
return side * side;
}
}
public class Rectangle implements Shape {
private Point topLeft;
private double height;
private double width;
@Override
public double area() {
return width * height;
}
}
public class Circle implements Shape {
private Point center;
private double radius;
public final double PI = 3.1415926;
@Override
public double area() {
return PI * radius * radius;
}
}
再次对比上面的两种实现:他们是截然对立的。
过程式代码便于在不改动机油数据结构的前提下添加新函数。面向对象代码便于在不改动既有函数的前提下添加新类。所以,对面向对象较难的事,对于过程式代码却较容易,反之亦然。
在任何一个复杂系统中,都会有需要添加新数据类型而不是新函数的时候,这时面向对象比较合适。当需要新函数而不是新数据类型的时候,过程式代码和数据结构更合适。老练的程序员知道,一切都是对象只是一个传说。