代码改变世界

看着这个实例,一起来学重构吧!

2010-08-17 18:54  Aga.J  阅读(2136)  评论(24编辑  收藏  举报

       浏览jake的blog时看到这样一篇文章30 Days of .NET [Windows Mobile Applications] - Day 01: Minutes to Midnight Countdown(午夜倒数器),题目非常吸引人,我点了进去,看到原来这是jake翻译一个叫Chris Craft的人的系列文章,看完了jake这篇文章后,我也去了Chris Craft的blog看了这篇原文,下载了源代码,代码很简单,就一个timer_Tick的函数把所有程序逻辑都搞清楚了,就是计算我们离今天的结束还有多少小时,多少分钟,多少秒,然后一个一个的列举在form上。下面就是timer_Tick的源代码

private void timer_Tick(object sender, EventArgs e)

        {

            TimeSpan timeSpan = DateTime.Now.Date.AddDays(1) - DateTime.Now;

            labelHours.Text = string.Format("{0} of 24 hours left", timeSpan.Hours);

            labelMinutes.Text = string.Format("{0} of 60 minutes left", timeSpan.Minutes);

            labelSeconds.Text = string.Format("{0} of 60 seconds left", timeSpan.Seconds);

 

            labelTotalMinutes.Text = string.Format("{0} of 1440 total minutes left", timeSpan.TotalMinutes.ToString("#.0"));

            labelTotalSeconds.Text = string.Format("{0} of 86400 total seconds left", timeSpan.TotalSeconds);

 

            progressBarTotal.Value = 86400 - (int) timeSpan.TotalSeconds;

 

            progressBarHours.Value = 24 - timeSpan.Hours;

            progressBarMinutes.Value = 60 - timeSpan.Minutes;

            progressBarSeconds.Value = 60 - timeSpan.Seconds;

 

            progressBarTotalMinutes.Value = 1440 - (int) timeSpan.TotalMinutes;

            progressBarTotalSeconds.Value = 86400 - (int) timeSpan.TotalSeconds;

        }

       一开始看到这个代码的时候我就觉得好长好复杂,作者设定每100ms就会触发timer事件从而来调用这个函数,但是运行起来我们发现,数值上一直在变化的只有秒数,而分钟数则在60秒后才会有变化,小时的话更需要3600秒,但是作者每过100ms就调用这个timer_Tick的函数,使得每次都要对窗体上的控件的内容进行重置,不管数值有没有变,我们每过100ms都会给labelHours或者是progressBarHours等控件的相关属性赋值一次,而经常需要改变的只是labelSeconds 和progressBarSeconds以及progressBarTotalSeconds。所以我就对作者的这份代码进行了----重构。

     我想改的时候,我的第一个念头就是---对象!但是我又觉得没那个必要,所有一开始我想是通过if语句来判断秒数是不是过了60秒,从而来修改分钟的数值,用if来判断分钟数是不是过了60分钟来修改hour的数值,但是这样写下来的代码也是很混乱,所以最后我还是使用了对象,使用了面向对象的东西来重构作者的程序。下面是我作的修改

     class NotifyElem

        {

            //每个元素都有一个周期,秒有60秒的周期,分有60分的周期,小时有24的周期

            //这里定义的是一个周期事件处理委托

            public delegate void CycleEventHandler();  

            public event CycleEventHandler cycleEvent;

           

            //这里是显示到控件上的文本

            string stringToShow;

 

            //这里是每个对象的周期值

            int cycleValue;

           

            //每个对象现在的值

            double currValue;

 

            public NotifyElem(string stringToShow, int cycleValue)

            {

                this.cycleValue = cycleValue;

                this.stringToShow = stringToShow;

            }

 

            public void setCurrValue(double value)

            {

                currValue = value;

 

                if (value == cycleValue-1 && cycleEvent != null)  //一旦注册了委托并且过了一个周期,那么就会激发事件

                {

                    cycleEvent();

                }

 

            }

            public string getNotifyString()             //将作者对label的赋值串进行了函数上的封装

            {

                return string.Format(stringToShow, currValue);

            }  

            public int notifyValue()                    //将作者对progressBar的赋值值进行了函数上的封装

            {

                return cycleValue - (int)currValue;

            }

 

        }

      我定义了一个NotifyElem的类,类名的意思主要和我们这个程序相关,指的是告知我们现在还有多少秒,多少分钟,多少小时,然后基本的说明在注释里面。使用这个类,我对主窗体的函数做了下面的修改

     首先我自己添加一个form_Load的事件处理,目的是要在form运行时初始化所有显示的东西

        private void frmM2M_Load(object sender, EventArgs e)

        {

            TimeSpan timeSpan = DateTime.Now.Date.AddDays(1) - DateTime.Now; 

 

            labelSeconds.Text = string.Format("{0} of 60 seconds left", timeSpan.Seconds);

            progressBarSeconds.Value = 60 - timeSpan.Seconds;

 

            labelTotalSeconds.Text = string.Format("{0} of 86400 total seconds left", timeSpan.TotalSeconds);

            progressBarTotalSeconds.Value = 86400 - (int)timeSpan.TotalSeconds;

 

            labelHours.Text = string.Format("{0} of 24 hours left", timeSpan.Hours);    //剩余的小时,分钟,秒,使用减出来的timeSpan来获得

            progressBarHours.Value = 24 - timeSpan.Hours;

 

            labelMinutes.Text = string.Format("{0} of 60 minutes left", timeSpan.Minutes);

            progressBarMinutes.Value = 60 - timeSpan.Minutes;

 

            labelTotalMinutes.Text = string.Format("{0} of 1440 total minutes left", timeSpan.TotalMinutes.ToString("#.0"));

            progressBarTotalMinutes.Value = 1440 - (int)timeSpan.TotalMinutes;

        }

//里面的代码是拷贝自作者的,和second有关的其实可以不用初始化,因为等下会初始化,这里我也没再去重构,为了方便就贴上去了

     接下来就是使用我所定义的类

        NotifyElem secondElem;

        NotifyElem minuteElem;

        NotifyElem hourElem;

 

        public frmM2M()

        {

            InitializeComponent();

             secondElem= new NotifyElem("{0} of 60 seconds left",60);

             minuteElem= new NotifyElem("{0} of 60 minutes left", 60);

             hourElem= new NotifyElem("{0} of 24 hours left", 24);

 

             secondElem.cycleEvent += new NotifyElem.CycleEventHandler(secondElem_ cycleEvent);

             minuteElem.cycleEvent += new NotifyElem.CycleEventHandler(minuteElem_cycleEvent);

        }

 

        void minuteElem_cycleEvent()

        {

            TimeSpan timeSpan = DateTime.Now.Date.AddDays(1) - DateTime.Now;

 

            hourElem.setCurrValue(timeSpan.Hours);

            updateUI2(labelHours, progressBarHours, hourElem);

        }

 

        void secondElem_cycleEvent()

        {

            TimeSpan timeSpan = DateTime.Now.Date.AddDays(1) - DateTime.Now;

 

            minuteElem.setCurrValue(timeSpan.Minutes);

            updateUI2(labelMinutes, progressBarMinutes, minuteElem);

 

        }

    主要有三个对象,两个委托。委托的目的是在每个cycle后设置由这个cycle引起的其他时间元素的变化

     接下来就是作者代码里的那个函数timer_Tick

        private void timer_Tick(object sender, EventArgs e)         //使用一个timer时间间隔是100ms,每到这个时间就更新信息。

        {

            TimeSpan timeSpan = DateTime.Now.Date.AddDays(1) - DateTime.Now;    //使用1天后的时间减去现在,得到timeSpan

 

            secondElem.setCurrValue(timeSpan.Seconds);

            updateUI2(labelSeconds, progressBarSeconds, secondElem);

      }

       我只用了三行代码,首先还是要得到timeSpan,然后就调用secondElem.setCurrValue,因为在timer的tick中,经常变的是second,所有把它的setCurrValue和updateUI函数放到这里来调用,然后大家请看setCurrValue的实现。

              public void setCurrValue(double value)

            {

                currValue = value;

 

                if (value == cycleValue-1 && cycleEvent != null)          //一旦注册了委托并且过了一个周期,那么就会激发事件

                {

                    cycleEvent();

                }

 

            }

      每次到了一个周期后,它就会调用委托的事件,而我们在注册委托时就实现了这个事件的处理,

        void secondElem_cycleEvent()

        {

            TimeSpan timeSpan = DateTime.Now.Date.AddDays(1) - DateTime.Now;

 

            minuteElem.setCurrValue(timeSpan.Minutes);

            updateUI2(labelMinutes, progressBarMinutes, minuteElem);

 

        }

  每个周期后,会对相应的上一级的时间元素进行更新,这里更新了分钟。

    private void updateUI2(Label label, ProgressBar p,NotifyElem n)
        {
            label.Text = n.getNotifyString();
            p.Value = n.notifyValue();
        }  //这里是更新ui的代码,把重复的工作抽取出来吧。但是传入那么多对象貌似不是个好主意

       修改完毕,就到这里,我也不想再继续完美它了,虽然还有好几个地方可以完善,可以不用我现在这样复杂,我对作者原来的代码进行了彻底的重构,作者在写这个程序的时候,很好的给我们展示了timeSpan的应用和一般的wm控件的使用,同样也给了我机会展示一下什么是重构,前阵子看Object-Oriented的书,觉得没地方可以实践,现在就加上重构的知识,实践了一次,当然Object-Oriented Design and Analysis 不仅仅只有这点,它所包含的是整个工程,我在这里只是使用了重构的思想,再加上一下OO的原则。大家对这个程序还有什么更好的重构的方法呢?都来说说吧,大家交流交流OO的东西。