PC的blog

Finding... Thinking... Solving...

BlogJava 首页 新随笔 联系 聚合 管理
  9 Posts :: 0 Stories :: 54 Comments :: 0 Trackbacks

2008年8月4日 #

本文紧接使用重构移除丑陋的if else代码(4)

上篇文章谈到如何能够彻底把这个switch也移除掉呢?很简单,我们只需要在getSystemStatePerformer()方法被调用之前先创建所有 performer匿名类的实例,然后在该方法被调用时直接返回对应的实力。 如何具体实现呢? 用Map, 请看代码:

package de.jingge.refactoring;

 

import static de.jingge.refactoring.SystemState.*;

import java.awt.Image;

import java.awt.image.BufferedImage;

import java.lang.reflect.Method;

import java.util.Collections;

import java.util.HashMap;

import java.util.Map;

 

/**

 *

 * 
@author gejing@gmail.com

 
*/

public class SystemStatePerformerFactory {

 

private static SystemStatePerformerFactory INSTANCE = new SystemStatePerformerFactory();

   

    
private Map<SystemState, SystemStatePerformer> performers;

 

    
private SystemStatePerformerFactory() {

}

 

    
public static SystemStatePerformerFactory getInstance() {

        
return INSTANCE;

    }

   

    
private synchronized Map<SystemState, SystemStatePerformer> getPerformers()

            
throws Exception {

        
if (performers == null) {

            performers 
= new HashMap<SystemState, SystemStatePerformer>();

            
// call all @FactoryMethod using reflection

            
for (Method m : getClass().getDeclaredMethods()) {

                
if (m.getAnnotation(FactoryMethod.class!= null) {

                    SystemStatePerformer p 
= (SystemStatePerformer) m.invoke(

                            
thisnew Object[]{});

                    performers.put(p.getState(), p);

                }

            }

            
// make it readonly

            performers 
= Collections.unmodifiableMap(performers);

        }

        
return performers;

    }

 

    
public SystemStatePerformer getSystemStatePerformer(SystemState state) throws Exception{

        
return getPerformers().get(state);

    }

 

@FactoryMethod

    
private SystemStatePerformer createLoggedInPerformer() {

        
return new SystemStatePerformer(LOGGEDIN, getImage("loggedin.gif")) {

 

            @Override

            
public void perform() {

                
// do something after logging in is successful,

                
// for example: show welcome dialog, open the last edit document, etc.

            }

        };

    }

 

@FactoryMethod

    
private SystemStatePerformer createLoggedOutPerformer() {

        
return new SystemStatePerformer(LOGGEDOUT, getImage("loggedout.gif")) {

 

            @Override

            
public void perform() {

                
// do something after logging out is successful,

                
// for example: free used resource, dispose GUI components, etc.            }

            }

        };

    }

 

@FactoryMethod

    
private SystemStatePerformer createIdlePerformer() {

        
return new SystemStatePerformer(IDLE, getImage("idle.gif")) {

 

            @Override

            
public void perform() {

                
// do something after the user is idle,

                
// for example: save the application state temporarily, lock the application, etc.

            }

        };

    }

 

    
private Image getImage(String string) {

        
return new BufferedImage(1010, BufferedImage.TYPE_4BYTE_ABGR);

    }

}

从代码中可以看出,当getPerformers()方法被第一次调用时,我们会为每一个performer匿名类创建一个实例,并且将它们纳入Map的管 理之中,以后每次调用的时候,直接从Map里面提取对应某个状态的performer就可以了, switch可以舍弃了。 @FactoryMethod这个注释是我自己写的,使用它主要是为了避免每次新增加一个create***Performer()方法后,都必须修改 getSystemStatePerformer()。

@FactoryMethod的代码如下:

package de.jingge.refactoring;

 

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

 


@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.METHOD})

public @interface FactoryMethod {


}

到这里整个重构已经结束了, 我们已经将if else, switch完全从代码里剔除了。

读过Refactoring to Patterns这本书的朋友可能会觉得,这里所作的一些和书中第七章最后一节Replace Conditional Dispatcher with Command完全一样。 Well,第一眼看上去确实很像,但是看完我写的所有代码后,再仔细想一想,两者还是有区别的(Refactoring to Patterns这本书写的非常好,对此书,我可以说是爱不释手,还曾经写过一篇书评。事实上,我这篇文章正式基于这本书的):

1. Factory + annonymous类而不是每一个状态一个具体的实体类。

    这样处理问题, 类的数量大大减少,类关联的复杂程度也大大减少,维护起来很方便。

2. performer并不单单是一个command,它拥有状态,并且可以处理更多的逻辑。


全文完。
posted @ 2008-08-04 03:48 polygoncell 阅读(4608) | 评论 (37)编辑 收藏

本文紧接使用重构移除丑陋的if else代码(3)

OK, 到目前为止,所有的逻辑代码已经从SystemManager重构到了SystemStatePerformer。下一步应该继续重构SystemManager, 将SystemState替换为performer:

1, 使用IDE的重构功能,将变量SystemState改为SystemStatePerformer

2. 在updateState()方法中调用SystemStatePerformerFactory

3. 在测试代码里面,调用manager.statePerformer.getState()

重构后的代码如下:

package de.jingge.refactoring;

 

import static de.jingge.refactoring.SystemState.*;


public class SystemManager {

 

    SystemStatePerformer statePerformer;

 

    
public void login() {

        
// call service#login()

        updateState(LOGGEDIN);

    }

 

    
public void logout() {

        
// call service#logout()

        updateState(LOGGEDOUT);

    }

 

    
public void idle() {

        
// call some other services

        updateState(IDLE);

    }

 

    
public void updateState(SystemState state) {

        
this.statePerformer = SystemStatePerformerFactory.getInstance()

                getSystemStatePerformer(state);

        statePerformer.perform();

    }

}

可以看到if else已经消失了。


测试代码也要做相应修改:
package de.jingge.refactoring;

 

import org.junit.AfterClass;

import org.junit.BeforeClass;

import org.junit.Test;

import static org.junit.Assert.*;

import static de.jingge.refactoring.SystemState.*;


public class SystemManagerTest {

    
private static SystemManager manager;

    @BeforeClass
    
public static void setUpClass() throws Exception {

        manager 
= new SystemManager();

        
// add some service mock objects

    }

    @AfterClass
    
public static void tearDownClass() throws Exception {

    }

    @Test
    
public void login() {

        manager.login();

        assertEquals(manager.statePerformer.getState(), LOGGEDIN);

    }

    @Test
    
public void logout() {

        manager.logout();

        assertEquals(manager.statePerformer.getState(), LOGGEDOUT);

    }

    @Test
    
public void idle() {

        manager.idle();

        assertEquals(manager.statePerformer.getState(), IDLE);

    }

}

到这里重构已经差不多完成了,代码已经更加面向对象了。这里还有一个小问题,在factory里面还有一个switch,这个和if else其实是没有本质区别的,也就是说if else并没有被完全移除掉。


那么如何能够彻底把这个switch也移除掉呢?很简单,我们只需要在getSystemStatePerformer()方法被调用之前先创建所有 performer匿名类的实例,然后在该方法被调用时直接返回对应的实力。 那么具体如何实现呢,请看下一篇文章使用重构移除丑陋的if else代码(5)
posted @ 2008-08-04 03:08 polygoncell 阅读(1926) | 评论 (1)编辑 收藏

本文紧接使用重构移除丑陋的if else代码(2)

移除if else

首先仔细观察一 下updateState()方法,我们会发现,导致该方法内存在大量if else的原因是它的参数仅仅是一个enum。由于enum本身并不含有任何逻辑代码,因此导致处理enum的方法需要使用if else来分析enum然后调用相应的逻辑。明白了这个道理之后,重构的方向就明了了。简单的说,我们需要要将方法参数由enum替换成一个更加强壮的抽 象类,每一个继承该类的子类将具体负责处理一个enum实例,之后再将updateState()方法中相应的逻辑代码转移到这些子类中。这样处理之后, 令人讨厌的if else就会消失了。


我们将这个替换enum的抽象类命名为SystemStatePerformer,代码如下:

package de.jingge.refactoring;

 

import java.awt.Image;


public abstract class SystemStatePerformer {

    
private final SystemState state;

    
private Image image;

    
public SystemStatePerformer(SystemState state, Image image) {

        
this.state = state;

        
this.image = image;

    }

    
public SystemState getState() {

        
return state;

    }

    
public Image getImage() {

        
return image;

    }
    
    
public abstract void perform();

}

从代码中可以看出,每 一个performer都含义有一个SystemState,这个SystemState属性,将只能通过构建器映射方式射入一个performer的对 象实例。换句话说SystemState只是一个只读属性,而且每一个performer实体类都只负责处理一个enum的实例(下面马上会解释如何实现 的)。这里使用的Image作为一个例子,它表示用户的每一个状态都可以使用一个图标来表示。performer()方法将负责处理具体的逻辑。这个 SystemStatePerformer的实体子类可以引用任何类型的对象,然后在perform()方法里面进行调用。




下 一步就是编写SystemStatePerformer的实体子类。我首先想到的是为每一个enum实例编写一个实际的子类,理论上来说是没问题的,但是 这样做必须编写一大堆的子类,不便于管理。所以我决定使用Factory + annonymous classes来构建具体的实体子类,让Factory来管理所有的实体子类。 代码如下:

package de.jingge.refactoring;

 

import static de.jingge.refactoring.SystemState.*;

import java.awt.Image;

import java.awt.image.BufferedImage;


public class SystemStatePerformerFactory {

 

private static SystemStatePerformerFactory INSTANCE = new SystemStatePerformerFactory();

   

    
private SystemStatePerformerFactory() {

}

 

    
public static SystemStatePerformer getSystemStatePerformer(SystemState state) {

        
switch (state) {

            
case LOGGEDIN:

                
return createLoggedInPerformer();

            
case IDLE:

                
return createIdlePerformer();

            
case LOGGEDOUT:

                
return createLoggedOutPerformer();

            
default:

                
throw new IllegalAccessError("Unkonw status");

        }

    }

 

    
private static SystemStatePerformer createLoggedInPerformer() {

        
return new SystemStatePerformer(LOGGEDIN, getImage("loggedin.gif")) {

 

            @Override

            
public void perform() {

                
// do something after logging in is successful,

                
// for example: show welcome dialog, open the last edit document, etc.

            }

        };

    }

 

    
private static SystemStatePerformer createLoggedOutPerformer() {

        
return new SystemStatePerformer(LOGGEDOUT, getImage("loggedout.gif")) {

 

            @Override

            
public void perform() {

                
// do something after logging out is successful,

                
// for example: free used resource, dispose GUI components, etc.            }

            }

        };

    }

 

    
private static SystemStatePerformer createIdlePerformer() {

        
return new SystemStatePerformer(IDLE, getImage("idle.gif")) {

 

            @Override

            
public void perform() {

                
// do something after the user is idle,

                
// for example: save the application state temporarily, lock the application, etc.

            }

        };

    }

 

    
private static Image getImage(String string) {

        
return new BufferedImage(1010, BufferedImage.TYPE_4BYTE_ABGR);

    }

}

从 代码中可以看到,针对每一个enum状态都有一个创建performer的方法,该方法返回一个匿名类。逻辑代码将会被转移至个匿名类的 perform()方法之内。整个Factory只有一个公开的方 法:getSystemStatePerformer(SystemState),SystemManager可以调用这个方法来获得相应的 Performer实例。


在 这篇文章中,我希望专属于if else的问题。对于其他设计方面的问题,我采取的态度是能省略就省略。实际开发中,还有有很多问题需要处理,例如,使用static方法会导致系统的可 测试性下降,在实际开发中应该尽量避免,解决这类问题的方法之一是使用DI框架,例如Google Guice。

下一篇文章使用重构移除丑陋的if else代码(4)继续讲解。
posted @ 2008-08-04 02:54 polygoncell 阅读(2236) | 评论 (4)编辑 收藏

本文紧接使用重构移除丑陋的if else代码(1)

使用Enum替换int常量

这一步比较简单,先创建一个enum类:

package de.jingge.refactoring;

public enum SystemState {

    LOGGEDIN,

    LOGGEDOUT,

    IDLE;

}


然后开始重构SystemManager, 使用SystemState代替SystemManager里的int状态:

   1. 添加 import static de.jingge.refactoring.SystemState.*;
   2. 删除所有的integer常量  
   3. 将变量state的类型改为SystemState.

代码如下:



package de.jingge.refactoring;

import static de.jingge.refactoring.SystemState.*;

public class SystemManager {

    SystemState state;

    
public void login() {
        
// call service#login()
        updateState(LOGGEDIN);
    }
   
    
public void logout() {
        
// call service#logout()
        updateState(LOGGEDOUT);
    }
   
    
public void idle() {
        
// call some other services
        updateState(IDLE);
    }
   
    
public void updateState(SystemState state) {
        
if (state == LOGGEDIN) {
            
// do something after logging in is successful,
            
// for example: show welcome dialog, open the last edit document, etc.
        } else if (state == LOGGEDOUT) {
            
// do something after logging out is successful,
            
// for example: free used resource, dispose GUI components, etc.
        } else if (state == IDLE) {
            
// do something after the user is idle,
            
// for example: save the application state temporarily, lock the application, etc.
        } else {
            
throw new IllegalArgumentException("unknown state");
        }
        
this.state = state;
    }
}

然后重构测试类:

1.    添加import static de.jingge.refactoring.SystemState.*;
2.    删除所有常量前引用的SystemManager.

package de.jingge.refactoring;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
import static de.jingge.refactoring.SystemState.*;


public class SystemManagerTest {

    
private static SystemManager manager;

    @BeforeClass
    
public static void setUpClass() throws Exception {
        manager 
= new SystemManager();
        
// add some service mock objects
    }
   
    @AfterClass
    
public static void tearDownClass() throws Exception {
    }
   
    @Test
    
public void login() {
        manager.login();
        assertEquals(manager.state, LOGGEDIN);
    }
  
    @Test
    
public void logout() {
        manager.logout();
        assertEquals(manager.state, LOGGEDOUT);
    }

    @Test
    
public void idle() {
        manager.idle();
        assertEquals(manager.state, IDLE);
    }
}


运行这个测试类->通过

下一篇文章使用重构移除丑陋的if else代码(3)开始处理if else hell
posted @ 2008-08-04 02:45 polygoncell 阅读(2084) | 评论 (0)编辑 收藏

我们知道因为编程语言的限制,历史遗留下来的系统总是有很多的毛病,不够面向对象,尤其是很多系统滥用if else。我曾经见过一个项目,大家基本上就是写一个方法,然后在里面if else套if esle得嵌套了好几层,难看就不必说了,这种代码根本就没法维护。

今天我就使用从实际项目中提炼出来的例子来讲解一下如何将这类代码变得更加面向对象 - 重构成模式并且添加测试代码,

先来看一个丑陋的类:

package de.jingge.refactoring;

 

public class SystemManager {

 

    
public static final int LOGGEDIN = 0;

    
public static final int LOGGEDOUT = 1;

    
public static final int IDLE = 2;

    
int state;

 

    
public void login() {

        
// call service#login()

        updateState(LOGGEDIN);

    }

   

    
public void logout() {

        
// call service#logout()

        updateState(LOGGEDOUT);

    }

   

    
public void idle() {

        
// call some other services

        updateState(IDLE);

    }

    
public void updateState(int state) {

        
if (state == LOGGEDIN) {

            
// do something after logging in is successful,

            
// for example: show welcome dialog, open the last edit document, etc.

        } 
else if (state == LOGGEDOUT) {

            
// do something after logging out is successful,

            
// for example: free used resource, dispose GUI components, etc.

        } 
else if (state == IDLE) {

            
// do something after the user is idle,

            
// for example: save the application state temporarily, lock the application, etc.

        } 
else {

            
throw new IllegalArgumentException("unknown state");

        }

        
this.state = state;

    }

}


这里我们展示了一个 SystemManager,它负责处理用户在系统中的状态:登入(logged in),登出(logged out),以及空闲(idle)。从代码中可以看到,这个类用了int来定义状态并且因此导致了updatteState()方法里面出现大量if else。从目前看来这些if else是无法避免的,应为这个类需要针对不同的状态作出反应。随着状态的增加,if else的数量也会继续增加。这个解决方案显然很差。

那么怎么样才能让这个类更加地面向对象呢?

在处理面向对象之前,我们首先要编写一个测试类,这也是处理这类历史遗留下来代码所必需做的第一步,只有在测试代码的保护下,我们才能放心大胆地进行重构。

初步的测试代码如下:

package de.jingge.refactoring;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;



public class SystemManagerTest {
    
private static SystemManager manager;

    @BeforeClass
    
public static void setUpClass() throws Exception {
        manager 
= new SystemManager();
        
// add some service mock objects
    }

    @AfterClass
    
public static void tearDownClass() throws Exception {

    }

    @Test
    
public void login() {

        manager.login();

        assertEquals(manager.state, SystemManager.LOGGEDIN);

    }

    @Test
    
public void logout() {

        manager.logout();

        assertEquals(manager.state, SystemManager.LOGGEDOUT);

    }

    @Test
    
public void idle() {

        manager.idle();

        assertEquals(manager.state, SystemManager.IDLE);

    }

}

运行测试代码->通过。

在下一篇文章我们将正式开始重构。地址:使用重构移除丑陋的if else代码(2)
posted @ 2008-08-04 02:36 polygoncell 阅读(2688) | 评论 (3)编辑 收藏