重构实践:体验interface的威力(二)

简介:

    前一篇博客 写了在这次重构中,如何找到关键的概念,并将它提取成为接口。这样,重构的方案基本上就已经被确定了。这篇博客主要说一些有意义的细节:

抽象实现

    提取接口模型后,各“指标”类已经呈现出一种正确的逻辑关系。那么,现在更重要的就是重构上文中提到的“普通计算过程”。由于计算过程依然有很多种,并且有通用的抽象部分。再加上接口模型已经定义出了大量重要的业务属性,所以我在这里使用约束的泛型抽象类来定义最上层基类。这里捡重点的几个计算类,组织类图如下:

image

图1 计算过程的类的结构


重点细节

    最IndicatorCalcBase<TModel>作为最上层基类,定义如下:

public abstract class IndicatorCalcBase<TModel> where TModel : BusinessBaseIProjectIndicator {……}

    在这里使用了IProjectIndicator来约束TModel泛型,使得IndicatorCalcBase类的计算能力十分强大。这是因为这个类只处理抽象的“指标”概念IProjectIndicator,而这个接口中定义了计算业务所需要使用的95%的属性、方法。所以原来的那些重复的计算类的代码,基本上都已经全部在这个类实现了。该类使用模板方法定义了一个固定的处理过程:

/// <summary>
/// 计算指标,不需要返回指标。
/// </summary>
public void Caculate()
{
    this.PreCollect();

    this.Collect();

    this.Save();

    this.CalculateEnd();
}
    看上去,感觉是不是有点象ASP.NET的WebForm的PreLoad、Load、Unload啊?:) 这些方法在基类都已经默认实现,并标记为虚方法,这样子类就可以重写它们以添加新的功能,如下:
 
图2 IndicatorCalcBase<TModel>类的设计
 
    这里把部分方法设置为protected,为子类开放出必要的使用权限,以满足特定的计算需求。而它无法实现,必须延迟到子类实现的抽象方法,就被提取为abstract方法。这个类的设计,成了整个重构的核心,这里需要深刻理解原有代码并有一定的重构经验和设计经验。
    另外,这个类中的计算过程为排序所构建的内部类PBSIdComparer,实现了多种IComparer接口,以满足对多种不同的对象进行同一功能(根据PBSId)的排序。这里也把它的设计贴一下,我觉得这里比较好的设计是Convert方法的转换模式、私有的Compare方法的API思想(虽然这并不是本次重构的重点,不过暂时在这做一记录。 :))。见图:
 
 
图3 统一实现的比较类
 
    接下来,是计算所有量指标的类QuantityCalcBase<TModel>:
 
 
图4 量指标计算基类。
 
    注意到,这里面的类的定义中,已经把TModel约束为IProjectQuantityIndicator,使得计算过程可以对量指标进行特殊处理。
    有了以上两个基类,其它子类的实现变得十分简单,几个量指标类都实现如下:
public class QuantityCalc_Entity : QuantityCalcBase<ProjectIndicatorEntity>
{
    public QuantityCalc_Entity(Project project, BudgetPhaseType budgetType, PBSs pbss)
        : base(project, budgetType, pbss) { }

    protected override string GetPreCollectProcedure()
    {
        return "dbo.CreateBudgetEntityQuantityQuery";
    }
    protected override ExtendedBindingList<ProjectIndicatorEntity> CreateAddList()
    {
        return ProjectIndicatorEntityList.GetNew();
    }
    protected override IList<ProjectIndicatorEntity> GetListByProject()
    {
        var models = ProjectIndicatorEntityList.GetListByProjectId(this.Project.Id, this.BudgetType);
        return models;
    }
}


SQL优化

    我曾经尝试不进行代码重构,而直接使用profiler寻找性能点来对原来的代码进行优化。只可惜损耗和结构也息息相关,结果那次优化只给性能带来较小的提升。现在重构过后,类设计得比较通畅了,那么整个运算过程中性能的损耗点就更容易被找到,做起优化来也更得心应手。比如,我可以把一些分散的没必要的多次查询,合并在一起进行一次查询……

    举个具体的例子,原代码中有个指标的计算过程中,会递归+循环去调用数据库中一个冗长的存储过程,来计算每个树状结点的指标值,那个存储过程我看着就头大了,大概150行吧 :(。而现在,在子类中,实现父类方法中,我只写了一个SQL语句,不但不使用存储过程,而且也不再需要多次访问数据库。这样的效果就是,原来的时间是14S,现在只要:1S。代码如下:

        protected override IList<ProjectCostIndicator> GetListByProject()
        {
            var models = ProjectCostIndicatorList.GetListByProjectId(this.Project.Id, this.BudgetType);
            this.PrepareAllModels(models);
            return models;
        }
        private void PrepareAllModels(IList<ProjectCostIndicator> models)
        {
            string sql = this.QueryTotalCostSql();
            //执行sql,并对模型进行赋值。
         }
        private string QueryTotalCostSql()
        {
            return string.Format(@"
SELECT 
ISNULL(SUM(CBFBFXBQItem.Amount), 0)
+ ISNULL(SUM(CBFGQBQItem.Amount), 0)
+ ISNULL(SUM(CBMeasureItem.Amount), 0)
+ ISNULL(SUM(CBOtherItem.Amount), 0)
AS TotalCost, Q.PBSId
FROM BudgetgetCostItemQuery as Q
LEFT OUTER JOIN CBFBFXBQItem ON CBFBFXBQItem.ID=Q.ItemId
LEFT OUTER JOIN CBFGQBQItem ON CBFGQBQItem.ID=Q.ItemId
LEFT OUTER JOIN CBMeasureItem ON CBMeasureItem.ID=Q.ItemId AND CBMeasureItem.Name <> ''
LEFT OUTER JOIN CBOtherItem ON CBOtherItem.ID=Q.ItemId AND CBOtherItem.Name <> '' AND (CBOtherItem.IsNotInPBS=0 OR CBOtherItem.IsNotInPBS is null) 
WHERE BudgetType = {1} AND ProjectId = '{0}'
GROUP BY Q.PBSId
HAVING NOT (Q.PBSId IS NULL)
", this.Project.Id.ToString(), (int)this.BudgetType);
        }


 

总结

    目前的整个普通计算过程,耗时只需要5秒种。其实还有很多地方我并没有对其进行优化,不过相对于原来的2分钟,已经是完全能让客户接受了。所以暂时就优化至此。而20分钟的复杂计算过程,估计也能够降低很多,不过暂时客户并不使用,且项目的其它需求也比较紧,所以只有先不管了。:)

    接口能够抽象的、逻辑的的表示现实世界的概念。合理使用它,会给我们的编程带来很多好处和一些意想不到的效果。

目录
相关文章
|
6月前
|
设计模式 算法 Java
设计模式第十五讲:重构 - 改善既有代码的设计(下)
设计模式第十五讲:重构 - 改善既有代码的设计
238 0
|
6月前
|
存储 前端开发 JavaScript
潮玩宇宙大逃杀无聊猿卷轴模式系统开发详细规则丨步骤需求丨方案项目丨技术架构丨源码功能
确定游戏类型和规则:明确无聊猿卷轴模式游戏类型和游戏规则,包括敌人类型、地图设计、任务类型、战斗机制等。
|
5月前
|
存储 NoSQL MongoDB
变形记---抽象接口,屎山烂代码如何改造成优质漂亮的代码
在游戏服务器开发过程中,我们经常会在动手码代码之前好好的设计一番,如何设计类,如何设计接口,如何调用,有没有什么隐患,在这些问题考虑评审可以Cover现阶段的需求的情况下再动手。不过,对于一些初级,甚至中高级开发者,仍然不可避免的进入了一个死胡同,缺少设计,屎山代码堆积,越堆越臭,越写越烂,直到很难维护必须要重新改造。最近我给M部门面试服务器主程序开发的职位,我不问开发语言的语法,我只问他们的架构设计经验,我发现相当一部分5-12年“本应该有足够开发经验。
|
5月前
|
安全 测试技术 持续交付
软件开发、测试常用知识点总结与拓展
脚本(Script): 定义:脚本是一系列计算机指令的文本文件,通常用于自动化任务或执行特定的操作。它可以包括编程语言的代码或一系列命令。 用途:脚本用于自动化重复性任务、批处理作业、配置系统设置等。例如,Shell脚本、Python脚本和JavaScript脚本用于执行各种任务。 图解:通常,脚本的图示是一张文本文件图标,包括文件名和脚本内容的代码段。 队列(Queue): 定义:队列是一种数据结构,遵循FIFO(先进先出)原则,其中最早加入队列的元素最早被移除。队列通常用于管理和协调多个任务或进程之间的顺序执行。 用途:队列在计算机科学中用于任务调度、消息传递、数据缓冲等。例如,操作系统使
201 1
|
6月前
|
设计模式 Java 测试技术
设计模式第十五讲:重构 - 改善既有代码的设计(上)
设计模式第十五讲:重构 - 改善既有代码的设计
256 0
|
8月前
|
开发框架 前端开发 安全
ETH链游开发的关键步骤和技术讲解[附源码逻辑示例]
ETH链游开发的关键步骤和技术讲解[附源码逻辑示例]
|
9月前
|
算法
简单几行代码背后的重大意义
简单几行代码背后的重大意义
|
11月前
|
算法 安全 Java
重构--必经之路?
简要讲述代码重构是什么?为什么要进行代码重构?如何写出干净整洁的代码?
|
11月前
|
缓存 微服务
聊聊编程学习方法,企业级开发到底在做什么,难不难?
聊聊编程学习方法,企业级开发到底在做什么,难不难?
|
设计模式 程序员 开发者
重构·改善既有代码的设计.01之入门基础
近期在看Martin Fowler著作的《重构.改善既有代码的设计》这本书,这是一本经典著作。书本封面誉为软件开发的不朽经典。书中从一个简单的案例揭示了重构的过程以及最佳实践。同时给出了重构原则,何时重构,以及重构的手法。用来改善既有代码的设计,提升软件的可维护性。
579 1
重构·改善既有代码的设计.01之入门基础