微信扫一扫

028-83195727 , 15928970361
business@forhy.com

[置顶] 设计模式之面向对象再回首

2016-08-07

楔子

    如果你不能很好的理解对象这个思想,你可以把对象看做是现实生活中的一个个的个体。这里就不在详细介绍对象的概念等知识了,直接说一些我个人对对象一部分知识的理解。为方便阐述,下面以一个小小计算器的Demo做例子展开我对封装和耦合的理解。

原始代码

<span style="font-family:KaiTi_GB2312;font-size:24px;">namespace 计算器代码V1._0
{
    /// <summary>
    /// 计算器的原始代码
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("请输入数字A:");       //提示
            string A = Console.ReadLine();            //定义string变量A接收用户输入的第一个数字

            Console.WriteLine("请输入数字B:");       //提示
            string B = Console.ReadLine();            //定义string变量B接收用户输入的第二个数字

            Console.WriteLine("选择运算符号:+、-、*、/");        //提示
            string C= Console.ReadLine();            //定义string变量C接收用户选择的运算符号

            string D = "";          //定义一个string变量D接收计算结果

            if (C =="+")
                D =Convert.ToString(Convert.ToDouble (A )+Convert.ToDouble (B ));

            if (C == "-")
                D = Convert.ToString(Convert.ToDouble(A) - Convert.ToDouble(B));
            
            if (C == "*")
                D = Convert.ToString(Convert.ToDouble(A) * Convert.ToDouble(B));

            if (C == "/")
                D = Convert.ToString(Convert.ToDouble(A) / Convert.ToDouble(B));

            Console.WriteLine(A + C + B + "的结果是"+D);
            Console.ReadKey(true);

        }
    }
}
</span>

    从上面的代码中,我们不难看出:代码很好的体现了“穷举”的思想;只要是一个功能,哪怕是一个最小的不可再分的功能,都有着自己的“完整的”一套代码;逻辑上的关系也不复杂。很适合这样的小的程序,但是一旦代码量超过一定程度,通过这种方法开发出来的程序对任何一个维护人员来说不亚于一场灾难。

封装

解读

    封装的出发点就是将业务逻辑和界面逻辑分离开来,就好像我们的硬件和软件一样对代码进行一个初级的“模块化”。

代码

<span style="font-family:KaiTi_GB2312;font-size:24px;">namespace 简单工厂
{
    class Program
    {
       /// <summary>
       /// 客户端代码
       /// </summary>
       /// <param name="args"></param>    
        static void Main(string[] args)     //客户端代码
        {
            /*
             * 客户端代码
             */

            try         //Try—Catch 调错语句
            {
                //输入第一个数字A
                Console.WriteLine("请输入数字A:");       
                string strNumberA = Console.ReadLine();

                //输入运算符
                Console.WriteLine("请选择运算符号(+、-、*、/)");      
                string strOperated=Console.ReadLine();

                //输入第二个数字B
                Console.WriteLine("请输入第二个数字B");     
                string strNumberB = Console.ReadLine();

                //计算程序代码
                string strResult = "";
                strResult = Convert.ToString(Operation.GetResult(Convert.ToDouble(strNumberA), Convert.ToDouble(strNumberB), strOperated));

                //输出计算结果
                Console.WriteLine("计算结果是:{0}",strResult );
                Console.ReadLine();
            }
            catch (Exception ex)       //调错语句
            {
                Console.WriteLine("您的输入有误,请重新输入:{0}",ex.Message );

                
            }
        }
    }
    #region Operation运算类
    public class Operation      //运算类
    {
        /// <summary>       //运算类代码
        /// 运算类代码
        /// </summary>
        /// <param name="numberA"></param>
        /// <param name="numberB"></param>
        /// <param name="operate"></param>
        /// <returns></returns>
        public static double GetResult(double numberA, double numberB, string operate)  // 运算类代码
        {
            double result = 0d;
            switch (operate)
            {
                case "+":       //加法
                    result=numberA +numberB ;
                    break ;


                case "-" :      //减法
                    result = numberA - numberB;
                    break;


                case "*":       //乘法
                    result = numberA * numberB;
                    break;

                case "/":       //除法
                    result = numberA / numberB;
                    break;


  

            }
            return result;          //返回值
        }


    }
    #endregion
}
</span>

    不难看出,上面这版代码将原始程序分割成了两部分:客户端和运算类。客户端即是我们前面提到的界面逻辑的显示,运算类即是我们前面提到的业务逻辑的显示。通过分割,客户端和运算类不再是藕断丝连的千丝万缕,而是值通过过一个函数的调用实现运算功能的实现。

解耦

解读

    在介绍解耦为何物之前,我们需要知道耦合可以看成一个描述模块之间关系亲密程度的指标吧:如果模块之间比较亲密,那么模块之间具有强耦合关系;如果模块之间一般,那么称模块之间具有弱耦合关系。而考量耦合强弱,可以从模块间接口的复杂性、模块之间调用的方式和模块之间传送数据的量进行衡量。虽然我们的老祖宗不懂得程序不懂得编写代码,但是智慧的老祖宗早在几千年前就告诫我们一条普适性的哲理“牵一发而动全身”。模块之间如果具有强耦合关系,那么修改其中一个必然会影响到另一个。这就给系统的维护带来了巨大的挑战。所以,我们在编写代码时力求对模块进行解耦处理。下面请看计算器的第三版代码。

代码

<span style="font-family:KaiTi_GB2312;font-size:24px;">namespace 紧耦合和松耦合
{
    class Program
    {
        /*
         * 1、分离加减乘除等运算
         * 2、修改其中一个不影响其他三个
         * 3、力求类之间的松耦合
         * 4、通过继承和多态实现
         */
        static void Main(string[] args)     //客户端代码
        {
            Operation oper;     //声明类型为Operation的变量oper


            oper = OperationFactory.createOperate("+");         //调用加法

            oper.NumberA = 1;
            oper.NumberB = 2;

            double result = oper.GetResult();
            Console.WriteLine(result );

            Console.ReadKey (true );

        }
    }
    public class Operation          //运算总部,运算类
    {
        //声明两个double变量存储数字A和数字B
        private double _numberA = 0;
        private double _numberB = 0;

        //数字A的属性
        public double NumberA       //数字A的属性
        {
            get { return _numberA ;}
            set { _numberA = value; }
        }

        public double NumberB       //数字B的属性
        {
            get { return _numberB; }
            set { _numberB = value; }
        }

        /// <summary>
        /// 声明一个类显示返回值
        /// </summary>
        /// <returns></returns>
        public virtual double GetResult()
        {
            double result = 0;
            return result;
        }
    }

    public class OperationAdd : Operation         //加法类,继承运算类
    {
        public override double GetResult()
        {
            double result = 0;      //初始化result变量
            result = NumberA + NumberB;     //执行加法运算
            return result;      //执行加法后,将结果返回给result
        }
    }

    public class OperationSub : Operation       //减法类,继承运算类
    {
        public override double GetResult()
        {
            double result = 0;
            result = NumberA - NumberB;
            return result;
        }
    }

    public class OperationMul:Operation         //乘法类,继承运算类
    {
        public override double GetResult()
        {
            double result = 0;
            result = NumberA * NumberB;
            return result;
        }

    }

    public class OperationDiv : Operation       //除法类,继承运算类
    {
        public override double GetResult()
        {
            double result = 0;

            //通过if判断除数不能为零
            if (NumberB ==0)
            {
                throw new Exception("除数不能为0");
            }
            result = NumberA / NumberB;
            return result;
            
        }
    }
    
    /// <summary>         简单工厂模式的总结
    /// 简单工厂模式
    /// 1、实例化对象
    /// 2、选择实例化的对象
    /// 3、有无在将来增加对象的可能
    /// 4、通过简单工厂实现创造实例的过程
    /// </summary>
    public class OperationFactory           //简单工厂模式之简单运算工厂
    {
        public static Operation createOperate(string operate)
        {
            Operation oper = null;
            switch (operate )
            {
                //加法
                case "+":
                    oper = new OperationAdd();
                    break;
                //减法
                case "-":
                    oper = new OperationSub();
                    break;
                //乘法
                case "*":
                    oper = new OperationMul();
                    break;
                //除法
                case "/":
                    oper = new OperationDiv();
                    break;
            }
            return oper;        //返回值为result 
        }
    }

}</span>

    上面这段代码主要是对业务逻辑进行了解耦处理:信息的输入和后台计算逻辑的解耦、计算逻辑中的加减乘除四则运算之间的解耦。经过解耦后,我们优化一部分代码时就不对对另外的代码造成特别大的影响。当然,封住也给维护带来了好处,只不过没有解耦带来的好处的影响大。

感悟

    现在有这样一个不成熟的想法:在我看来,封装就是解耦的一个初始化吧。PS:一段代码经过封住也好解耦也好,产生的结果的数量是不定的;这需要我们自己结合系统的大小做出决定。下面给出一张我对封装和解耦的理解的图。


后记

    面向对象的概念对于我们来说并不算陌生了。但是让我完全理解面向对象的是《大话设计模式》这本书。面向对象最醒目的地方就是牵一发而不动全身。能够体现出面向对象的代码和不能体现出面向对象思想的代码就好比活字印刷术和印刷术的之间的关系。我们知道活字印刷术的印版上的每一个字都是可以替换的而印刷术的印版就不具备活字印刷术的这种特性了。所以当出现错字、客人要求换字的情况时,活字印刷术只需要替换需要替换的字即可;而印刷术则需要替换全部的印版。两者的这一特性无疑会让大众选择活字印刷术而非印刷术。活字印刷术的特性放到面向对象中就是可维护、可复用、可扩展和灵活性好这四点特性了。设计模式第一遍给我最大的震撼不是接触到模式之后的欣喜,而是一名程序员工作之后坚持学习的精神。

感谢您的宝贵时间,祝生活愉快,谢谢~~

                                           —joker