面向对象编程的一点感受

                       面向对象编程的一点感受

  在几天前应用软件综合设计技术的课上,老师提问到了关于面向对象编程的意义,由于初次接触面向对象的概念是在大一c++课程上,乍一问起,脑子里就只剩下了封装、继承和多态几个词,然后就没有然后了。听了一节课,再结合一下这几天看的《大话设计模式》一书中的知识,写一下自己的看法。

  那就也先从一个小程序说起吧:“请用C++、Java、C# 或VB.NET任意一种面向对象语言实现一个计算器控制台程序,要求输入两个数和运算符号,得到结果。”

  作为一个新手,可能刚看到题目要求后,马上会脑子发热,觉得题目不难,随机动手开始写代码,大概样子如下:

class Program
{
    static void Main(string[] args)
    {
        Console.Write("请输入数字A:");
        string A = Console.ReadLine();
        Console.Write("请选择运算符号(+、-、*、/):");
        string B = Console.ReadLine();
        Console.Write("请输入数字B:");
        string C = Console.ReadLine();
        string D = "";
        if (B == "+")
            D = Convert.ToString(Convert.ToDouble(A) + Convert.ToDouble(C));
        if (B == "-")
            D = Convert.ToString(Convert.ToDouble(A) - Convert.ToDouble(C));
        if (B == "*")
            D = Convert.ToString(Convert.ToDouble(A) * Convert.ToDouble(C));
        if (B == "/")
            D = Convert.ToString(Convert.ToDouble(A) / Convert.ToDouble(C));
        Console.WriteLine("结果是:" + D);
    }     
}
  代码虽然可以运行,但还是暴露了我们作为新手的一些不好的习惯,很有必要做改进:

 1.代码的规范性问题:A,B,C这种命名太过于随便。

 2.if-else语句的写法不合理,不加else语句,每次都要执行4此判断,影响代码效率。

 3.缺少对于用户错误输入的处理:如输入字母或者除数输入0。

  修改后的代码如下:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Console.Write("请输入数字A:");
            string strNumberA = Console.ReadLine();
            Console.Write("请选择运算符号(+、-、*、/):");
            string strOperate = Console.ReadLine();
            Console.Write("请输入数字B:");
            string strNumberB = Console.ReadLine();
            string strResult = "";
            switch (strOperate)
            {
                case "+":
                    strResult = Convert.ToString(Convert.ToDouble(strNumberA) 
                        + Convert.ToDouble(strNumberB));
                    break;
                case "-":
                    strResult = Convert.ToString(Convert.ToDouble(strNumberA) 
                        - Convert.ToDouble(strNumberB));
                    break;
                case "*":
                    strResult = Convert.ToString(Convert.ToDouble(strNumberA) 
                        * Convert.ToDouble(strNumberB));
                    break;
                case "/":
                    if (strNumberB != "0")
                        strResult = Convert.ToString(Convert.ToDouble(strNumberA) 
                              / Convert.ToDouble(strNumberB));
                    else
                        strResult = "除数不能为0";
                    break;
            }
            Console.WriteLine("结果是:" + strResult);
            Console.ReadLine();
        }
        catch (Exception ex)
        {
            Console.WriteLine("您的输入有错:" + ex.Message);
        }
    }
}
  好了,修改至此才可以算是一个合格的新手,但是这样的代码只是满足了可以运行,题目说明用一种面向对象的语言来实现程序,也就是暗示了要运用面向对象的思想来实现程序,下面再来一步步展开说明封装、继承和多态

(所有编程初学者都会有这样的问题,就是碰到问题就直觉地用计算机能够理解的逻辑来描述和表达待解决的问题及具体的求解过程。这其实是用计算机的方式去思考,比如计算器这个程序,先要求输入两个数和运算符号,然后根据运算符号判断选择如何运算,得到结果,这本身没有错,但这样的思维却使得我们的程序只为满足实现当前的需求,程序不容易维护,不容易扩展,更不容易复用。从而达不到高质量代码的要求。” -----引用《大话设计模式》)

1.关于程序的可复用性(封装成类)

  还是刚才的题目,现在要求实现web页面下的计算器程序,怎么办?直接复制一下再稍加改动?那如果代码很多怎么办?会不会引起代码混乱?这其实是非常不好的编码习惯,因为当你的代码中重复的代码多到一定程度,维护的时候,可能就是一场灾难。越大的系统,这种方式带来的问题越严重,编程有一原则,就是用尽可能的办法去避免重复。所以需要想别的办法,想想看,我们写的这段代码,有哪些是和控制台无关的,而只是和计算器有关的?

  所以答案就明显了,我们需要把计算和显示分开,封装成类。准确地说,就是让业务逻辑与界面逻辑分开,(类似于.net中模板引擎的作用)让它们之间的耦合度下降。只有分离开,才可以达到容易维护或扩展。

那我们再来修改一下代码:

Operation运算类:

public class Operation
{
    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 "/":
                    if (numberB == 0)
                        throw new Exception("除数不能为0!");
                result = numberA / numberB;
                break;
        }
        return result;
    }
}
客户端代码:

static void Main(string[] args)
{
    try
    {
        Console.Write("请输入数字A:");
        string strNumberA = Console.ReadLine();
        Console.Write("请选择运算符号(+、-、*、/):");
        string strOperate = Console.ReadLine();
        Console.Write("请输入数字B:");
        string strNumberB = Console.ReadLine();
        string strResult = "";
        strResult = Convert.ToString(Operation.GetResult(Convert.ToDouble(strNumberA),
        Convert.ToDouble(strNumberB), strOperate));
        Console.WriteLine("结果是:" + strResult);
        Console.ReadLine();
    }
    catch (Exception ex)
    {
        Console.WriteLine("您的输入有错:" + ex.Message);
    }
}
  这样即把业务和界面分离了,如果再需要写web程序,手机移动端等程序就可以复用运算类Operation了,至此,面向对象的封装实现。

2.关于程序的可修改和可扩展性(继承)

  现在在上面问题的基础上,在增加新的要求,比如,要求增加求平方根的功能,怎么改?

  可能我们一开始的想法会很简单,不就是在Operation类的switch语句下再加一个case条件吗?这样做的确可以,但是这样做的真的好吗?我们只需要加一个平方根运算,却需要让加减乘除的运算都得来参与编译,而且如果我们一不小心动了其他的代码,把加法运算改成了减法,这岂不是大大的糟糕。

打个比方,如果现在公司要求你为公司的薪资管理系统做维护,原来只有技术人员(月薪),市场销售人员(底薪+提成),经理(年薪+股份)三种运算算法,现在要增加兼职工作人员(时薪)的算法,但按照你昨天的程序写法,公司就必须要把包含原三种算法的运算类给你,让你修改,你如果心中小算盘一打,‘真是郁闷,公司给我的工资这么低,我真是郁闷,这下有机会了’,于是你除了增加了兼职算法以外,在技术人员(月薪)算法中写了一句

if(员工是小菜)

{

   salary = salary * 2.0;

}

那就意味着,你的月薪每月都会增加一倍(小心被抓去坐牢),本来是让你加一个功能,却使得原有的运行良好的功能代码产生了变化,这个风险太大了。你明白了吗?”------------引用《大话设计模式》)

  所以我们应该把加减乘除等运算分离,修改其中一个不影响另外的几个,增加运算算法也不影响其他代码(根本就不让别人看到其它模块的代码,降低风险)。在此指导下,我们修改代码如下:

Operation运算类:

public class Operation
{
    private double _numberA = 0;
    private double _numberB = 0;
    
    public double NumberA
    {
        get  {  return _numberA;  }
        set  {  _numberA = value;  }
    }
    public double NumberB
    {
        get  {  return _numberB;  }
        set  {  _numberB = value;  }
    }
    public virtual double GetResult()
    {
        double result = 0; 
        return result;
    }
}
加减乘除类:

class OperationAdd : Operation
{
    public override double GetResult()
    {
        double result = 0; 
        result = NumberA + NumberB;
        return result;
    }
}

class OperationSub : Operation
{
   public override double GetResult()
    {
        double result = 0;
        result = NumberA - NumberB;
        return result;
    }
}

class OperationMul : Operation
{
    public override double GetResult()
    {
        double result = 0;
        result = NumberA * NumberB;
        return result;
    }
}

class OperationDiv : Operation
{
    public override double GetResult()
    {
        double result = 0;
        if (NumberB==0)
            throw new Exception("除数不能为0。");
        result = NumberA / NumberB;
        return result;
    }
}
上述代码中:

1.首先是一个运算类,它有两个Number属性,主要用于计算器的两个操作数;然后有一个虚方法GetResult()(用于被子类的同名方法覆盖),用于得到结果

2.然后我们把“加减乘除”都写成了该运算类的子类,继承它后,重写了GetResult()方法,这样如果要修改任何一个算法,就不需要提供其他算法的代码了。如果想要再增加开平方根的功能,只需要再增加一个类继承Operation类即可,也不用再担心影响其他的代码了。至此,继承性实现。但是问题是,我如何让计算器知道我是希望用哪一个算法呢?也就是如何去实例化对象的问题。这就需要我们用到面向对像的第三个特性:多态了。

3.关于代码的灵活性(多态)

 (关于多态的概念是让人觉得比较抽象的,先给出一个定义,再通过下面的介绍加以理解,

多态性是指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果。不同的对象,收到同一消息可以产生不同的结果,这种现象称为多态性。多态性允许每个对象以适合自身的方式去响应共同的消息。多态性增强了软件的灵活性和重用性。)

  接着上面的问题,如何去实例化对象,实例化谁,将来会不会增加实例化的对象,比如增加开根运算,这是很容易变化的地方,应该考虑用一个单独的类来做这个创造实例的过程,这就是工厂(简单工厂模式),现在我们来写一下这个类:

简单运算工厂类:

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;
    }
}
  这样子,我们只需要输入运算符号,工厂就实例化出合适的对象,通过多态,返回父类的方式实现了计算器的结果。(关于具多态的具体实现,建议使用断点来查看,有助于加强理解)

客户端代码:

static void Main(string[] args)
    {
        try
        {
            Console.Write("请输入数字A:");
            string strNumberA = Console.ReadLine();
            Console.Write("请选择运算符号(+、-、*、/):");
            string strOperate = Console.ReadLine();
            Console.Write("请输入数字B:");
            string strNumberB = Console.ReadLine();

            Operation oper;
            oper = OperationFactory.createOperate(strOperate);
            oper.NumberA = Convert.ToDouble(strNumberA);
            oper.NumberB = Convert.ToDouble(strNumberB);
            double result = oper.GetResult();//运行时才知道调用哪个类的方法:多态
            Console.WriteLine("结果是:" + result.ToString());
            Console.ReadLine();

        }
        catch (Exception ex)
        {
            Console.WriteLine("您的输入有错:" + ex.Message);
        }
    }
  界面的实现就是这样的代码,不管你是控制台程序,Windows程序,Web程序,PDA或手机程序,都可以用这段代码来实现计算器的功能,如果有一天我们需要更改加法运算,只需要改OperationAdd 就可以了(易修改:可维护性强);那么我们需要增加各种复杂运算,比如平方根,立方根,自然对数,正弦余弦等,则需要增加相应的运算子类就,还要去修改运算类工厂,在switch中增加分支即可(易添加:可扩展性强)如果要修改界面,就直接去修改界面(易复用:可复用性强)

好了,关于面向对象的想法就写这么多,面向对象作为一种重要的思想,需要我们加强理解,更需要体现在代码里,让自己代码质量更高。Ps(郑重说明:文中代码引用了《大话设计模式》一书,经调试通过,均可以运行,另外如果大家对设计模式感兴趣,可以参考此书,还有《Head First Design Patterns》一书也很好)。

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值