多分支相似逻辑重构设计

工作中经常碰到很多分支的处理逻辑,但是每个分支的处理逻辑相似,只是具体某些字段或者说比较逻辑等有不同。如果不思考直接写代码很容易出现一大堆的if else出现,这种代码难于维护、冗余特别严重。这个时候其实我们需要对于结构进行重新设计,提高扩展性和可维护性。

拿个实际的例子,最近在弄的某促销下系统,对于一个展示情况,在活动类型=1的时候要xxx,活动类型=2要xxx,这个如果用ifelse很容易实现,但是里面很容易出现特别多的冗余代码,拆出来不好拆,不拆又特别糟心。比如下面的代码:

//1.普通 ifelse方式
    public static Object handleByIfElse(int id) {
        if (id == 1) {
            return "aaa";
        }
        if (id == 2) {
            return 2;
        }
        return null;
    }

我们实际情况中肯定比这个要复杂的多,return一般也会有大篇幅的代码段存在,代码的可读性非常差。

于是我们可以设计一下处理逻辑,在确定每个分支下面的处理逻辑大体相同后,我们可以创建一个抽象类,抽象类中有一个抽象方法供子类覆盖,同时还有一个protected的方法是所有子类公用的,这些protected的方法就是ifelse分支中重复的代码。如此,结构变成:

public abstract class Handler {

    abstract Object handle();

    protected HandleConfigurations getConfiguration() {
        return new HandleConfigurations();
    }

//    @PostConstruct
    protected void regist() {
        HandlerEntrance.regist(this);
    }
}

代码非常简单,拿个postconstruct的内容后面解释,其余的部分其实就是一个protected方法供所有子类共享,一个abstract方法供所有子类覆盖。

public class Handler1 extends Handler {
    @Override
    Object handle() {
        HandleConfigurations handleConfigurations = getConfiguration();
        return handleConfigurations.toString();
    }
}
public class Handler2 extends Handler {
    @Override
    Object handle() {
        return null;
    }
}

如此,我们把所有的分支全部重构称为一个一个的子类,需要变动哪一个直接找到对应的子类进行修改。有新的分支,就可以直接新增一个子类,这样子代码可读性提高了很多。

但是我们还有一件需要做的事情就是需要让请求具体分发到具体的子类上面,我们可以简单采用switch来进行:

//2.通过switch方式
    public static Object handleBySwitch(int id) {
        switch (id) {
            case 1:
                return new Handler1().handle();
            case 2:
                return new Handler2().handle();
            default:
                return null;
        }
    }

我们会发现,如果我们这样子做,switch会限制只有基本类型和枚举才可以,如果其他数据结构我们还需要用ifelse,而且代码也相当不优雅。所以我们采用map来进行:

 //3.通过初始化好的map进行映射
    private static Map<Integer, Handler> handlerMap = Maps.newHashMap();

    static {
        handlerMap.put(1, new Handler1());
        handlerMap.put(2, new Handler2());
    }

    public static Object handleByMap(int id) {
        return handlerMap.get(id).handle();
    }

我们初始化好,传入参数与具体处理的子类的对应关系,在入口拿到参数后获得子类的实例后直接进行处理。但是这样子,我们不得不维护一个map来做对应关系,或者在xml里面进行配置,这样子如果map中的值特别多,也会变成一大坨,相当难看。

所以我们需要想一个办法来避免这种情况,反射可以做到我们想要的:

//4.通过反射
    public static Object handleByReflection(int id) {
        try {
            Handler handler = (Handler) Class.forName("com.mt.design1.Handler" + id).newInstance();
            return handler.handle();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

通过反射,我们可以直接根据传入的参数直接生成到具体的类名字,然后拿到这个类的实例。但是这个做法还有一个比较恶心的地方就是我们需要写死类的绝对路径,如果我们这么做,代码发生重构,会出现classnotfound的情况,而且这种传入办法效率并不高,当然我们可以在初始化后放入map中,然后下次冲map的缓存中获得类的实例就可以。然而这么做还是觉得不太舒服。

我们回到第三步的内容,如果我们可以做到map由子类自动去注册,这样子我们似乎就可以在传入的时候不做考虑,也不用专门维护对应关系,有新逻辑只要专注于把具体处理的子类写完就可以了。于是我们可以这样子做:

1、定义一个注解,用于表示该子类的key属性:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IdAnno {
    int id();
}

我们将该注解注入到具体的子类中:

@IdAnno(id = 1)
public class Handler1 extends Handler {
    @Override
    Object handle() {
        HandleConfigurations handleConfigurations = getConfiguration();
        return handleConfigurations.toString();
    }
}

这种做法可以让我们在一个子类中获得了所有需要的内容,下一步我们要考虑的就是将这个实例在系统启动的阶段就加载到jvm中:

//5.通过注解初始化和刷新map
    private static Map<Integer, Handler> handlerMapByAnnotation = Maps.newHashMap();

    public static void regist(Handler handler) {
        if (handler.getClass().isAnnotationPresent(IdAnno.class)) {
            handlerMapByAnnotation.put(handler.getClass().getAnnotation(IdAnno.class).id(), handler);
        }
    }

刚才抽象类中的@PostConstrct注解的方法我们就了解到,如果子类在初始化的时候,就会进行注册操作,注册时候会将类的实例放入缓存的map中(其实和spring的beanfactory类似),用的时候直接获得就可以了:

//5.通过注解初始化和刷新map
    private static Map<Integer, Handler> handlerMapByAnnotation = Maps.newHashMap();

    public static void regist(Handler handler) {
        if (handler.getClass().isAnnotationPresent(IdAnno.class)) {
            handlerMapByAnnotation.put(handler.getClass().getAnnotation(IdAnno.class).id(), handler);
        }
    }

    public static Object handleByAnnotation(int id) {
        return handlerMapByAnnotation.get(id).handle();
    }

最后,我们考虑的就是在系统的启动阶段就得把子类的初始化的动作给完成,如果你使用了spring框架,那可以在子类上面加上@Service @Component等注解就可以:

@Service
@IdAnno(id = 1)
public class Handler1 extends Handler {
    @Override
    Object handle() {
        HandleConfigurations handleConfigurations = getConfiguration();
        return handleConfigurations.toString();
    }
}

如果并没有使用spring框架,那这个里面可以自己写一个读取某文件夹下面的类然后进行初始化的util执行,但是这种做法就得不偿失,还是可以用3或者4来进行了。

Ok,探索到此为止。

时间: 2024-10-12 17:17:46

多分支相似逻辑重构设计的相关文章

逻辑数据库设计 - 单纯的树(递归关系数据)(转)

逻辑数据库设计 - 单纯的树(递归关系数据) 相信有过开发经验的朋友都曾碰到过这样一个需求.假设你正在为一个新闻网站开发一个评论功能,读者可以评论原文甚至相互回复. 这个需求并不简单,相互回复会导致无限多的分支,无限多的祖先-后代关系.这是一种典型的递归关系数据. 对于这个问题,以下给出几个解决方案,各位客观可斟酌后选择. 一.邻接表:依赖父节点 邻接表的方案如下(仅仅说明问题): CREATE TABLE Comments( CommentId int PK, ParentId int, --

[实战]MVC5+EF6+MySql企业网盘实战(15)——逻辑重构2

写在前面 上篇文章修改文件上传的逻辑,这篇修改下文件下载的逻辑. 系列文章 [EF]vs15+ef6+mysql code first方式 [实战]MVC5+EF6+MySql企业网盘实战(1) [实战]MVC5+EF6+MySql企业网盘实战(2)——用户注册 [实战]MVC5+EF6+MySql企业网盘实战(3)——验证码 [实战]MVC5+EF6+MySql企业网盘实战(4)——上传头像 [Bootstrap]modal弹出框 [实战]MVC5+EF6+MySql企业网盘实战(5)——登录

游戏开发(三)——WIN32 黑白棋(一)——棋局逻辑的设计

今天以黑白棋为例,开始给一个win32的小游戏设计, 这里打算分3部分介绍 1.棋盘,棋局的现实 2.玩家.AI的现实(且听下回分解) 3.游戏画面的现实(且听下下回分解) 其中第一部分为黑白棋游戏的主要逻辑: 1.棋盘,以及棋盘上的棋子的存储形式.这里用到了位图. 2.是否可以落子的判断(黑白棋是只有你落子的位置,在横竖斜八个方向中任意一个方向,能吃掉对方的子,你才可以落在该位置,八个方向都吃不掉对方子的位置是不能下的),以及吃子的逻辑(吃子的逻辑同样是八个方向,两个己方棋子之间夹住的对方棋子

逻辑数据库设计 - 单纯的树(递归关系数据)

相信有过开发经验的朋友都曾碰到过这样一个需求.假设你正在为一个新闻网站开发一个评论功能,读者可以评论原文甚至相互回复. 这个需求并不简单,相互回复会导致无限多的分支,无限多的祖先-后代关系.这是一种典型的递归关系数据. 对于这个问题,以下给出几个解决方案,各位客观可斟酌后选择. 一.邻接表:依赖父节点 邻接表的方案如下(仅仅说明问题): CREATE TABLE Comments( CommentId int PK, ParentId int, --记录父节点 ArticleId int, Co

网络框架重构设计

一.背景 网络管理层是各上层业务都要用到的层级,为提供更高效率.更高质量的服务,需对网络服务层进行重构. 二.重构目标 1.提供连接管理 在App整个运行过程中,始终向上层业务提供两条有效的长连接(云端连接和路由器连接),并支持在网络断开.心跳失败后的重连机制. 2.自动登陆(已实现) 在长连接重建后, 自动登陆服务器. 3.自动同步(已实现) 在长连接重建后.并自动登陆服务器后, 自动同步本地信息到云平台/路由器. 4.网络服务代理层RemoteProxy RemoteProxy作为对上层业务

谈五层逻辑构架设计

术语表 逻辑:Logical 物理:Physical 构架:Architecture 框架:Framework 表现层:Presentation 用户界面:User Interface 业务逻辑:Business Logic 数据访问:Data Access 数据和存储管理:Data and Storage Management 图形用户接口:Graphical User Interface 胖客户端:Richer Client-side 智能客户端:Smart Client 应用程序:Appl

逻辑数据库设计 - 需要ID(谈主键Id)

本文的目标就是要确认那些使用了主键,却混淆了主键的本质而造成的一种反模式. 一.确立主键规范 每个了解数据库设计的人都知道,主键对于一张表来说是一个很重要,甚至必需的部分.这确实是事实,主键是好的数据库设计的一部分.主键是数据库确保数据行在整张表唯一性的保障.它是定位到一条记录并且确保不会重复存储的逻辑机制.主键也同时可以被外键引用来建立表与表之间的关系. 难点是选择那一列作为主键.大多数表中的每个属性值都有可能被很多行使用.例如姓名,电子邮件地址等等都不能保证不会重复. 在这样的表中,需要引入

逻辑数据库设计 - 多态关联

多态关联 先说明什么是多态关联. 假设我们有一张地址表,其中的地址可能是对于User中的,也可能是对于Orders中的. 以上,只是举个例子,实际的例子还有很多,比如我们要设计一个内容管理系统(CMS),我们的CMS有一个文章表,一个软件表.还要求支持评论,那么我们的评论表的Id是引用文章表还是引用软件表呢? 对于以上例子的缺点,貌似书本上有故意为此多态关联的模式走软的嫌疑.缺点不说了,主要是查询麻烦,其次不能够支持外键约束. 解决方案 交叉表 对于这种需要外键引用为多个表的情况,可以建立一张交

逻辑数据库设计 - 多列属性(多列转行)

假设有一个要开发一个试题系统,全是不定项选择题.一道题可能有2,3,4...个答案,数据应如何设计呢?本处旨在说明问题所在,例如同类问题还有存储电话,一个人可能有多个号码等等. 一.存储多值属性 反模式:创建多个列. 我们知道每列最好只存储一个值,因此先看如下设计: CREATE TABLE Question( QuestionId int PK, QuestionBody nvarchar(500), Answer1 nvarchar(500), Answer2 nvarchar(500),