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

来源:互联网 发布:印尼旅行社软件 编辑:程序博客网 时间:2024/06/05 10:52

最近在阅读《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 reachedSystem.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



     


















阅读全文
0 0
原创粉丝点击