您的位置:首页 > 其它

保护性拷贝和不可变视图的学习,更面向对象的设计

2017-06-20 16:02 465 查看
最近在阅读《Effective JAVA》,感谢它让我重温了很多知识点。不过有些章节不是一时半会就能全懂,也是要多回头再看,结合一些实际经验就好理解了。今天看到“必要时进行保护性拷贝”有感,记录一下。

JAVA是一门面对向象的语言,对象作为主体。对象中可能有不少内部组件,比如List,map等。按照惯例,我们可能会为一些属性提供setter和getter。这个时候我们可能就越过了对象控制属性,而是直接操作了。另外从安全性上讲,对外提供的查询类型的方法,是不允许修改到组件内部来的。因为对外的方法返回的是内部组件对象的引用,客户端在不可控的时候,有可能破坏。

如下面的代码,声称可以表示一段不可变的时间。粗略看start和end都有比较先后,并且都是final的。

public final class Period {
    private final Date start;
    private final Date end;

    /**
     * @param start
     *            the beginning of the period
     * @param end
     *            the end of the period; must not precede start
     * @throws IllegalArgumentException
     *             if start is after end
     * @throws NullPointerException
     *             if start or end is null
     */
    public Period(Date start, Date end) {
        if (start.compareTo(end) > 0)
            throw new IllegalArgumentException(start + " after " + end);
        this.start = start;
        this.end = end;
    }

    // Repaired constructor - makes defensive copies of parameters - Page 185
    // Stops first attack
    // public Period(Date start, Date end) {
    // this.start = new Date(start.getTime());
    // this.end = new Date(end.getTime());
    //
    // if (this.start.compareTo(this.end) > 0)
    // throw new IllegalArgumentException(start +" after "+ end);
    // }

    public Date start() {
        return start;
    }

    public Date end() {
        return end;
    }

    // Repaired accessors - make defensive copies of internal fields - Page 186
    // Stops second attack
    // public Date start() {
    // return new Date(start.getTime());
    // }
    //
    // public Date end() {
    // return new Date(end.getTime());
    // }

    public String toString() {
        return start + " - " + end;
    }

    // Remainder omitted
}



然而,Date类本身是可变的。

Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
// 修改了end对象,也间接地影响了p对象
end.setYear(2016);
为了保护Period实例的内部不受这种篡改,对构造方法的每个可变参数进行保护性拷贝。

public Period(Date start, Date end) {

this.start = new Date(start.getTime());
this.end = new Date(end.getTime());

if (start.compareTo(end) > 0)
throw new IllegalArgumentException(start + " after " + end);
this.start = start;
this.end = end;
}
使用新对象而不是老对象的引用,这样就切断了通过老对象的修改,间接影响p对象。但是这并不能完全解决问题,还可以通过 p.end().setYear(2016);来修改。两个方法得改。

public Date start() {
return new Date(start.getTime());
}

public Date end() {
return new Date(end.getTime());
}
这样子后,Period真的是不可变的了。

把内部组件返回给调用方之前,应该认真考虑一下是否会引起上述的问题。一种方法是进行保护性拷贝,另一种是返回不可变视图。

保护性视图可以见下面的例子,引用一篇博客,这也是之前未接触过的一个方法  Collections.unmodifiableList(List<? extends T> list))。在《重构——改善既有代码的设计》一书中,有一种重构手法叫Encapsulate Collection (封装集群)。类Student有一个ArrayList属性, 很多人可能会如下设计类Student。但是,如果通过Student.getCourses()获得对ArrayList属性引用后,就可以任意为Student对象添加“课程”,而Student对象对此一无所知,这不符合面向对象编程的约定。

import java.util.ArrayList;

public class Student {
private String name;

private ArrayList<String> courses;

public Student(String name, ArrayList<String> courses) {
this.name = name;
this.courses = courses;
}

public ArrayList<String> getCourses() {
return courses;
}

public void setCourses(ArrayList<String> courses) {
this.courses = courses;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("001");
list.add("002");
Student s = new Student("Tom", list);
ArrayList<String> anotherList = s.getCourses();
anotherList.add("999"); // 获取了list的引用,就可以随意增加课程。
System.out.println("Tom's course.length = " + s.getCourses().size());
}
}
稍作修改,首先仅对外提供的getCourses()方法,而没有setCourses()方法,而且通过getCourses()方法获得的courses是“只读的”,如果你试图向其添加一个新课程,则抛出java.lang.UnsupportedOperationException。你必须通过Student1.addCourse()来向特定的Student1对象添加一个新课程。就好像,你必须让顾客自己向购物车里放食物,而不能在顾客毫不知情下,偷偷向其购物车里放食物。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Student {
private String name;

private ArrayList<String> courses;

public Student(String name, ArrayList<String> courses) {
this.name = name;
this.courses = courses;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public void addCourse(String course) {
courses.add(course);
}

public String removeCourse(String course) {
boolean removed = courses.remove(courses);

if (removed) {
return course;
} else {
return null;
}
}

public List<String> getCourses() {
return Collections.unmodifiableList(courses);
}

public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("001");
list.add("002");
Student s = new Student("Tom", list);

List<String> anotherList = s.getCourses();

/**
* throws java.lang.UnsupportedOperationException should replace with
* s.addCourse(String course)
*/
anotherList.add("999");

// never reached
System.out.println("Tom's course.length = " + s.getCourses().size());
}
}


返回只读对象Collections.unmodifiableList(List);实现的,它是一个新的类,对于那些不涉及修改的方法,直接返回原对象的方法结果;对于增删的方法,全部都抛出了“不支持的操作”的异常。

public int size()                   {return c.size();}
public boolean isEmpty()            {return c.isEmpty();}
public boolean contains(Object o)   {return c.contains(o);}
public Object[] toArray()           {return c.toArray();}
public <T> T[] toArray(T[] a)       {return c.toArray(a);}
public String toString()            {return c.toString();}

public boolean add(E e) {
throw new UnsupportedOperationException();
}
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}

public boolean containsAll(Collection<?> coll) {
return c.containsAll(coll);
}
public boolean addAll(Collection<? extends E> coll) {
throw new UnsupportedOperationException();
}
public boolean removeAll(Collection<?> coll) {
throw new UnsupportedOperationException();
}
public boolean retainAll(Collection<?> coll) {
throw new UnsupportedOperationException();
}
public void clear() {
throw new UnsupportedOperationException();
}


参考文献:http://hi.baidu.com/onejava/blog/item/10388f2b77d340fae6cd4091.html

     
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐