【问题标题】:In java, what does exposing the rep mean?在java中,暴露代表是什么意思?
【发布时间】:2014-03-19 21:56:55
【问题描述】:

一个对象的“暴露代表”可能是由以下哪一项引起的:

  1. 未将其类中的变量声明为私有。
  2. 允许对象中的变量引用传递给的对象 它作为其构造函数的参数。
  3. 允许存储在对象变量中的引用 作为对该对象调用的方法的返回值传递。
  4. 以上所有。

我知道答案是 4,但我不知道为什么,我对暴露代表的理解并不完全清楚。

编辑:以获得明确的答案

【问题讨论】:

  • 表示可能?
  • 如果你知道答案是 4,你的问题到底是什么?
  • 他的问题是“暴露代表 [意味着]”。答案是“暴露内部类的表示”。
  • 谢谢,我的意思是代表,但想留下问题的确切表述方式。我将继续我的研究,感谢 maksimov 提供的链接和答案。

标签: java oop encapsulation


【解决方案1】:

我从来没有听说过这个词,所以我用谷歌搜索了它。这篇文章解释得很清楚:

http://www.cs.newpaltz.edu/~pletcha/oop_chap5_1.html

暴露代表意味着违反对象的方法控制其状态的规则。例如,如果一个对象有实例变量和修改器来改变它们的值,那么这个对象就有一种控制它的状态的方法。如果我调用foo.setBar(5),那么如果文档说getBar 返回由setBar 设置的值,则foo.getBar() 最好返回5。

我将解释为什么你给出的三个描述中的每一个都暴露了一个对象的表示(或者更一般地说,打破了封装):

不将其类中的变量声明为私有。

这个是最简单的。如果实例变量是公共的,则 JVM 中的任何内容都可以从同一对象/类中的代码外部更改它们的值。如果我们调用foo.setBar(5),然后调用foo.getBar(),我们可能会得到5 以外的值,因为bar 是公共范围,因此其他代码区域可能已经对其进行了变异。

允许对象中的变量引用作为构造函数参数传递给它的对象。

我花了一分钟才理解这一点,但如果您将一个对象及其依赖项视为一个单一的单元,这将是有意义的。

如果Foo 有一个Bar 并且Bar 有一个称为xint,那么Foo 可以看到并控制bar 上的x 属性,因为它有一个引用。如果我创建Foo 的实例并在Foo 的构造函数中传递对Bar 实例的引用,则看起来Foo 具有完全控制权。但事实并非如此。示例:

public class Foo {
    private Bar bar;

    public Foo(Bar bar) {
        this.bar = bar;
    }

    // immutable property - can only be read once this object is instantiated
    public Bar getBar() {
        return this.bar;
    }
}

public class Bar {
    private int x;

    public Bar(int x) {
        this.x = x;
    }

    public int getX() {
        return this.x;
    }

    public void setX(int x) {
        this.x = x;
    }
}

// some other java class
Bar bar = new Bar(10);
Foo foo = new Foo(bar);
bar.setX(5);

此代码公开了代表,因为foo 做出了一个关键假设,即它控制bar。注意它对bar 的引用是不可变的。但它并不是一成不变的。只有引用本身是不可变的。创建foo 的代码仍然有对bar 的引用,并且可以在foo 不知道的情况下对其进行变异。

更简单地说,foo 依赖于 bar,并将其视为自身的一部分。但是bar 实际上可以独立改变,因此foo 的状态会在它不知情的情况下间接改变。

允许将存储在对象变量中的引用作为对该对象调用的方法的返回值传递。

这是最容易通过集合来解释的。

public class Foo {
    private Collection<Bar> bars = new ArrayList<Bar>();

    // immutable property - can only be read once this object is instantiated
    public Collection<Bar> getBars() {
        return this.bars;
    }

    public void addBar(Bar bar) {
        this.bars.add(bar);
    }

    public int getBarCount() {
        return this.bars.size();
    }
}

Foo foo = new Foo();
foo.getBars().add(new Bar(someUnexpectedBar));
System.out.println(foo.getBarCount());  // -> 1

这违反了合同。要添加条形图,您应该致电addBar。这就是暴露该方法的原因。通过返回对getBars 中集合的引用,可以操作底层集合。

起初这似乎微不足道。但是如果Foo 制定了这条规则并且上述用法违反了它,那么如果我想将Foo 重构为此会发生什么(假设是为了性能):

public class Foo {
    private Collection<Bar> bars = new LinkedList<Bar>();
    private int barCount;  // for faster inserts, use a linked list and to maintain fast counts, track the count ourselves by tracking the adds.

    // immutable property - can only be read once this object is instantiated
    public Collection<Bar> getBars() {
        return this.bars;
    }

    public void addBar(Bar bar) {
        this.bars.add(bar);
        this.barCount++;
    }

    public int getBarCount() {
        return this.barCount;
    }
}

Foo foo = new Foo();
foo.getBars().add(new Bar(someUnexpectedBar));
System.out.println(foo.getBarCount());  // -> 0

从功能上讲,它是相同的。如果您适当地使用其中的方法,则不会有任何区别。但是我们作弊了。我们抓取了底层集合并对其进行了变异。现在getBarCount() 方法返回错误答案 (0)。

解决这个问题的方法是返回一个带有原始副本的新集合。

    public Collection<Bar> getBars() {
        return new ArrayList<Bar>(this.bars);
    }

甚至

    public Collection<Bar> getBars() {
        return Collections.unmodifiableCollection(this.bars);
    }

【讨论】:

  • 所以如果我理解的话,#2 是个问题,因为Foo 的代码,在这种情况下,假设它控制bar--但这并不取决于@987654363 如何@是写的吗?如果Foo 是在理解bar 可以在它下面更改的情况下编写的,那么我认为这不是问题。这是我能理解它的唯一方法。如果做得好,这个成语(设置一个引用另一个对象的对象)似乎是不可或缺的。我已经在 J​​RE 的一个地方找到了它,而且我确信我可以在“设计模式”中找到许多其他的以及示例。
  • 没错。这取决于Foo 的设计/意图/合同是什么。我提供的示例代码有助于说明控制实例的意图。一个班级完全有可能根本不关心这个。
  • 我刚刚想到的一个更好的例子:HashMap。当您调用HashMap.put 时,会对密钥进行哈希处理以找到正确的存储桶。但是密钥可以在之后以改变其哈希码的方式进行变异。我不能代表HashMap 的设计意图,但通常假设,至少我通常假设,当向 HashMap 添加对象时,其哈希码永远不会改变。这将是最糟糕的错误之一,因为找出它将是一场噩梦。
猜你喜欢
  • 2023-03-26
  • 2012-08-13
  • 1970-01-01
  • 1970-01-01
  • 2011-01-09
  • 1970-01-01
  • 2022-08-18
  • 1970-01-01
  • 2012-12-11
相关资源
最近更新 更多