主题:从Java的角度理解设计模式(连载)
jiameng1987
[专家分:0] 发布于 2009-06-19 16:18:00
从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模式和框架设计的编程中;一个则是如何利用自动化重构工具对代码实施重构(当然并不是所有的重构技巧都得到工具支持)。
本书对重构提供的阅读方法是:本章将会列出所有本书中用到的重构技巧名录(按字母顺序排列),而在后续各个章节中,对这些重构技巧的应用时机和方法会有更贴近实际的讲解;关于自动化重构工具的使用仅在本章列出,在后续章节中如果希望得到自动化的重构指导(对于每个重构技巧,如果自动化重构工具支持的话,都会有相应的指导),可参考本章。
回复列表 (共5个回复)
沙发
jiameng1987 [专家分:0] 发布于 2009-06-19 16:19:00
从Java的角度理解设计模式2:自动化重构工具的使用
重构的理论很具有吸引力,然而纯手工的重构需要检查的东西相当多,是一项耗时、易错的工作。也因为这个原因,很多程序员觉得重构的成本太大而不愿意进行重构。自动化重构工具的出现,大大改善了这个局面。现代化IDE融入重构功能后,使得编程和重构之间的差异也日益缩小(程序员几乎不再需要分辩“正在编程”还是“正在重构”),许多自动化的重构甚至基本都无须测试,重构成本也因此大大降低。
Java语言语法简洁清晰,是非常便于重构的一门语言,本书介绍的自动化重构工具便是一款成熟的Java IDE:Eclipse(使用的版本是3.3),读者可在http://www.eclipse.org/downloads/ 下载到Eclipse的最新版本。
板凳
jiameng1987 [专家分:0] 发布于 2009-06-22 13:46:00
从Java的角度理解设计模式3:自动提炼方法
下面将简单展示两个重构技巧“提炼方法(Refactoring:Extract Method)”和“搬移方法(Refactoring:Move Method)”如何通过Eclipse自动实施。给出原始代码,如下所示。
HelloBean.java
package ch2.extractandmovemethod.prototype;
public class HelloBean {
public void sayHello() {
System.out.println("朋友,时刻重构吧!");
}
}
RefactoringIDETest.java
package ch2.extractandmovemethod.prototype;
public class RefactoringIDETest {
public static void main(String[] args) throws Exception {
String className = "ch2.refactoringusingide.HelloBean";
Object beanInstance = null;
try {
Class clazz = Class.forName(className);
beanInstance = clazz.newInstance();
} catch (Exception ex) {
throw ex;
}
((HelloBean)beanInstance).sayHello();
}
}
注意上述代码的框选部分,显然这部分代码可以被提炼出来形成一个公用方法。
说明:如果使用重构工具,只需简单地圈选出要提炼的段落,然后点选菜单选项“Extract Method”就行了。重构工具会自动检查被圈选的代码段是否可以提炼。代码无法提炼的原因有很多,可能是:它可能只包含部分标识符声明;可能对某个变量赋值而该变量又被其它代码用到。所有这些情况,程序员都完全不必担心,因为重构工具会自动识别并处理一切。随后,工具会自动计算出新方法所需的参数,并要求程序员为新方法取一个名字。此时,程序员可自行决定新方法参数的命名、排列顺序等。所有准备工作都完成后,重构工具会把圈选的代码从源方法中提炼出来,并在源方法中加入对新方法的调用。随后它会再源方法所属的类中建立新的方法,并以程序员指定的名称来命名该方法。整个重构过程耗时极短,且不会出错,相比手工进行这个重构,方便、安全了许多,体现了自动化重构工具的威力。
现在用Eclipse来实现上述步骤,首先选中上述代码的框选部分,点击鼠标右键,找到重构菜单“Refactor”,点选“Extract Method” (注意,重构菜单会自动识别出当前圈选的代码段有哪些重构技巧可用),如图1所示。
图1 自动化重构过程第一步
在随后出现的对话框中,为提炼出的新方法命名(命名为“instantiateClass”),并决定其它的方法签名(注意,为了后续可以不用修改地将此方法搬移到另一个工具类中,请选择“public”的方法访问权限),如图2所示。
图2 自动化重构过程第二步
点选“Preview”可预览重构所牵动的资源和结果,点选“OK”将直接进行重构,点选“Cancel”将放弃重构,当然即使在重构完成后,Eclipse也提供了强大的重构回复功能,可将所有牵动的代码回复到重构前的状态,甚至还有重构的历史回复功能。现在,点击“OK”,得到应用了(Refactoring:Extract Method)后的代码,如下所示。
RefactoringIDETest.java
package ch2.extractandmovemethod;
import ch2.extractandmovemethod.prototype.HelloBean;
public class RefactoringIDETest {
public static void main(String[] args) throws Exception {
String className = "ch2.refactoringusingide.HelloBean";
Object beanInstance = instantiateClass(className);
((HelloBean)beanInstance).sayHello();
}
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;
}
}
融智技术学苑(http://www.rzchina.net)版权所有,本公司致力于提供更好更实用的Java培训课程,帮助学员迅速成为编程的行家里手,更多Java面试题\Java视频\Java教程请访问我们的网站.转载请保留这段文字。
3 楼
jiameng1987 [专家分:0] 发布于 2009-06-22 13:47:00
从Java的角度理解设计模式4:自动搬移方法
接着,需要通过(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教程请访问我们的网站.转载请保留这段文字。
4 楼
jiameng1987 [专家分:0] 发布于 2009-06-23 11:03:00
从Java的角度理解设计模式5:自动改变方法签名
(Refactoring:Change Method Signature)其实是Eclipse组合了《重构》一书中的几个重构技巧而成,它们分别是Add/Remove Parameter(增加/移除 方法参数)、Rename Method(重命名方法)。
1.动机
方法签名包含方法名、参数列(类型和次序)、方法返回类型、方法访问权限等,在编程的时候,改变方法签名有各种动机,比如当发现方法的名称未能明确揭示方法用途时,则需要改变方法名称(Refactoring:Rename Method);当发现某个方法需要从调用端得到更多信息时,则需要为方法添加参数(Refactoring:Add Parameter,见2.1节示例);当发现方法本体不再需要某个参数时,就需要移除参数(Refactoring:Remove Parameter),冗余的参数往往会给客户带来负担。
2.作法
以上这些重构技巧,在Eclipse中都可以通过重构菜单“Change Method Signature”来一并完成。给出原始的类,如下所示。
BeanFactory.java
package ch2.changemethodsignature.prototype;
import ch2.utils.BeanUtils;
public class BeanFactory {
public Book getBook() throws Exception {
return (Book)BeanUtils.instantiateClass(
"ch2.changemethodsignature.prototype.Book");
}
}
显然getBook()作为BeanFactory的主力工厂方法它不够通用,因此需要适当改变它的方法签名和实现。现在用Eclipse来实现上述步骤,首先双击选中getBook()方法,点击鼠标右键,找到重构菜单“Refactor”,点选“Change Method Signature”,在随后出现的对话框中,为getBook()方法重命名(命名为“getBean”);调整它的返回值为“Object”、访问权限为“public”;增加两个String型的参数“packageName”和“className”,如图5所示。
图5 自动改变方法签名
去掉方法内的强制转型后,得到初步重构后的代码,如下所示。
BeanFactory.java
public class BeanFactory {
public Object getBean(String packageName, String className) throws Exception {
return BeanUtils.instantiateClass(packageName + className);
}
}
发现方法参数冗余,使用一个Bean的标识符就足够了。迅速作出再次重构,通过Eclipse的“Change Method Signature”菜单去掉一个参数(使用图5中的Remove按钮),并改变参数名称,得到最后的代码,如代码所示。
代码 BeanFactory.java
package ch2.changemethodsignature;
import ch2.utils.BeanUtils;
public class BeanFactory {
public Object getBean(String beanIdentifier) throws Exception {
return BeanUtils.instantiateClass(beanIdentifier);
}
}
融智技术学苑(http://www.rzchina.net )版权所有,本公司致力于提供更好更实用的Java培训课程,帮助学员迅速成为编程的行家里手,更多Java面试题\Java视频\Java教程请访问我们的网站.转载请保留这段文字。
5 楼
jiameng1987 [专家分:0] 发布于 2009-06-24 16:34:00
从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);
}
}
融智技术学苑(http://www.rzchina.net )版权所有,本公司致力于提供更好更实用的Java培训课程,帮助学员迅速成为编程的行家里手,更多Java面试题\Java视频\Java教程请访问我们的网站.转载请保留这段文字。
我来回复