一次重构小记

  • 起因

有很多毫无关系又或者有亲戚关系的对象,对这些对象有很多相同的处理(比如在界面上显示某些和修改属性)。你并不确定这些对象来自何方又将去往何处,所需要做的是对它们做某些共同的修改,而根据对象的不同又需要各自做一些不同处理。比如有人(person)和树(tree)两个对象,共同的操作是要对他们的名称和数量做修改(这里假设名称和数量对于人和树有不同意义,比如数量对人说是身高而对树来说是直径)。于是乎前辈定义了类似于下面的基类和子类(有新旧对象是为了做Undo和Redo用)。

 /// <summary>
    /// 基类
    /// </summary>
    public class ViewBase
    {
        /// <summary>
        /// 名称
        /// </summary>
        public virtual string Name { get; set; }

        /// <summary>
        /// 数量
        /// </summary>
        public virtual double Number { get; set; }

        /// <summary>
        /// 旧对象
        /// </summary>
        public object ObjOld { get; set; }

        /// <summary>
        /// 新对象
        /// </summary>
        public object ObjNew { get; set; }

        /// <summary>
        /// 改变标志
        /// </summary>
        public virtual bool IsChanged
        {
            get
            {
                return !ObjOld.Equals(ObjNew);
            }
        }
    }
/// <summary>
    /// 人子类
    /// </summary>
    public class PersonView : ViewBase
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        public PersonView(Person person)
        {
            ObjOld = CloneHelper.DeepClone(person);
            ObjNew = person;
        }

        /// <summary>
        /// 相应的对象
        /// </summary>
        public new Person ObjNew
        {
            get { return objNew; }
            set { base.ObjNew = objNew = value; }
        }
        private Person objNew;

        /// <summary>
        /// 原始对象
        /// </summary>
        public new Person ObjOld
        {
            get { return objOld; }
            set { base.ObjOld = objOld = value; }
        }
        private Person objOld;

        /// <summary>
        /// 名称
        /// </summary>
        public override string Name
        {
            get
            {
                return ObjNew.Name;
            }
            set
            {
                ObjNew.Name = value;
            }
        }

        /// <summary>
        /// 身高
        /// </summary>
        public override double Number
        {
            get
            {
                return ObjNew.Height;
            }
            set
            {
                ObjNew.Height = value;
            }
        }

        public override bool IsChanged
        {
            get
            {
                //检查是否变化的代码

                return base.IsChanged;
            }
        }

        //其他处理
        //..........
    }
 public class TreeView : ViewBase
    {
        public TreeView(Tree tree)
        {
            ObjOld = CloneHelper.DeepClone(tree);
            ObjNew = tree;
        }

        /// <summary>
        /// 相应的对象
        /// </summary>
        public new Tree ObjNew
        {
            get { return objNew; }
            set { base.ObjNew = objNew = value; }
        }
        private Tree objNew;

        /// <summary>
        /// 原始对象
        /// </summary>
        public new Tree ObjOld
        {
            get { return objOld; }
            set { base.ObjOld = objOld = value; }
        }
        private Tree objOld;

        /// <summary>
        /// 品种
        /// </summary>
        public override string Name
        {
            get
            {
                return ObjNew.Type;
            }
            set
            {
                ObjNew.Type = value;
            }
        }

        /// <summary>
        /// 直径
        /// </summary>
        public override double Number
        {
            get
            {
                return ObjNew.Diameter;
            }
            set
            {
                ObjNew.Diameter = value;
            }
        }

        public override bool IsChanged
        {
            get
            {
                //检查是否变化的代码

                return base.IsChanged;
            }
        }

        //其他处理
        //..........
    }

看到使用new关键字我就有种蛋蛋的忧伤。对于new关键字的使用我一直持比较谨慎的态度,因为我感觉应该用到它的场景并不多。就像上面这种情况,基类和子类应该拥有相同的Obj实例,虽然子类在给ObjNew和ObjOld赋值的时候同样对基类的对象赋值,但肯定又有不同同一实例的风险,而且重复代码。在这里用new关键重写基类属性只是为了在使用特定子类的对象的时候不需要做类型转换,但却付出了代码重复和容易产生bug的代价。

  • 题目话

在刚开始结接手项目的时候,在别的部分程序代码中,前辈在类似场景了也做了同样的设计。当时我就在想重写那部分代码,但是时间紧任务重,加之那部分代码太过庞大复杂(基类有个几十个属性和方法,并且还有几十个子类),有牵一发而扯着蛋的危险,实在是不敢动手。今天在修改另外一段程序的时候又看到了同样设计,实在是有点忍无可忍不想再忍,加上这段程序里面的类比较简单,于是决定重写。

  •  修改

对于这种场景,很容易想到使用泛型基类。于是我先把基类和子类做了如下修改,去掉了子类的重复代码,而且保证基类的类型安全:

/// <summary>
    /// 基类
    /// </summary>
    public class ViewBase<T>
    {
        /// <summary>
        /// 名称
        /// </summary>
        public virtual string Name { get; set; }

        /// <summary>
        /// 数量
        /// </summary>
        public virtual double Number { get; set; }

        /// <summary>
        /// 旧对象
        /// </summary>
        public T ObjOld { get; set; }

        /// <summary>
        /// 新对象
        /// </summary>
        public T ObjNew { get; set; }

        /// <summary>
        /// 改变标志
        /// </summary>
        public virtual bool IsChanged
        {
            get
            {
                return !ObjOld.Equals(ObjNew);
            }
        }
    }

 /// <summary>
    /// 人子类
    /// </summary>
    public class PersonView : ViewBase<Person>
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="hasi"></param>
        /// <param name="cadType"></param>
        public PersonView(Person person)
        {
            ObjOld = CloneHelper.DeepClone(person);
            ObjNew = person;
        }
        /// <summary>
        /// 名称
        /// </summary>
        public override string Name
        {
            get
            {
                return ObjNew.Name;
            }
            set
            {
                ObjNew.Name = value;
            }
        }

        /// <summary>
        /// 身高
        /// </summary>
        public override double Number
        {
            get
            {
                return ObjNew.Height;
            }
            set
            {
                ObjNew.Height = value;
            }
        }

        public override bool IsChanged
        {
            get
            {
                //检查是否变化的代码

                return base.IsChanged;
            }
        }

        //其他处理
        //..........
    }

 public class TreeView : ViewBase<Tree>
    {
        public TreeView(Tree tree)
        {
            ObjOld = CloneHelper.DeepClone(tree);
            ObjNew = tree;
        }

        ///// <summary>
        ///// 相应的对象
        ///// </summary>
        //public new Tree ObjNew
        //{
        //    get { return objNew; }
        //    set { base.ObjNew = objNew = value; }
        //}
        //private Tree objNew;

        ///// <summary>
        ///// 原始对象
        ///// </summary>
        //public new Tree ObjOld
        //{
        //    get { return objOld; }
        //    set { base.ObjOld = objOld = value; }
        //}
        //private Tree objOld;

        /// <summary>
        /// 品种
        /// </summary>
        public override string Name
        {
            get
            {
                return ObjNew.Type;
            }
            set
            {
                ObjNew.Type = value;
            }
        }

        /// <summary>
        /// 直径
        /// </summary>
        public override double Number
        {
            get
            {
                return ObjNew.Diameter;
            }
            set
            {
                ObjNew.Diameter = value;
            }
        }

        public override bool IsChanged
        {
            get
            {
                //检查是否变化的代码

                return base.IsChanged;
            }
        }

        //其他处理
        //..........
    }

这貌似很美好,类型安全,代码减少,降低bug风险。 但是问题在于C#是强类型语言,必须在使用泛型类的时候为其指定特定的类型,因此失去了作为统一接口的能力。

如何保证在使用的泛型基类的情况下,又可以使子类有统一的处理接口?

答案自然是使用interface,于是我添加一个IView的interface,让ViewBase<T>实现IView,代码如下:

  public interface IView
    {
        /// <summary>
        /// 名称
        /// </summary>
        string Name { get; set; }

        /// <summary>
        /// 数量
        /// </summary>
        double Number { get; set; }

        /// <summary>
        /// 旧对象
        /// </summary>
        object ObjOld { get; set; }

        /// <summary>
        /// 新对象
        /// </summary>
        object ObjNew { get; set; }

        /// <summary>
        /// 改变标志
        /// </summary>
        bool IsChanged { get; }
    }

ViewBase<T>做相应修改

 /// <summary>
    /// 基类
    /// </summary>
    public class ViewBase<T>: IView
    {
        /// <summary>
        /// 名称
        /// </summary>
        public virtual string Name { get; set; }

        /// <summary>
        /// 数量
        /// </summary>
        public virtual double Number { get; set; }

        /// <summary>
        /// 旧对象
        /// </summary>
        public T ObjOld { get; set; }

        /// <summary>
        /// 新对象
        /// </summary>
        public T ObjNew { get; set; }

        /// <summary>
        /// 改变标志
        /// </summary>
        public virtual bool IsChanged
        {
            get
            {
                return !ObjOld.Equals(ObjNew);
            }
        }

        object IView.ObjOld
        {
            get
            {
                return this.ObjOld;
            }
            set
            {
                this.ObjOld = (T)value;
            }
        }

        object IView.ObjNew
        {
            get
            {
                return this.ObjNew;
            }
            set
            {
                this.ObjNew = (T)value;
            }
        }
    }

这里需要注意的是接口要显示实现,关于显示接口实现和隐式接口实现的区别大家可以查看《CLR via C#》。

嗯,这下貌似完美了,类型安全,代码减少,降低bug风险,统一接口。

但但是,人生总是有那么多的但是。。。。。

这时候来了一个Student的类,继承自Preson,PersonView所有的处理它都有,而且还有一些自己的处理, 学生总是喜欢搅和。。。

很自然定义StudentView继承自PersonView,这时问题又出来了,StudentView里面Student对象又要如何来实现呢?

嗯,要不把PersonView在泛型化吧,于是乎,我又修改了PersonView的代码

 /// <summary>
    /// 人子类
    /// </summary>
    public class PersonView<T> : ViewBase<Person> where T : Person
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="hasi"></param>
        /// <param name="cadType"></param>
        public PersonView(Person person)
        {
            ObjOld = CloneHelper.DeepClone(person);
            ObjNew = person;
        }

        ///// <summary>
        ///// 相应的对象
        ///// </summary>
        //public new Person ObjNew
        //{
        //    get { return objNew; }
        //    set { base.ObjNew = objNew = value; }
        //}
        //private Person objNew;

        ///// <summary>
        ///// 原始对象
        ///// </summary>
        //public new Person ObjOld
        //{
        //    get { return objOld; }
        //    set { base.ObjOld = objOld = value; }
        //}
        //private Person objOld;

        /// <summary>
        /// 名称
        /// </summary>
        public override string Name
        {
            get
            {
                return ObjNew.Name;
            }
            set
            {
                ObjNew.Name = value;
            }
        }

        /// <summary>
        /// 身高
        /// </summary>
        public override double Number
        {
            get
            {
                return ObjNew.Height;
            }
            set
            {
                ObjNew.Height = value;
            }
        }

        public override bool IsChanged
        {
            get
            {
                //检查是否变化的代码

                return base.IsChanged;
            }
        }

        //其他处理
        //..........
    }

public class StudentView : PersonView<Student>
    {
        public StudentView(Student student)
            :base(student)
        {

        }

        //其他处理
        //......
    }

MD,问题重现啊,Person有很多子类和子类的子类。。。。。。 PersonView<T>又是失去了接口性。嗯蛋疼,不知如何是好。。。。想来想去没想到好办法,我饶了一圈回到起点。

结果,PersonView还是采用非泛型,PersonView的子类依旧使用了最开始的写法。

不知道各位大侠有啥好办法,还望告知,不胜感激。

时间: 2024-08-07 14:54:48

一次重构小记的相关文章

退役前的小记

前言 再过几天就省选了-- 估计以我的实力,,,考完就退役了. 所以趁没退役之前来写篇小记吧. 最近几天的计划 大概就是复习一下之前学的算法之类的,可能会简单的了解一下计算几何和群论,不至于遇到相关题目就完全懵逼就可以了. 代码应该不会打太多. 考试中的计划 后天下午就住到宾馆里面去了,在宾馆里面的自习时间应该会大部分花在看以前的易错点,看一些套路/总结,调整心态之类的上面.估计不太会码代码. 后续可能被咕的计划 退役之后大概就要狂搞文化了,博客什么的肯定是管不了了,所以以下flag均建立在毕业

关于重构工作的一点思考

最近两周一直忙着和重构相关的事情,本文将简要概述从开始制定重构方案,到具体执行的过程中遇到的问题,以及对重构的一点理性思考. 起因: 本系统是2015年11月开始建设,当时为了快速投入使用,大量的烂代码,后期一直保持快速前进,没有进行过实质性的重构. 具体表现: ● 分层不清,sql哪都有,dao有.service也有,就差controller没写了.同样dao也包含业务逻辑. ● sql用的是spring jdbc,并没有使用mybatis,导致sql写起来有些复杂,封装不够基本都是原始sql

SpringBoot - 二零一七0421小记

一.SpringBoot使用起来比起SpringMVC更便捷,在注解上的小变化,我记录了下面几个: @Controller + @ResponseBody = SpringMVC中用@RestController来代替前面两个注解,通过这个注解,可以将所有的前端http请求放入SpringBoot的Controller容器中,并返回json格式的数据给前端 @RequestMapping(value={"/hello","/hi"},method=RequestMe

我们需要重构吗

当我开始写这篇文章的时候 ,我的思想还处于斗争阶段,多年来我也在程序开发的第一线,经历了很多项目,也编写了很多代码,但是从心里说我几乎没有用到重构这一方法,但最近看一本书名字叫<重构 改善既有代码的设计> ,它让我感觉我这些年的迷惑终于找到了明灯. 我以前认为代码只要实现了就OK了,不用再去动他了,不管当时实现时绕了多少个圈,用了多少冗余代码.有时候想想也是既然已经实现了,干嘛还要化那么多精力去装饰那些代码,而且保证系统功能正常运行和项目按时完成是第一要务,剩下的一切都变得不在重要了.当我开始

重构二叉树

重构二叉树 这是剑指offer中关于二叉树重构的一道题.题目原型为: 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树.假设输入的前序遍历和中序遍历的结果中都不含重复的数字.例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回. 一.二叉树的数据结构 做题之前,我们先熟悉下二叉树的数据结构.其一,定义:二叉树是一个连通的无环图,并且每一个顶点的度不大于3.有根二叉树还要满足根结点的度不大于2.有了根结点之后,每个顶点定

跟王老师学接口:(五)实例:对电子宠物系统进行重构

对电子宠物系统进行重构 主讲教师:王少华   QQ群号:483773664 一.重构需求 定义Eatable接口,在接口中定义eat()方法,表示吃饭功能 定义FlyingDiscCatchable接口,在接口中定义catchingFlyDisc()方法,表示接飞盘功能 定义Swimmable接口,在接口中定义swim()方法,表示游戏功能 定义抽象类Pet,包括宠物名称(name).健康值(health)和与主人亲密度(love)属性,并提供抽象方法print(),用来输出宠物信息 定义狗类(

重构第10天:提取方法(Extract Method)

理解:经常写的代码中,有一些计算逻辑比较复杂的方法,写下来一个很长很长的方法,我们可以把这个方法,根据功能,分解成单独的几个小方法.这样做不仅能够增加代码的可维护性,而且增加了易读性. 详解: 重构前代码: 1 public class Receipt 2 { 3 private IList<decimal> Discounts { get; set; } 4 private IList<decimal> ItemTotals { get; set; } 5 6 public de

重构第9天:提取接口(Extract Interface)

理解:提取接口的意思是,多于一个类共同使用某个类中的方法或属性,那么我们可以把这些方法和属性提出来,作为一个单独的接口.这样的好处是解除代码间的依赖,降低耦合性. 详解: 先看重构前的代码: 1 public class ClassRegistration 2 { 3 public void Create() 4 { 5 // create registration code 6 } 7 8 public void Transfer() 9 { 10 // class transfer code

重构第6天:降低字段(Push Down Field)

理解:和提升字段正好相反,跟降低方法类似,就是把基类中,只有部分继承类需要用到的字段,降低到继承类自身去. 详解: 重构前代码: 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace _31DaysRefactor 7 { 8 public abstract class Task 9 { 10 protected string _res