java 如何修复这种隐私泄露?

vshtjzan  于 2023-03-16  发布在  Java
关注(0)|答案(1)|浏览(180)

我用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。我不确定如何防止这种情况。
我已经尝试了上述代码,直接分配和这个.开始直接分配.

cetgtptt

cetgtptt1#

“封装”错误指的是可变性问题。
当你有一个对象,它的值可以通过调用它的方法(或者直接干预它的字段)来修改时,那么你就有了下面的问题:
如果您将对象交给您无法控制的代码,* 或 * 您从您无法控制的代码处接收此对象,则该对象可能随时发生更改,完全超出您的控制范围
换句话说,你做的测试是创建一个TripPart对象,然后 * 之后 * 更改dep变量引用的对象的时间,并测试这是否确实更改了TripPart示例的开始时间?事实上,这正是不应该发生的事情
这是一个“封装错误”。
想象一下,你想让TripPart保证开始总是在结束之前,这并不是一个疯狂的愿望,它很方便:当我接收到一个TripPart对象时,我甚至不想费心去想'end is in actual before start'到底是什么意思,事实上,你的构造函数甚至花了一些功夫来迎合这一点,不管任何试图设置一个在start日期 * 之后 * 的enddate的尝试。

但现在这是一个毫无意义的练习-毕竟,我可以简单地将'today'和'tomorrow'传递给你的构造函数,这很好,然后将'tomorrow'条目更改为实际上是昨天,这个动作不会神奇地重新运行你的基于构造函数的检查。现在我有一个'broken' TripPart对象,它的start实际上在end之前。

这只是一个简单的例子:如果你不封装(允许外部代码独立地修改你的内部代码),那么检查任何类型的约束都是 * 毫无意义的 *。
“封装失败”并不是代码错误,它意味着你的代码肯定不起作用或无法编译,而是一个风格提示:您正在运行一些额外的软件检查工具(通常称为“链接器”),该工具扫描您的代码,寻找 * 可能 * 表明代码质量不好的内容,并检测封装错误。

修复它

  • 注:这个一般的修复实际上是一个非常糟糕的主意,我强烈建议不要在这种情况下;选择第二种策略!*
  • 通常 *,您可以通过创建防御性拷贝来修复封装错误:不要只是将从第一个构造函数中获得的datedate2引用直接存储到字段中,而是进行复制。现在调用者可以随心所欲地处理他们传递的date对象;它不会影响您代码。

不幸的是,这种防御性的复制是双向的:你也不能分发内部的可修改字段(Date对象是可修改的),相反,你还必须做一个防御性的拷贝,这是大量的拷贝,非常烦人。

更好的解决方案

不可变的数据类型。这些数据类型根本没有办法改变它们。充其量,有办法要求它们做一些修改来克隆自己。
下面是一个您肯定很熟悉的小例子-String-它是一个不可变的数据类型(通常为1)。

public static void main(String[] args) {
  String a = "Hello!";
  a.toLowerCase();
  System.out.println(a);
}

这段代码打印了... 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.Instantjava.time.ZonedDateTimejava.time.LocalDateTime,具体取决于您的需要。(新API做对的一件事是认识到对时间概念的不同估计是相互不兼容的。任何只有一种类型表示时间的日期/时间API必然是坏的)。幸运的是,所有这些类型都是不可变的
不需要担心封装。
所以这就是你应该做的。抛弃日期(和日历)。使用java.time包中的各种东西。

[1]“不可变”是一个模糊的概念。字符串通常是不可变的,但也不是完全不可变的--例如,它有一个“hashcodes are fast”属性,可以通过对字符串调用hashCode()来改变该属性,也可以通过计算hashCode()的运行时间来观察该属性。如果您的程序依赖于hashCode所花费的时间()调用,你显然在做一些非常非常疯狂的事情,所以字符串在这方面并不是完全不可变的(或者当然应该是任何理智的代码)无关紧要的。重点是,如果你试图将它锁定在 exactly,那么immutable并不是一个定义良好的概念,但是对于大多数实际用途来说,这是很清楚的,而且对于大多数实际用途来说,所有字符串都是不可变的。

相关问题