保护性拷贝和不可变视图的学习,更面向对象的设计
2017-06-20 16:02
465 查看
最近在阅读《Effective JAVA》,感谢它让我重温了很多知识点。不过有些章节不是一时半会就能全懂,也是要多回头再看,结合一些实际经验就好理解了。今天看到“必要时进行保护性拷贝”有感,记录一下。
JAVA是一门面对向象的语言,对象作为主体。对象中可能有不少内部组件,比如List,map等。按照惯例,我们可能会为一些属性提供setter和getter。这个时候我们可能就越过了对象控制属性,而是直接操作了。另外从安全性上讲,对外提供的查询类型的方法,是不允许修改到组件内部来的。因为对外的方法返回的是内部组件对象的引用,客户端在不可控的时候,有可能破坏。
如下面的代码,声称可以表示一段不可变的时间。粗略看start和end都有比较先后,并且都是final的。
然而,Date类本身是可变的。
把内部组件返回给调用方之前,应该认真考虑一下是否会引起上述的问题。一种方法是进行保护性拷贝,另一种是返回不可变视图。
保护性视图可以见下面的例子,引用一篇博客,这也是之前未接触过的一个方法 Collections.unmodifiableList(List<? extends T> list))。在《重构——改善既有代码的设计》一书中,有一种重构手法叫Encapsulate Collection (封装集群)。类Student有一个ArrayList属性, 很多人可能会如下设计类Student。但是,如果通过Student.getCourses()获得对ArrayList属性引用后,就可以任意为Student对象添加“课程”,而Student对象对此一无所知,这不符合面向对象编程的约定。
返回只读对象Collections.unmodifiableList(List);实现的,它是一个新的类,对于那些不涉及修改的方法,直接返回原对象的方法结果;对于增删的方法,全部都抛出了“不支持的操作”的异常。
参考文献:http://hi.baidu.com/onejava/blog/item/10388f2b77d340fae6cd4091.html
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
相关文章推荐
- [设计模式学习笔记之一]面向对象是什么?
- 面向对象的设计模式的学习笔记,不断学习归纳总结ing
- 设计模式学习--面向对象的5条设计原则之Liskov替换原则--LSP
- 一步步学习微软InfoPath2010和SP2010--第三章节--表单设计基础:处理InfoPath布局、控件和视图
- 设计模式学习系列2 面向对象的5大原则(转)
- 关于Singleton设计模式的计数器代码实例(拷贝粘贴即可学习)
- 一步步学习微软InfoPath2010和SP2010--第三章节--表单设计基础:处理InfoPath布局、控件和视图(2)--添加一个布局和表格
- 设计模式学习--面向对象的5条设计原则之开放封闭原则--OCP
- 一步步学习微软InfoPath2010和SP2010--第三章节--表单设计基础:处理InfoPath布局、控件和视图
- 一步步学习微软InfoPath2010和SP2010--第三章节--表单设计基础:处理InfoPath布局、控件和视图(1)--表单布局
- 面向对象分析设计学习与探索(六):面向对象的灾难(OO Catastrophe)
- java 设计模式学习笔记(7) - 浅拷贝和深拷贝
- qt从根本设计上是采用面向对象思想的(转载学习)
- 设计模式学习--面向对象的5条设计原则之依赖倒置原则--DIP
- js设计模式学习之面向对象的javascript(三)--原型式继承
- [设计模式学习笔记之一]面向对象是什么?
- 设计模式的学习(一)面向对象的原则 _ 面对变化应该怎么做
- js设计模式学习之面向对象的javascript(一)
- 设计模式学习--面向对象的5条设计原则
- BITED-Windows8应用开发学习札记之二:Win8应用常用视图设计