`

使用重构移除丑陋的if else代码

 
阅读更多

我们知道因为编程语言的限制,历史遗留下来的系统总是有很多的毛病,不够面向对象,尤其是很多系统滥用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);

}

运行测试代码->通过。

使用Enum替换int常量

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

package de.jingge.refactoring;

public enum SystemState {

    LOGGEDIN,

    LOGGEDOUT,

    IDLE;

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 {

 &nb;  throw new IllegalArgumentException("unknown state");

    this.state = state;

然后重构测试类:

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

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

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(10, 10, BufferedImage.TYPE_4BYTE_ABGR);

}

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

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

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

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

2. 在updateState()方法中调用SystemStatePerformerFactory3. 在测试代码里面,调用

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匿名类的实例,然后在该方法被调用时直接返回对应的实力。 如何具体实现呢? 用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(

                            this, new 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(10, 10, 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,它拥有状态,并且可以处理更多的逻辑。

分享到:
评论

相关推荐

    java代码重构经验总结

    复杂的条件逻辑:** 简化if-else语句,避免过多的嵌套,提高代码的可读性。 **4. 数据类滥用:** 避免仅用于数据传输的“贫血模型”,引入更多的业务逻辑到实体类中。 **5. 变量命名模糊:** 提高变量命名的清晰...

    重构改善既有代码的设计(PDF)

    6. 用多态代替条件表达式(Replace Conditional with Polymorphism):当需要使用条件表达式(如if-else或switch)来决定具体的行为时,可以考虑使用多态来代替。这样做可以使行为更加灵活,易于扩展和维护。 7. ...

    重构_改善既有代码的设计[高清版]2010.4.pdf

    - **简化条件表达式**:使用更简单的条件语句来替代复杂的if-else结构。 - **移除死代码**:删除不再使用的代码,减少代码库的冗余。 - **优化数据结构**:选择更合适的数据结构来存储数据,提高程序的运行效率。 #...

    重构--改善既有代码的设计

    - **替换条件表达式为策略类**:用策略模式替换复杂的if-else语句,让代码更易于扩展和理解。 - **内联函数**:如果一个函数只在一个地方被调用,可以考虑将其内容直接放入调用处,消除函数调用开销。 - **移除...

    重构——改善既有代码的设计 中文版

    - **示例**: 将一个巨大的`if-else`语句块分解成多个单独的方法,每个方法负责判断一个特定的条件。 6. **提取类(Extract Class)** - **定义**: 从现有的类中分离出新的类。 - **目的**: 降低类的复杂度,提高...

    java重构设计.doc

    - **移除死代码**:删除不再使用的代码,以保持代码库的整洁。 - **简化条件表达式**:复杂的if-else语句可以通过策略模式、状态机等方式简化。 - **引入中间人(中介者)**:减少类之间的耦合,通过引入中介者对象...

    软件工程代码重构说明.pdf

    2. 分离条件:将长方法中的if-else语句拆分为多个独立的方法,提高代码可读性,遵循单一职责原则。 3. 引入参数对象/保留全局对象:减少方法的参数数量,通过参数对象封装相关数据,简化调用。 4. 用符号常量替换...

    java重构1.rar part1

    过于复杂的if-else或switch语句可能隐藏错误,不易理解和调试。可以考虑使用策略模式、状态模式或者引入函数式编程的概念,如使用函数组合或lambda表达式,来简化这些逻辑。 在实际的Java重构过程中,我们需要使用...

    重构Javascript代码示例(重构前后对比)

    在重构后的代码中,使用了更简洁的条件运算符来实现相同的功能: ```javascript input[0].checked=cb.checked; ``` 这不仅使代码更清晰易读,也避免了不必要的逻辑判断。 2. 移除冗余的循环: 在重构前的`function...

    重构-改善既有代码的设计简体中文高清文字pdf

    - **示例**:假设有一段代码使用了一个临时变量`total`来计算几个数字的总和。如果`total`只在一处被使用,则可以直接将计算表达式放入使用它的位置,避免不必要的变量声明。 ### Introduce Assertion(引入断言) ...

    控制代码ppppppppp

    1. **代码控制结构**:在任何编程语言中,控制代码都涉及流程控制语句,如条件语句(if-else)、循环(for, while)、开关语句(switch)等。这些结构允许程序员根据特定条件改变程序的执行顺序。 2. **代码删除**...

    fowler-refactoring:马丁·福勒(Martin Fowler)的《改进现有代码的设计》中的示例中的重构实践。 将作为实用测试提供给我的软件工程课

    2. **简化条件表达式**:使用策略模式、多态性或者更简洁的逻辑结构替换复杂的if-else语句,使代码更易于理解和维护。 3. **提炼函数**:将过长的函数拆分为小的、具有单一职责的函数,使函数更专注,增强可读性。 ...

    代码审查表 英文版 通用 JAVA/C/C++

    - **审查关注点**:确保IF-ELSEIF结构中的一般情况优先得到处理。 **3. 是否考虑了所有可能的情况,包括ELSE语句或DEFAULT语句?** - **要点说明**:充分覆盖所有可能的情况可以防止意外行为。 - **审查关注点**:...

    VBNet-C#常用的代码-程序技巧

    3. **条件语句**:`If...Then...Else`用于基于条件执行不同代码,`Select Case`提供了多分支选择。 4. **字符串操作**:`String.Format`用于格式化字符串,`StringBuilder`用于高效地构建和修改字符串。 5. **数组和...

    敏捷软件开发的必要技巧

    - **重构if-then-else-if链**:提取条件分支为独立方法或使用策略模式等。 - **拆分长方法**:将长方法分解为多个较小的方法,每个方法专注于单一职责。 **4. 示例解析** - 分析一段包含上述问题的代码。 - ...

    refactoring-workshop-slides-sep-2015:重构研讨会幻灯片

    3. **使用策略模式替换复杂的条件语句**:将复杂的if-else逻辑替换为可配置的策略,使代码更易于理解和测试。 4. **使用函数组合**:通过组合小型纯函数,可以构建出功能强大的组件,同时保持代码简洁。 5. **移除...

Global site tag (gtag.js) - Google Analytics