我用java写了这段代码。我创建了一个类,它有两个私有变量。我有一个构造函数,它接受两个日期作为参数,一个复制构造函数,一个默认构造函数和一个setter。它还有一个getter方法,用于我没有包含的每个变量。
public class TripPart{
private Date start;
private Date end;
public TripPart (Date date, Date date2){
if ((date == null && date2 == null) || date == null || date2 == null) {
start = date;
end = date2;
}
else {
boolean before2 = date.before(date2);
if (before2) {
start = date;
end = date2;
}
else {
start = date;
end = null;
}
}
}
public TripPart(){
start = new Date();
Calendar newEnd = Calendar.getInstance();
newEnd.setTime(start);
newEnd.add(Calendar.HOUR_OF_DAY, 1);
end = newEnd.getTime();
}
public TripPart (TripPart toCopy){
this(toCopy.start,toCopy.end);
}
public void setStart(Date date) {
if (end != null) {
if (date.before(end)) {
start = date;
}
}
else {
start = date;
}
}
public void setEnd(Date date) {
if (start != null) {
if (date.after(start)) {
this.end = date;
}
}
else {
this.end = date;
}
}
我正在运行的测试一直未能通过封装测试。
public class TripPartTest{
private Date getDate(int year, int month, int day, int hour, int minute) {
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(year, month, day, hour, minute, 0);
return cal.getTime();
}
@Test
public void testConstructor_startEncapsulation(){
Date argumentstart = getDate(2019,11,28,10,25);
TripPart c = new TripPart(argumentstart,null);
argumentstart.setTime(getDate(2020,10,20,14,0).getTime());
Date expectedstart = getDate(2019,11,28,10,25);
assertEquals("Changed argument after creation from 2019:11:20:10:25 to 2020:10:20:14:00, expect TripPart start to remain unchanged.", expectedstart.toString(), c.getStart());
}
@Test
public void test_setter_and_getter_start_setterEncapsulation(){
Date dep = getDate(2019,11,28,10,20);
TripPart c = new TripPart(dep, getDate(2019,11,28,11,21));
dep.setTime(getDate(2018,11,28,8,0).getTime());
Date expectedstart = getDate(2019,11,28,10,20);
assertEquals("Changed start time provided as argument to setstart to 2018 from 2019, testing that start in TripPart unchanged", expectedstart.toString(), c.getStart());
}
当在测试中调用方法后更改argumentStart和dep时,它会更改TripPart。我不确定如何防止这种情况。
我已经尝试了上述代码,直接分配和这个.开始直接分配.
1条答案
按热度按时间cetgtptt1#
“封装”错误指的是可变性问题。
当你有一个对象,它的值可以通过调用它的方法(或者直接干预它的字段)来修改时,那么你就有了下面的问题:
如果您将对象交给您无法控制的代码,* 或 * 您从您无法控制的代码处接收此对象,则该对象可能随时发生更改,完全超出您的控制范围。
换句话说,你做的测试是创建一个
TripPart
对象,然后 * 之后 * 更改dep
变量引用的对象的时间,并测试这是否确实更改了TripPart
示例的开始时间?事实上,这正是不应该发生的事情。这是一个“封装错误”。
想象一下,你想让TripPart保证开始总是在结束之前,这并不是一个疯狂的愿望,它很方便:当我接收到一个
TripPart
对象时,我甚至不想费心去想'end is in actual before start'到底是什么意思,事实上,你的构造函数甚至花了一些功夫来迎合这一点,不管任何试图设置一个在start日期 * 之后 * 的enddate的尝试。但现在这是一个毫无意义的练习-毕竟,我可以简单地将'today'和'tomorrow'传递给你的构造函数,这很好,然后将'tomorrow'条目更改为实际上是昨天,这个动作不会神奇地重新运行你的基于构造函数的检查。现在我有一个'broken' TripPart对象,它的start实际上在end之前。
这只是一个简单的例子:如果你不封装(允许外部代码独立地修改你的内部代码),那么检查任何类型的约束都是 * 毫无意义的 *。
“封装失败”并不是代码错误,它意味着你的代码肯定不起作用或无法编译,而是一个风格提示:您正在运行一些额外的软件检查工具(通常称为“链接器”),该工具扫描您的代码,寻找 * 可能 * 表明代码质量不好的内容,并检测封装错误。
修复它
date
和date2
引用直接存储到字段中,而是进行复制。现在调用者可以随心所欲地处理他们传递的date对象;它不会影响您代码。不幸的是,这种防御性的复制是双向的:你也不能分发内部的可修改字段(
Date
对象是可修改的),相反,你还必须做一个防御性的拷贝,这是大量的拷贝,非常烦人。更好的解决方案
不可变的数据类型。这些数据类型根本没有办法改变它们。充其量,有办法要求它们做一些修改来克隆自己。
下面是一个您肯定很熟悉的小例子-
String
-它是一个不可变的数据类型(通常为1)。这段代码打印了...
Hello!
.,其中大写的是H
。为什么呢?因为.toLowerCase()
* 根本没有修改字符串 *。它只是创建了一个修改过的克隆:它生成一个全新的字符串("hello!"
)并返回 that,这是因为字符串不能被修改,它们没有set
方法,也没有任何其他方法来修改它的内部结构(很容易观察到)。因此,不需要封装。你可以像糖果一样分发任何包含你内部状态的字符串,或者接受一个传入的字符串而不做防御性的复制。因为没有人可以改变这些字符串,他们只能创建新的字符串,这对你的代码无关紧要。
代码的特定修复:
java.time
java.util.Date
(和java.util.Calendar
)是过时的。Java-the-core-lib有意不将过时的核心功能标记为“@Deprecated
”,原因很复杂,可能是错误的,但它就是这样。尽管如此,这些类型永远不应该使用。它们的API很糟糕(Date中四分之三的方法实际上是@Deprecated
,而核心库几乎从不这样做,只有那些根本无法修复的方法才会这样做),它们的名称是谎言(Date
根本不表示日期,它们表示时间上的时刻。Calendar
示例也不表示日历(它还表示时间上的时刻)。正确使用的API是
java.time
-具体来说,可以是java.time.Instant
、java.time.ZonedDateTime
或java.time.LocalDateTime
,具体取决于您的需要。(新API做对的一件事是认识到对时间概念的不同估计是相互不兼容的。任何只有一种类型表示时间的日期/时间API必然是坏的)。幸运的是,所有这些类型都是不可变的。不需要担心封装。
所以这就是你应该做的。抛弃日期(和日历)。使用
java.time
包中的各种东西。[1]“不可变”是一个模糊的概念。字符串通常是不可变的,但也不是完全不可变的--例如,它有一个“hashcodes are fast”属性,可以通过对字符串调用
hashCode()
来改变该属性,也可以通过计算hashCode()
的运行时间来观察该属性。如果您的程序依赖于hashCode所花费的时间()调用,你显然在做一些非常非常疯狂的事情,所以字符串在这方面并不是完全不可变的(或者当然应该是任何理智的代码)无关紧要的。重点是,如果你试图将它锁定在 exactly,那么immutable并不是一个定义良好的概念,但是对于大多数实际用途来说,这是很清楚的,而且对于大多数实际用途来说,所有字符串都是不可变的。