Effective Java 条款 14:改变表示形式必须同时改变 API。

3
这段内容摘自 Joshua Bloch 的《Effective Java》—— 条款14(或第三版中的条款16):在公共类中,使用访问器方法而不是公共字段 //像这样的退化类不应该是公共的!
class Point {
  public double x;
  public double y;
}

由于这些类的数据字段被直接访问,所以它们不提供封装的好处(第13条)。引用块中写道:“您无法更改表示方式而不更改API”。作者在同一项目中多次使用此语句,请解释其含义。术语导出API或API应按照作者在书中建议的方式进行解释。导出的API包括可以在定义API的包之外访问的API元素。

你发布的类不是公共的,它是包私有的。总的来说,你的问题表达得不太清楚。 - XtremeBaumer
@Xtreme 嗯,楼主只引用了书中那一部分的一半 :) - OneCricketeer
6个回答

4

API由所有公共方法和成员组成。

数据成员是表示(或实现)的一部分。

因此,如果您将数据成员设置为公共的,更改表示也会更改API。

例如,如果您使用公共getter而不是公共数据成员,则可以在不更改API的情况下更改表示。

例如,这里有两个具有相同API的不同表示:

class Point {
    private double x;
    private double y;

    public double getX () {return this.x;}
    public double getY () {return this.y;}
}

class Point {
    private InnerPoint p;

    public double getX () {return p.x;}
    public double getY () {return p.y;}
}

这仅因为数据成员是私有的(因此不属于API的一部分)才可能实现。

4
好的,这很简单,假设有一个Point类。
class Point {
  public double x;
  public double y;
}

现在,x和y本质上是二维平面上点的表示。如果有人决定使用它,例如,它将如下所示:

Point p;
p.x = 5;
p.y = 10;

由于x和y被声明为公共变量,因此任何人都可以访问它们。如果有一天您决定将点的表示方法从2D平面上的坐标(x,y)改为半径和角度,例如:

class Point {
  public double r;
  public double angle;
}

以上代码也将需要进行更改,这意味着使用您的Point对象的客户端将不得不重新编译他们的代码。因此,通过将x和y声明为公共变量,您将限制自己,因为这些声明现在是Point API的一部分。

然而,更好的方法是封装点的内部表示为私有点状态,并仅公开所需的功能:

class Point {
  private double x;
  private double y;

  // Computes norm2 of the point
  public double norm2() {
    return Math.sqrt(x*x + y*y);
  }
}

现在,使用您的Point类的人将只能在这种情况下使用公共API中的norm方法,类似于以下方式:

Point p;
// Init coordinates
double norm = p.norm();

最后,如果您想更改内部表示方式,与先前的示例不同,客户端代码不会受到影响。

已点赞。但是这个类是一个包私有的类。如果我分发API,它将无法被所有人访问。请建议。 - Farhan stands with Palestine
1
即使类是通道范围的,其内部表示也不会泄漏到外部,但这仍然会在同一包中强制执行代码的紧密耦合,使得重用和重构变得困难。因此,无论类是公共的还是包范围的,我上面的建议仍然保持完整。 - Artem Barger

1
你不能重命名字段或将它们更改为 private 以提供封装。
使用此 API 的代码是使用 point.x 而不是 point.getX() 编写的。

0
你不能在不改变 API 的情况下改变表示方式。
这句话的意思是,一旦共享了某个类的属性定义,如果你想要改变它们,就不可避免地需要修改现有的 API 定义。举个例子:
class Point {
  public double x;
  public double y;

  // accessors
  double getX() { return this.x; }
  double getY() { return this.y; }
}

class Graph {

   void draw(Point point) {
       double x = point.x; // what if you decide this should be 0 if y is not 0?
}

相反,如果你在那里使用了getX() getter,你可以将该逻辑放在Point类中。

double getX() {
    if(p == 0) return 0;
    return this.x
}

在图的实现中不做任何更改。


0

这意味着当您拥有公共字段时,您的字段(您的表示)本身就是API,当您更改字段时,API也会改变。

如果您将私有字段设置为有公共getter,则可以自由地使用您拥有的私有字段,并且您的API(公共方法)可以保持不变。


0
作者的意思是,如果你想改变数据的存储方式或者数据的表示方式(比如使用不同的容器),就必须同时改变API。例如,如果你决定使用一个点类来代替double来表示x和y,那么你就需要改变所有使用该类的消费者(从而改变该类的API)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接