伍迷家园

让编程融入生活
随笔 - 81, 文章 - 0, 评论 - 1422, 引用 - 136
数据加载中……

小菜编程成长记(五 体会简单工厂模式的美妙)

 

(续上篇) 
       次日,小菜再来找大鸟,问道:“你昨天说计算器这样的小程序还可以用到面向对象三大特性?继承和多态怎么可能用得上,我实在不可理解。”
        大鸟:“小菜很有钻研精神吗?好,今天我让你功力加深一级。你先要考虑一下,你昨天写的这个代码,能否做到很灵活的可修改和扩展呢?”
        小菜:“我已经把业务和界面分离了呀,这不是很灵活了吗?”
        大鸟:“那我问你,现在如果我希望增加一个开根(sqrt)运算,你如何改?”
        小菜:“那只需要改Operation类就行了,在switch中加一个分支就行了。”
        大鸟:“问题是你要加一个平方根运算,却需要把加减乘除的运算都得来参与编译,如果你一不小心,把加法运算改成了减法,这不是大大的糟糕。打个比方,如果现在公司要求你为公司的薪资管理系统做维护,原来只有技术人员(月薪),市场销售人员(底薪+提成),经理(年薪+股份)三种运算算法,现在要增加兼职工作人员的(时薪)算法,但按照你昨天的程序写法,公司就必须要把包含有的原三种算法的运算类给你,让你修改,你如果心中小算盘一打,‘TMD,公司给我的工资这么低,我真是郁闷,这会有机会了’,于是你除了增加了兼职算法以外,在技术人员(月薪)算法中写了一句 

if (员工是小菜)
{
    salary 
= salary * 1.1;
}

那就意味着,你的月薪每月都会增加10%(小心被抓去坐牢),本来是让你加一个功能,却使得原有的运行良好的功能代码产生了变化,这个风险太大了。你明白了吗?”
        小菜:“哦,你的意思是,我应该把加减乘除等运算分离,修改其中一个不影响另外的几个,增加运算算法也不影响其它代码,是这样吗?”
        大鸟:“自己想去吧,如何用继承和多态,你应该有感觉了。”
        小菜:“OK,我马上去写。”

    /// <summary>
    
/// 运算类
    
/// </summary>

    class Operation
    
{
        
private double _numberA = 0;
        
private double _numberB = 0;
        
        
/// <summary>
        
/// 数字A
        
/// </summary>

        public double NumberA
        
{
            
getreturn _numberA; }
            
set{ _numberA = value;}
        }


        
/// <summary>
        
/// 数字B
        
/// </summary>

        public double NumberB
        
{
            
getreturn _numberB; }
            
set{ _numberB = value; }
        }


        
/// <summary>
        
/// 得到运算结果
        
/// </summary>
        
/// <returns></returns>

        public virtual double GetResult()
        
{
            
double result = 0
            
return result;
        }


       
    }

 

 

    /// <summary>
    
/// 加法类
    
/// </summary>

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

    }


    
/// <summary>
    
/// 减法类
    
/// </summary>

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

    }


    
/// <summary>
    
/// 乘法类
    
/// </summary>

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

    }


    
/// <summary>
    
/// 除法类
    
/// </summary>

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

    }

        小菜:“大鸟哥,我按照你说的方法写出来了一部分,首先是一个运算类,它有两个Number属性,主要用于计算器的前后数,然后有一个虚方法GetResult(),用于得到结果,然后我把加减乘除都写成了运算类的子类,继承它后,重写了GetResult()方法,这样如果要修改任何一个算法,都不需要提供其它算法的代码了。但问题来了,我如何让计算器知道我是希望用哪一个算法呢?”
        大鸟:“写得很不错吗,大大超出我的想象了,你现在的问题其实就是如何去实例化对象的问题,哈,今天心情不错,再教你一招‘简单工厂模式’,也就是说,到底要实例化谁,将来会不会增加实例化的对象(比如增加开根运算),这是很容易变化的地方,应该考虑用一个单独的类来做这个创造实例的过程,这就是工厂,来,我们看看这个类如何写。”

 

    /// <summary>
    
/// 运算类工厂
    
/// </summary>

    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;
        }

    }

          大鸟:“哈,看到吧,这样子,你只需要输入运算符号,工厂就实例化出合适的对象,通过多态,返回父类的方式实现了计算器的结果。”

Operation oper;
oper = OperationFactory.createOperate("+");
oper.NumberA 
= 1;
oper.NumberB 
= 2;
double result = oper.GetResult();
                

        大鸟: “哈,界面的实现就是这样的代码,不管你是控制台程序,Windows程序,Web程序,PDA或手机程序,都可以用这段代码来实现计算器的功能,当有一天我们需要更改加法运算,我们只需要改哪里?”
        小菜:“改OperationAdd 就可以了。”
        大鸟: “那么我们需要增加各种复杂运算,比如平方根,立方根,自然对数,正弦余弦等,如何做?”
        小菜:“只要增加相应的运算子类就可以了呀。”
        大鸟: “嗯?够了吗?”
        小菜:“对了,还需要去修改运算类工厂,在switch中增加分支。”
        大鸟: “哈,那才对,那如果要修改界面呢?”
        小菜:“那就去改界面呀,关运算什么事呀。”
        小菜:“ 回想那天我面试题写的代码,我终于明白我为什么写得不成功了,原来一个小小的计算器也可以写出这么精彩的代码,谢谢大鸟。”

(下为当时面试题时小菜所写代码,见《小菜编程成长记(一)》)

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 (O == "/")
            D 
= Convert.ToString(Convert.ToDouble(A) / Convert.ToDouble(C));

        Console.WriteLine(
"结果是:" + D);
    }
     
}


        大鸟: “吼吼,记住哦,编程是一门技术,更加是一门艺术,不能只满足于写完代码运行结果正确就完事,时常考虑如何让代码更加简炼,更加容易维护,容易扩展和复用,只有这样才可以是真的提高。写出优雅的代码真的是一种很爽的事情。不过学无止境,其实这才是理解面向对象的开始呢。给你出个作业,做一个商场收银软件,营业员根据客户购买商品单价和数量,向客户收费。”
        小菜:“就这个?没问题呀。”

(待续)

posted on 2006-09-23 09:17 伍迷 阅读(10255) 评论(70)  编辑 收藏 所属分类: 面向对象小菜编程成长记

评论

#1楼    回复  引用    

简单明了......不错......
2006-09-23 12:02 | join[匿名] [未注册用户]

#2楼    回复  引用  查看    

Operation oper;
oper.NumberA = 1;
oper.NumberB = 2;
oper = OperationFactory.createOperate("+");
double result = oper.GetResult();
-----------------------------------

这段有点问题吧,声明了但没实例化,就进行了oper.NumberA = 1;oper.NumberB = 2;
然后才调用工厂生成实例,顺序是不是放错了?
2006-09-23 13:53 | Amnoh      

#3楼 [楼主]   回复  引用  查看    

@Amnoh
非常不好意思,的确是笔误,感谢您的批评指正。
2006-09-23 13:59 | 伍迷      

#4楼    回复  引用  查看    

很好,不错
2006-09-23 20:22 | 中国土匪      

#5楼    回复  引用  查看    

呵呵,挺好的,浅显易懂,我要让我们公司那些做了N年PB且从来不愿意学OOP的“高手”们来看看。

:) 博主下一章是不是要开讲Factory Method了?
2006-09-24 13:44 | leonqin      

#6楼    回复  引用    

public static Operation createOperate(string operate)
请问这句中的 Operation createOperate(string operate)是什么意思
谢谢
小菜不懂啊
2006-09-24 14:31 | kttt [未注册用户]

#7楼 [楼主]   回复  引用  查看    

@kttt
Operation就是返回值是返回一个Operation对象。
createOperate是一个静态方法的名字,用来创建具体的运算对象
operate是一个字符串参数,代表输入的加减乘除符号
我不知道你对多态的理解有多少,这里就是用到了多态,即子类以父类的身份出现,却以自己的方式去实现。
这里就是加减乘除类以运算父类的身份出现,但每个子类实现GetResult()的方法是各自不同的,这样实现了界面代码根本不用去创建具体实例,达到松耦合的目的。
当你明白这个道理的时候,你会理解,面向对象,实在是太伟大的思想。
2006-09-24 15:53 | 伍迷      

#8楼 [楼主]   回复  引用  查看    

@leonqin
不一定会按设计模式的顺序写,不过迟早会讲到,哈,通过故事来理解编程,是一种尝试,我会努力把后续写好。
2006-09-24 16:56 | 伍迷      

#9楼    回复  引用  查看    

谢谢
你的回答!
2006-09-26 08:37 | 释天      

#10楼    回复  引用  查看    

MARK
2006-09-26 11:03 | 心有灵犀      

#11楼    回复  引用    

请问:你的作业"商场收银"什么时间才完成啊?!!
2006-11-11 11:49 | yanwc [未注册用户]

#12楼    回复  引用    


把你的程序用java重写了一次....

import java.io.*;

abstract class Operation
{
double NumberA;
double NumberB;

abstract double getResult() throws Exception;
}

class OperateAdd extends Operation
{
public double getResult()
{
double result=0;
result=NumberA+NumberB;
return result;
}
}

class OperateSub extends Operation
{
public double getResult()
{
double result=0;
result=NumberA-NumberB;
return result;
}
}

class OperateMul extends Operation
{
public double getResult()
{
double result=0;
result=NumberA*NumberB;
return result;
}
}

class OperateDiv extends Operation
{
public double getResult() throws Exception
{
double result=0;
if (NumberB==0)
{
throw new Exception("除数不能为零");
}
result=NumberA/NumberB;
return result;
}
}

class OperateFactory
{
public static Operation createOperation(char oper) throws Exception
{
Operation op=null;
switch(oper)
{
case '+':
op=new OperateAdd();
break;
case '-':
op=new OperateSub();
break;
case '*':
op=new OperateMul();
break;
case '/':
op=new OperateDiv();

break;
default:
throw new Exception("操作的操作符有误码");

}
return op;
}
}
class OperateTest
{
public static void main(String[] args)
{

BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
try
{
System.out.print("请输入A的值");
String strNumberA=br.readLine();

System.out.print("请输入操作符,如(+,-,*,/)");
String strOperate=br.readLine();

System.out.print("请输入B的值");
String strNumberB=br.readLine();

Operation operate=OperateFactory.createOperation(strOperate.charAt(0));
operate.NumberA=Double.valueOf(strNumberA).doubleValue();
operate.NumberB=Double.valueOf(strNumberB).doubleValue();

String result=""+operate.getResult();

System.out.println("结果为"+result);
}
catch(Exception e)
{
e.printStackTrace();
}

}
}
2007-01-14 09:46 | QB [未注册用户]

#13楼    回复  引用    

QB这位老兄真有心,继续努力呀,C#和Java其实是殊途同归。
2007-01-28 21:33 | 伍迷 [未注册用户]

#14楼    回复  引用  查看    

very good,很生动嘛,
2007-03-02 10:55 | chy710      

#15楼    回复  引用    

精彩!深入浅出!可解可行!谢谢!
2007-03-03 17:47 | 溢味轩 [未注册用户]

#16楼    回复  引用    

大鸟: “那么我们需要增加各种复杂运算,比如平方根,立方根,自然对数,正弦余弦等,如何做?”
小菜:“只要增加相应的运算子类就可以了呀。”

==========

真的不用修改界面吗?那么平方根,立方根,自然对数,正弦余弦这些“符号”怎么输入呢?window自带的计算器可都是有相应的按钮的呀。你的是给一个文本框直接输入吗?
2007-03-03 23:11 | jyk [未注册用户]

#17楼 [楼主]   回复  引用  查看    

@jyk
就这个例子而言,界面当然是要改的,不过完全可以改动非常之小,我指的更多的是其中的精神,而不是这个例子本身。
2007-03-03 23:15 | 伍迷      

#18楼    回复  引用  查看    


老兄真适合做小儿节目主持人
2007-03-05 14:35 | 航天奇侠      

#19楼 [楼主]   回复  引用  查看    

@航天奇侠
哈,用初学者的视角,看到问题会比较容易一些,因为有些问题直接写,没感觉,如果是初学者,那么什么问题都有可能提得出来。再说设计模式的确是比较难理解的,刚学的时候有很多不明白的地方。
2007-03-05 14:50 | 伍迷      

#20楼    回复  引用    

有个地方可以修改。
当用户输入当前没有的运算符号时,oper返回的是空。
建议将加减乘除等运算种类封装成一个ENUM,这样其他开发人员拿过来,也知道能够处理哪些计算了。
2007-03-07 13:27 | ghibli [未注册用户]

#21楼    回复  引用    

浅显易懂 不错!!
2007-03-21 10:48 | jacky [未注册用户]

#22楼    回复  引用  查看    

very good ! mark
2007-03-22 09:35 | Ame      

#23楼    回复  引用  查看    

不好意思,第一个回复的时候只看了五,没有看见面的。
现在又从一到五看了一遍,有了不同的理解。

学了一招:是不是定义一个父类的对象,然后可以把子类的实力付给他。

这是不是就是多态了?

一直没有用过多态。试一试。
2007-03-22 14:19 | 金色海洋(jyk)      

#24楼    回复  引用  查看    

如果在
class OperationAdd : Operation

里面再加一个函数,比如 public int aaa()

那么在
Operation oper;
oper = OperationFactory.createOperate("+");

oper 是不是没有 aaa这个方法呢?

我做了一个程序,是没有aaa这个函数的,是不是正常的呀?

如果想让 oper也有aaa这个函数除了在父类里面定义之外还有没有其他的方法呢?
2007-03-22 14:40 | 金色海洋(jyk)      

#25楼 [楼主]   回复  引用  查看    

@金色海洋(jyk)
你还没有真正理解多态,建议先去搜索“今天你多态了吗”的文章,再把计算器的代码一句一句的写一遍,或许你就能感悟多态的好处了。
2007-03-22 15:33 | 伍迷      

#26楼    回复  引用    

赞,喜欢你的文风
2007-03-24 16:45 | SnowDoggie [未注册用户]

#27楼    回复  引用  查看    

不错
不错,楼主说的非常幽默,通俗,把我们一步一步带入面向对象的世界
thx ^_^
2007-03-26 16:12 | Bryant      

#28楼    回复  引用    

很不错的文章
继续灌注
2007-03-26 20:11 | Six4 [未注册用户]

#29楼    回复  引用    

文章写的很好.显明易懂.
2007-03-28 11:06 | gangzi [未注册用户]

#30楼    回复  引用    

学了很多东西 ,关注
2007-03-28 17:02 | wuyisky [未注册用户]

#31楼    回复  引用    

真正的高手是用最生动的语言,最简单的例子,这是真正的“深入浅出”。

赞!!!


老兄,加油,继续哟
2007-03-29 13:49 | jack [未注册用户]

#32楼    回复  引用  查看