从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教程请访问我们的网站.转载请保留这段文字。