| 风信Java论坛 ›› J2EE/Java Web 讨论交流中心 ›› 从Java的角度理解设计模式(连载) | 登录 -> 注册 |
|
1F 从Java的角度理解设计模式(连载) jiameng198 Post by : 2009-06-19 15:13:59.0
从Java的角度理解设计模式1:什么是重构
MF在《重构》一书中是这样定义重构的:重构是这样一个过程,在不改变代码外在行为的前提下,对代码作出修改,以改进程序的内部结构。重构是一种有纪律的、经过训练的、有条不紊的程序整理方法,可以将整理过程中不小心引入错误的机率降到最低。本质上说,重构就是在代码写好之后改进它的设计。 通常,软件开发总是先设计后编码,然而难免地,程序员编码风格的随意性、系统庞大化等诸多因素,导致代码质量将变得愈发难以控制。重构使得即使在面对糟糕设计,甚至是代码一片混乱的情况下,也能逐步改良代码的设计。大多数重构的步骤非常简单,比如为一个方法添加一个参数(Refactoring:Add Parameter)、(在类间)搬动一个方法(Refactoring :Move Method)、在类阶层中上下迁移代码:如值域(Refactoring :Push Up/Down Field)。然而正是这些微小的步骤,保障了代码朝着优良的结构持续演变,或者说不会日益“腐烂”。 此外,重构的工作方法也使得所谓的精心设计不再如此举足轻重了,因为设计可以在持续重构中得到强化。 如下展示了一个非常简单的重构,它采用了(Refactoring:Add Parameter): private void mainTestBody() { ApplicationContext context = new ClassPathXmlApplicationContext("ch6/prototype/setter-injection.xml"); BookList booklist = (BookList)context.getBean("bookList"); Book[] books = booklist.findBooksWrittenBy("天下霸唱"); assertEquals("鬼吹灯",books[0].getName()); } private void mainTestBody(String configMetadata) { ApplicationContext context = new ClassPathXmlApplicationContext(configMetadata); BookList booklist = (BookList)context.getBean("bookList"); Book[] books = booklist.findBooksWrittenBy("天下霸唱"); assertEquals("鬼吹灯",books[0].getName()); } 本书对于重构剖析的侧重点有两个:一个是如何将重构应用于Java模式和框架设计的编程中;一个则是如何利用自动化重构工具对代码实施重构(当然并不是所有的重构技巧都得到工具支持)。 本书对重构提供的阅读方法是:本章将会列出所有本书中用到的重构技巧名录(按字母顺序排列),而在后续各个章节中,对这些重构技巧的应用时机和方法会有更贴近实际的讲解;关于自动化重构工具的使用仅在本章列出,在后续章节中如果希望得到自动化的重构指导(对于每个重构技巧,如果自动化重构工具支持的话,都会有相应的指导),可参考本章。 |
|
2F 从Java的角度理解设计模式2:自动化重构工具的使用 jiameng198 Post by : 2009-06-19 15:14:27.0
重构的理论很具有吸引力,然而纯手工的重构需要检查的东西相当多,是一项耗时、易错的工作。也因为这个原因,很多程序员觉得重构的成本太大而不愿意进行重构。自动化重构工具的出现,大大改善了这个局面。现代化IDE融入重构功能后,使得编程和重构之间的差异也日益缩小(程序员几乎不再需要分辩“正在编程”还是“正在重构”),许多自动化的重构甚至基本都无须测试,重构成本也因此大大降低。 |
|
3F Re:~ 从Java的角度理解设计模式3:自动提炼方法 jiameng198 Post by : 2009-06-22 11:15:39.0
下面将简单展示两个重构技巧“提炼方法(Refactoring:Extract Method)”和“搬移方法(Refactoring:Move Method)”如何通过Eclipse自动实施。给出原始代码,如下所示。 |
|
4F Re:~ 从Java的角度理解设计模式4:自动搬移方法 jiameng198 Post by : 2009-06-22 11:16:01.0
接着,需要通过(Refactoring:Move Method)将这个方法搬移至一个工具类中,为此,简单的拟一个抽象工具类BeanUtils,如下所示。
BeanUtils.java package ch2.utils; public abstract class BeanUtils { } 回到上文的RefactoringIDETest类,在IDE中双击选中instantiateClass()方法名,点击鼠标右键,在重构菜单中,点选“Move”(IDE会自动识别选定的资源,比如本例选中的是方法,那么采用的重构技巧就是(Refactoring:Move Method)),如图3所示。 图3 自动化重构过程第三步 在随后的对话框中,找到刚才新建的BeanUtils类,点击“OK”,如图4所示。 图4 自动化重构过程第四步 在实施了所有自动化重构后,给出所有更动过的代码,如代码~2.2所示。 代码 BeanUtils.java package ch2.utils; public abstract class BeanUtils { public static Object instantiateClass(String className) throws Exception { Object beanInstance = null; try { Class clazz = Class.forName(className); beanInstance = clazz.newInstance(); } catch (Exception ex) { throw ex; } return beanInstance; } } 代码 RefactoringIDETest.java package ch2.extractandmovemethod; import ch2.extractandmovemethod.prototype.HelloBean; import ch2.utils.BeanUtils; public class RefactoringIDETest { public static void main(String[] args) throws Exception { String className = "ch2.refactoringusingide.HelloBean"; Object beanInstance = BeanUtils.instantiateClass(className); ((HelloBean)beanInstance).sayHello(); } } 上面演示了自动化重构工具(Eclipse3.3)的基本使用方法,下文将介绍更多的重构技巧(自动/非自动) 融智技术学苑(http://www.rzchina.net )版权所有,本公司致力于提供更好更实用的Java培训课程,帮助学员迅速成为编程的行家里手,更多Java面试题\Java视频\Java教程请访问我们的网站.转载请保留这段文字。 |
|
5F 从Java的角度理解设计模式5:自动改变方法签名 jiameng198 Post by : 2009-06-22 11:17:06.0
(Refactoring:Change Method Signature)其实是Eclipse组合了《重构》一书中的几个重构技巧而成,它们分别是Add/Remove Parameter(增加/移除 方法参数)、Rename Method(重命名方法)。 BeanFactory.java package ch2.changemethodsignature.prototype; 图5 自动改变方法签名 BeanFactory.java public class BeanFactory { 代码 BeanFactory.java package ch2.changemethodsignature; |
|
6F Re:~ 从Java的角度理解设计模式6:封装集合 jiameng198 Post by : 2009-06-24 15:29:49.0
(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论坛 ›› J2EE/Java Web 讨论交流中心 ›› 从Java的角度理解设计模式(连载) | 登录 -> 注册 |