java中2个以下类的正确封装是什么?。我在许多代码中看到了这两个approches(大多数是第一个approche)。但似乎第二种方法是正确的。
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();
}
}
答案 0 :(得分:5)
取决于您获取/设置的字段类型是否为 immutable ,即它们是否可以在构建后进行修改。
整个Getter / Setter范例背后的一点是,实例的私有/受保护字段不能被任何外部类任意修改(或获得访问权限)。
因此,在您的第一个示例中,Class可以获取对私有Date字段的引用,然后(因为Date
不是不可变的)使用Date的setTime(long)
方法来修改Date,有效地绕过SomeClass
的Setter方法(以及它可能具有的任何副作用,例如执行验证,更新GUI元素等)。
在你的第二个例子中,这是无法完成的,因为外部类只会获得实际Date的 clone ,因此在此之后所做的任何修改都不会影响原始的私有Date-field。 SomeClass
。
底线:
这完全取决于您的私有/受保护字段的类型以及您要实现/阻止的内容。
要记住的要点:
clone()
并不总是返回Object的深层克隆(特别是对于复杂的Object,其字段引用其他可变对象等)。因此必须谨慎使用(并了解其内部工作原理)。
原始数据类型和字符串是不可变的,因此在获取/设置这些类型的字段时不需要clone()
。
答案 1 :(得分:4)
clone
不是一个好主意 -
对象的一般clone
方法,创建同一个类的新实例,并将所有字段复制到新实例并返回它=浅拷贝。 Object
类提供克隆方法并为shallow copy
提供支持。它返回'Object'作为类型,您需要明确cast back
到原始对象。
当您实施深层复制时,请注意cyclic dependencies
。
克隆不适用于instantiation
和initialization
。不应将其视为创建新对象。因为克隆对象的构造函数可能永远不会在进程中被调用。
4.另一个缺点(以及许多其他.. :)),克隆阻止使用final
字段。
5。在Singleton模式中,如果superclass
实现了public clone()
方法,则为了防止您的子类使用此类的clone()
方法获取副本覆盖它并抛出CloneNotSupportedException
< / p>
因此,方法1 更好而不是方法2
答案 2 :(得分:4)
封装的两个基本特征是:
- 保持实例变量受保护(使用访问修饰符,通常是私有的)。
- 制作公共访问器方法,并强制调用代码使用这些方法而不是直接访问实例变量
在将可变对象传递到Constructors
和set
方法或类get
方法之外的任何时候创建defensive copy始终是一个好习惯。 。如果没有这样做,那么调用者通过改变同时对类及其调用者可见的对象的状态来破坏封装是很简单的。
此外,不要使用克隆方法来制作一个参数(可变对象)的防御性副本,该参数的类型可由不信任方进行子类化,因为这可能会导致实例变量内部状态的有意或无意更改。
按照所有这些规则,你的方法都不正确。
因此,在代码中遵循封装的正确方法是:
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());
}
}
答案 3 :(得分: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
做什么。我希望你能理解为什么那里有克隆。
答案 4 :(得分: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);
}
}
答案 5 :(得分: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....
这样,子类就无法掌握内部结构。
答案 6 :(得分: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。
答案 7 :(得分:0)
不是针对这个具体的例子,而是针对一般方法:
为用户提供将返回的对象强制转换为他想要的任何其他兼容类的方法总是很好的。
所以第一个方法看起来不错,因为如果你要返回的对象已被其他类扩展,那么很容易将它转换为该类型,而不是提供固定类型的对象。
通过这种方式,您可以提供更一般的对象,或者我可以说abstraction
并鼓励polymorphism
。