问题引入
请用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);
}
}
这样写的缺点
- 变量命名不规范
- 该判断分支会使计算机做无用功
- 被除数缺少规范
稍加修改
A改为strNumberA
B改为strOperate
C改为strNumberB
if改为switch结构
除之前加上不等于0的判断
修改后还存在什么问题
编码过于结构化,未能体现面向对象的思想,具体体现在
- 不易维护(修改任何一个计算细节都会影响整个系统)
- 不易拓展(拓展时会先破坏这个类)
- 不易复用
面向对象与结构化编程的优点
- 可维护
- 可复用
- 可拓展
- 灵活性好
问题思考
面向对象编程的三大特性
学习了面向对象,应考虑如何通过封装、继承、多态把程序的耦合度降低,如何使用设计模式使得程序更加的灵活,容易修改,并且易于复用
复制和复用
编程有一原则,就是用尽可能的办法去避免重复,考虑代码,有哪些是和控制台无关的,而只是和计算器有关的。如何让计算和显示分离,如何让业务逻辑和界面逻辑分离等。
封装时考虑紧耦合与松耦合
上述程序在修改时存在不安全性,当增加一个计算功能组件时,需要将整个类重新编译,修改时可能出错。类内耦合过紧,不利于维护。
工厂模式
工厂模式简介
工厂模式专门负责将大量有共同接口的类实例化。工厂模式可以动态决定将哪个类实例化,不必事先知道每次要实例化哪个类。工厂模式有以下几种形态:简单工厂(SimpleFactory)模式、工厂方法模式(FactoryMethod)模式、抽象工厂(AbstractFactory)模式
简单工厂模式
SimpleFactory模式根据提供给它的数据,返回几个有可能类中的一个类的实例。通常它返回的类都有一个公共的父类和公共的方法。
工厂角色Creator(简单工厂类):工厂类在客户端的直接控制下创建产品对象。
抽象产品角色Product(产品类):定义简单工厂创建的对象的父类或它们共同拥有的接口。可以是一个类、抽象类或接口。
具体产品角色ConcreteProduct(产品衍生类):定义工厂具体加工出来的对象。
简单工厂模式实现计算器
抽象产品角色

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;
}
}
具体产品-加法类
public class OperationAdd:Operation
{
public override double GetResult()
{
double result = 0;
result = NumberA + NumberB;
return 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(NumberB==0)
{
throw new Exception("除数不能为0.");
}
result = NumberA / NumberB;
return result;
}
}
工厂类
public class OperationFactory
{
public static Operation createOperation(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;
}
}
客户端
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnj_Click(object sender, EventArgs e)
{
string ysf = cbB.Text;
Operation oper;
oper = OperationFactory.createOperation(ysf);
oper.NumberA = Convert.ToDouble(tbA.Text);
oper.NumberB = Convert.ToDouble(tbB.Text);
double result = oper.GetResult();
trt.Text =result.ToString();
}
private void button1_Click(object sender, EventArgs e)
{
cbB.Text = "";
tbA.Text = "";
tbB.Text = "";
trt.Text = "";
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
客户端界面
典型疑问
上面实例中的简单工厂看起来不就是把客户端里面的“new OperationAdd()”移到了简单工厂里面吗?
答:接口是用来封装隔离具体的实现的,目标就是不要让客户端知道封装体内部的具体实现。简单工厂的位置是位于封装体内的,所以简单工厂知道具体类的实现是没有关系的。对于客户端来说,只是知道了接口和简单工厂。
简单工厂的优点与缺点
优点:
-
工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品。简单工厂模式通过这种做法实现了对责任的分割。
缺点: -
当产品类有复杂的等级结构时,工厂类只有自己,以不变应万变,就是模式的缺点。因为工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
-
同时,系统拓展困难,一旦添加新产品就不得不修改工厂逻辑,有可能造成工厂逻辑过于复杂。
-
另外,简单工厂模式通常使用静态工厂方法,这使得无法由子类继承,造成工厂角色无法形成基于继承的等级结构
简单工厂的本质
选择实现
应用场景
- 如果想要完全封装隔离具体实现,让外部类只能通过接口来操作封装体,则可以选用简单工厂。让客户端通过工厂来获取相应的接口,而无需关心具体的实现。
- 如果想要八对外创建对象的职责集中管理和控制,可以选择简单工厂