5

java中以下2个类的正确封装是什么?我在许多代码中都看到了这两种方法(主要是第一种方法)。但似乎第二种方法是正确的。

import java.util.Date;

public class SomeClass
{
    private Date date;

    public Date getDate()
    {
        return date;
    }

    public void setDate(Date date)
    {
        this.date = date;
    }   
}

或者

import java.util.Date;

public class SomeClass
{
    private Date date;

    public Date getDate()
    {
        return (Date) date.clone();
    }

    public void setDate(Date date)
    {
        this.date = (Date) date.clone();
    }

}
4

8 回答 8

6

这取决于您获取/设置的字段类型是否不可变,即它们是否可以在构造后进行修改。

整个 Getter/Setter 范式背后的要点是,实例的私有/受保护字段不能被任何外部类任意修改(或访问)。

因此,在您的第一个示例中,一个类可以获取对私有日期字段的引用,然后(因为Date它不是不可变的)使用日期的setTime(long)方法来修改日期,有效地绕过了 Setter 方法SomeClass(以及它可能具有的任何副作用,例如执行验证、更新 GUI 元素等)。
在您的第二个示例中,这是无法做到的,因为外部类只会获取实际日期的克隆SomeClass,因此之后所做的任何修改都不会影响.

底线
这完全取决于您的私有/受保护字段的类型以及您试图实现/防止的内容。


要记住的几点:

  • clone()不会总是返回对象的深度克隆(特别是对于复杂对象,其字段引用其他可变对象等)。因此,必须小心使用它(并了解其内部工作原理)。

  • clone()原始数据类型和字符串是不可变的,因此在获取/设置这些类型的字段时不需要ing。

于 2013-06-20T06:54:01.807 回答
4

不是一个好主意clone-

  1. 对象的一般clone方法,创建同一类的新实例并将所有字段复制到新实例并返回它 = 浅拷贝。Object类提供了一个克隆方法并为shallow copy. 它返回“对象”作为类型,您需要显式地cast back返回原始对象。

  2. 当你实现深拷贝时要小心,因为你可能会爱上cyclic dependencies.

  3. 克隆不适用于instantiationand initialization。不应将其视为创建新对象。因为克隆对象的构造函数可能永远不会在该过程中被调用。

4.还有一个缺点(还有许多其他...... :)),克隆阻止使用final 字段。

5. 在单例模式中,如果 superclass实现了一个public clone()方法,为了防止你的子类使用这个类的clone()方法来获取一个副本,覆盖它并抛出一个CloneNotSupportedException

因此,方法1优于方法2

于 2013-06-20T06:51:25.683 回答
4

封装的两个基本特征是:

  • 保护实例变量(使用访问修饰符,通常是私有的)。
  • 创建公共访问器方法,并强制调用代码使用这些方法,而不是直接访问实例变量

每当将可变对象传入类中的方法或传出方法时,创建可变对象的防御性副本始终是一个好习惯。如果不这样做,那么调用者很容易通过更改对类及其调用者同时可见的对象的状态来打破封装。 此外,不要使用 clone 方法制作其类型可被不受信任方子类化的参数(可变对象)的防御性副本,因为这可能会导致实例变量的内部状态有意或无意地发生变化。 按照所有这些规则,您的方法都不正确。Constructorssetget

因此,在代码中遵循封装的正确方法是:

import java.util.Date;

public class SomeClass
{
    private Date date;

    public Date getDate()
    {
        return new Date(date.getTime());
    }

    public void setDate(Date date)
    {
        this.date = new Date(date.getTime());
    }

}
于 2013-06-20T06:57:14.563 回答
3

这是安全/封装偏好的问题,最基本的封装是您发布的第一种方法,但是第二种是更高级的封装方法这也保护了通过克隆传递给类的对象。

考虑以下:

public class SomeData {
 private final Point value;
  public SomeData (final Point value) {
    this.value = value;
  }
  public Point getValue( ) {
    return value;
  }
}

现在上面的代码片段看起来是不可变的(类似于你的例子)但是这里面有一个大洞。看看下面的片段。

  public final static void main(final String[] args) {
    Point position = new Point(25, 3);
    SomeData data = new SomeData(position);
    position.x = 22;
    System.out.println(data.getValue( ));
  }

因为我们只传递位置变量的引用,我们仍然可以改变它的状态。克隆它有助于保护变量位置:

如果我们从这里更改声明:

public SomeData (final Point value) {
        this.value = value;
      }

(类似于你的克隆)

 public SomeBetterData(final Point value) {
    this.value = new Point(value);
  }

当我们再次调用 main 方法时:

Point position = new Point(25, 3);
        SomeData data = new SomeData(position);
        position.x = 22;

data无论我们做什么,对象都将保持不变position。我希望你明白为什么那里有克隆。

于 2013-06-20T06:48:15.957 回答
2

第一个 !第二个将为clone()每次getDate()调用创建一个新对象,这可能会令人尴尬,具体取决于您的应用程序。(即,如果你想用 调用Date方法aSomeDate.getDate().aMethod()

一个小样本来理解我糟糕的英语;)

public class Main {
  public static void main(String[] args) {
    SomeDate sd = new SomeDate(new Date(1991, 3, 3));
    System.out.println(sd);
    sd.getDate().setYear(2012);
    System.out.println(sd);
  }
}
于 2013-06-20T06:44:27.450 回答
2

第二个尝试强制执行防御性复制。考虑一个类Period,它存储两个日期,第一个必须在第二个之前:

public class Period {
    private Date first, second;

    public Period(Date first, Date second) {
        if(first.compareTo(second) > 0) 
            throw new IllegalArgumentException("first > second");

        this.first = first;
        this.second = second;
    }

    public Date getFirst() {
        return first;
    }

    public Date getSecond() {
        return second;
    }
}

乍一看,这听起来难以破解,但请看一下:

Date d1 = new Date(), d2 = new Date();
Period p = new Period(d1, d2) // no exception

d1.setYear(98); // broken period precondition

为了解决这个问题 - 防御性复制进来了,即内部实例不能被原始参数改变。尽管您的第二种方法可行,但它仍然是可破解的,因为子类可以覆盖clone()并保存所有创建的新实例......

正确的方法是:

this.first = new Date(first.getTime());

和返回语句:

return new Date(first.getTime()); // here you may use clone....

这样,子类就无法掌握内部结构。

于 2013-06-20T06:57:57.757 回答
1

这取决于使用SomeClass. 如果您打算将此类包含在第三方使用的库中,或者您的代码交互/运行您无法控制的代码,则绝对需要封装后一种形式。

即使您没有处于如此困难的位置,也值得屏蔽可变Date类的设计错误并返回一个新Date实例。这就是我所知道的所有 IDE 都将其标记为警告的原因。

虽然这通常会导致我这样的事情:

public class MyClass {
  private Date myDate;

  public Date getDate() { 
    return myDate != null ? new Date(myDate.getTime()) : null; 
  }
  public void setDate(Date date) { 
    myDate = (date != null ? new Date(date.getTime()) : null); 
  }
}

所以恕我直言,我会使用第二个变体或切换到 Jodas DateTime

于 2013-06-20T06:44:20.293 回答
0

不是针对此特定示例,而是针对一般方法:

为用户提供一种将返回的对象转换为他想要的任何其他兼容类的方法总是好的。

所以第一种方法看起来不错,因为如果您返回的对象已被其他类扩展,那么将其转换为该类型将很容易,而不是提供固定类型的对象。

所以这种方式你提供了一个更普遍的对象或者我可以说abstraction和鼓励polymorphism

于 2013-06-20T06:45:06.903 回答