从Java的角度理解设计模式6:封装集合
(Refactoring:Encapsulate Collection)是指当发现某个方法返回类型是集合(Collection)的时候,使该方法仅返回一个该集合的只读视图(read-only view),并在该class内提供添加/移除(add/remove)该集合元素的方法。如图6所示。 
图6 封装集合示意图 
1.动机 
一个类如果持有一个集合,那么它往往需要提供出对该集合的取值器或者设值器(getter/setter)。然而有时,取值器直接返回集合(引用)是不合适的,因为用户可以通过该引用直接修改集合内容。此外,也不应该为整个集合提供一个设值器,一个更好的方法是给出用以为集合添加/移除(add/remove)元素的方法。做到以上两点,这就加强了类对集合的封装并降低了用户和类(集合持有者)的耦合。 
2.作法 
给出原始的类,假设它是对一个购物车的抽象,称其为Cart,而Cart内部又持有一个商品集合,如下所示。 
Cart.java 
package ch2.encapsulatecollection.prototype; 
import java.util.ArrayList; 
import java.util.List; 
public class Cart { 
private List products = new ArrayList(); 
public List getProducts() { 
return products; 
} 
public void setProducts(List products) { 
this.products = products; 
} 
} 
给出商品抽象,称其为Product,注意其中实现了equals()方法,它用以判断两个Product对象是否相等(判断依据主要是Product的name),如代码所示。 
代码 Product.java 
package ch2.encapsulatecollection.prototype; 
public class Product { 
private String name; 
private int price; 
public Product(String name, int price) { 
this.name = name; 
this.price = price; 
} 
public String getName() { 
return name; 
} 
public int getPrice() { 
return price; 
} 
public boolean equals(Object obj) { 
if (this == obj) return true; 
if (obj != null && (obj.getClass() == getClass())) { 
if (((Product)obj).getName().equals(getName())) { 
return true; 
} 
} 
return false; 
} 
} 
(Refactoring:Encapsulate Collection)在Eclipse中并未提供自动支持,所以需要手工进行。大致步骤如下: 
(1)使Cart内部持有的集合只读化,如下所示。 
Cart.java 
public class Cart { 
... 
public List getProducts() { 
return Collections.unmodifiableList(products); 
} 
... 
} 
(2)向Cart增加/移除商品的方法,增加/移除商品的方法返回类型设为Cart是因为便于编程,这样就可在单行语句中连续刷新商品集合,如下所示。 
Cart.java 
public class Cart { 
... 
public Cart addProduct(Product product) { 
for (int i = 0; i < this.products.size(); i++) { 
Product currentProduct = (Product) this.products.get(i); 
if (currentProduct.equals(product)) { 
setProductAt(product, i); 
return this; 
} 
} 
products.add(product); 
return this; 
} 
public Cart removeProduct(Product product) { 
products.remove(product); 
return this; 
} 
private void setProductAt(Product product, int i) { 
products.set(i, product); 
} 
... 
} 
(3)重构Cart的setProducts()方法。 
这里读者可能会产生两个疑问:既然有了商品的增加/移除方法,为何不简单移除setProducts()方法?如果保留setProducts()方法,为何要重构它,它有什么问题?以下稍微解答一下,去掉setProducts()方法是可以的,然而当重构前的代码中存在大量对setProducts()方法的调用时,在每一处作相应改变是繁琐的,因此一个折中的方案是重构setProducts()方法;那么原来的setProducts()方法存在什么问题呢,请看如下测试代码。 
代码 CartTest.java 
package ch2.encapsulatecollection; 
import java.util.ArrayList; 
import java.util.List; 
import ch2.encapsulatecollection.prototype.Product; 
import junit.framework.TestCase; 
public class CartTest extends TestCase { 
public void testSetProducts() { 
Cart cart = new Cart(); 
List products = new ArrayList(); 
cart.setProducts(products);① 
int size1 = cart.getProducts().size(); 
Product p = new Product("aa", 1); 
products.add(p); ② 
int size2 = cart.getProducts().size(); 
assertEquals(size1, size2);③ 
} 
} 
如上所示,在①处向Cart初始化了一个空集合后,在②处对原有集合的变更理应不会波及到Cart,然而③处的测试却失败了。这就说明了原有的setProducts()方法有问题,它破坏了集合的封装,怎样修改呢?迅速对Cart的setProducts()方法作出改变,使其调用内部的addProduct()方法,并在之前清空了原有的集合,如下所示。 
public class Cart { 
... 
public void setProducts(List products) { 
this.products = new ArrayList(); 
for (Iterator iterator = products.iterator(); iterator.hasNext();) { 
addProduct((Product)iterator.next()); 
} 
} 
... 
} 
观察上述代码后发现,其实setProducts()只是逐一向一个空集合添加元素,它执行的是初始化功能,因此应该使用(Refactoring:Rename Method)来明示方法的用途,并且可以提供一个更简单、高效的实现,如下所示: 
public class Cart { 
... 
public void initializeProducts(List products) { 
this.products = new ArrayList(); 
this.products.addAll(products); 
} 
... 
} 
(4)修改牵连代码,比如原先通过getProducts()方法来改变Cart内部集合的代码需要改用addProduct()方法。 
完成所有重构后,给出Cart的完整实现,如代码所示。 
代码 Cart.java 
package ch2.encapsulatecollection; 
import java.util.ArrayList; 
import java.util.Collections; 
import java.util.List; 
import ch2.encapsulatecollection.prototype.Product; 
public class Cart { 
private List products = new ArrayList(); 
public void initializeProducts(List products) { 
this.products = new ArrayList(); 
this.products.addAll(products); 
} 
public List getProducts() { 
return Collections.unmodifiableList(products); 
} 
public Cart addProduct(Product product) { 
for (int i = 0; i < this.products.size(); i++) { 
Product currentProduct = (Product) this.products.get(i); 
if (currentProduct.equals(product)) { 
setProductAt(product, i); 
return this; 
} 
} 
products.add(product); 
return this; 
} 
public Cart removeProduct(Product product) { 
products.remove(product); 
return this; 
} 
private void setProductAt(Product product, int i) { 
products.set(i, product); 
} 
} 
融智技术学苑( )版权所有,本公司致力于提供更好更实用的Java培训课程,帮助学员迅速成为编程的行家里手,更多Java面试题\Java视频\Java教程请访问我们的网站.转载请保留这段文字。