一道经典面试题透彻理解面向对象编程思想和简单工厂模式

一道经典的面试题如下:

用一种面向对象编程语言实现一个计算器来满足整数的加减乘除运算。

大部分人的代码如下:

1.0版本

#include<iostream>
using namespace std;
#include<string>
//1.0版本
int main()
{
	int num1 = 0;
	int num2 = 0;
	string ope = " ";
	int re = 0;
	cout << "请输入左操作数:" << endl;
	cin >> num1;
	cout << "请输入右操作数:" << endl;
	cin >> num2;
	cout << "请输入操作符" << endl;
	cin >> ope;

	if (ope == "+")
	{
		re = num1 + num2;
	}
	if (ope == "-")
	{
		re = num1 - num2;
	}
	if (ope == "*")
	{
		re = num1 * num2;
	}
	if (ope == "/")
	{
		re = num1 / num2;
	}
	
	cout << re << endl;

	return 0;
}

这段代码,主要出现两个问题。首先,该段代码没有使用if-else-if语句,而是直接使用4段if语句,这意味着每个条件都要进行判断,计算机只要执行该程序就一定会做3次无用功,其次,在执行加减乘除的时候,如果客户输入的操作数是非数字形式,输入的操作符是非规定符号,以及在执行除法的时候,如果客户右操作数输入的是0怎么办,也就是说该段代码没有考虑到异常情况。

那么优化后的代码如下:

2.0版本

#include<iostream>
using namespace std;
#include<string>

class opeException
{
public:
	void getMessage()
	{
		cout << "您的输入有误!" << endl;
	}
};

//判断一个字符串是不是数字
bool isStringNum(string& s)
{
	bool flag = true;
	for (auto e : s)
		if (!isdigit(e))
		{
			flag = false;
			break;
		}	
	return flag;
}

int main()
{
	string num1 = "0";
	string num2 = "0";
	string ope = " ";

	try
	{
		cout << "请输入左操作数:" << endl;
		cin >> num1;
		if (!isStringNum(num1))
			throw opeException();

		cout << "请输入右操作数:" << endl;
		cin >> num2;
		if (!isStringNum(num2))
			throw opeException();

		cout << "请输入操作符" << endl;
		cin >> ope;
		if (ope != "+" && ope != "-" && ope != "*" && ope != "/")
			throw opeException();
		

		if (ope == "+")
		{
			cout<< stoi(num1) + stoi(num2)<<endl;
		}
		else if (ope == "-")
		{
			cout << stoi(num1) - stoi(num2) << endl;
		}
		else if (ope == "*")
		{
			cout << stoi(num1) * stoi(num2) << endl;
		}
		else if (ope == "/")
		{
			if (stoi(num2) != 0)
			{
				cout << stoi(num1) / stoi(num2) << endl;
			}
			else
				cout << "除数不能为0" << endl;
			
		}
	}
	catch (opeException ex)
	{
		ex.getMessage();
	}

	return 0;
}

解决了if语句以及异常情况之后,这段代码对于面试官来说就是合格的了吗?

答案显然是否定的,因为面试官让我们用面向对象语言来实现一个计算器,为何不使用C语言等面向过程语言来实现?因为他实际是想让我们在编程中体现面向对象思想,即封装、继承和多态思想。虽然我们用的是面向对象语言,但是上面的编程思想依旧是面向过程的,利用面向过程思想写出来的代码,不易维护,不易扩展,不易复用,会经常出现因为客户改需求,导致对程序进行大刀阔斧动手术的情况。

我们在编程中应该注重面向对象思想,利用封装、继承和多态,将程序之间的耦合度降低。

现在我们就利用面向对象思想来修改这个计算器程序。

首先,我们应当将业务逻辑和界面逻辑分开,即让计算和显示分开,这样计算是计算,显示是显示,计算和显示之间的耦合度就降低了。

3.0版本

#include<iostream>
using namespace std;
#include<string>

//业务逻辑
//异常类用于处理异常情况
class opeException
{
public:
	void getMessage()
	{
		cout << "您的输入有误!" << endl;
	}
};

//运算类用于处理运算
class Operation
{
public:
	Operation(string& _num1, string& _num2, string& _ope) :num1(_num1), num2(_num2), ope(_ope){}
		//获取运算结果
	int getResult()
	{
		if (!(isStringNum(num1) && isStringNum(num2) && (ope == "+" || ope == "-" || ope == "*" || ope == "/")))
			throw opeException();
		if (ope == "+")
		{
			re = stoi(num1) + stoi(num2);
		}
		else if (ope == "-")
		{
			re = stoi(num1) - stoi(num2);
		}
		else if (ope == "*")
		{
			re = stoi(num1) * stoi(num2);
		}
		else if (ope == "/")
		{
			if (stoi(num2) != 0)
			{
				re = stoi(num1) / stoi(num2);
			}
			else
				throw opeException();
		}

		return re;
	}
private:
	int re;
	string num1;
	string num2;
	string ope;

	//判断一个字符串是不是数字
	bool isStringNum(string& s)
	{
		bool flag = true;
		for (auto e : s)
			if (!(isdigit(e)))
			{
				flag = false;
				break;
			}
		return flag;
	}
};

//界面逻辑
int main()
{
	try
	{
		string _num1 = " ";
		string _num2 = " ";
		string _ope = " ";

		cout << "请输入左操作数:" << endl;
		cin >> _num1;


		cout << "请输入右操作数:" << endl;
		cin >> _num2;

		cout << "请输入操作符" << endl;
		cin >> _ope;

		Operation operation(_num1, _num2, _ope);
		cout << operation.getResult() << endl;
	}
	catch (opeException &ex)
	{
		ex.getMessage();
	}

	return 0;
}

实际上,将运算和界面分离,将运算所需要的操作数,运算符和运算方法封装成一个类,已经体现了面向对象中的封装思想,但这远远还不够。

如果我们要进行除法运算,还得在条件语句中判断是否是加法运算,然后判断是否是减法运算,再判断是否是乘法运算,效率依然比较低,而且如果我们要增加一个运算,又得增加一个if语句进行判断,那我们要增加10个运算,岂不是要增加10个if语句进行判断,而且还有可能不小心把之前的if语句修改,这就是面向过程思想的缺陷,极不易维护,不易拓展,且不易复用!而且,getResult函数中连续的if语句又是一个典型的面向过程的思想,这也提醒我们是不是可以把if语句中连续的运算判断封装成一个类呢?

因此我们考虑不仅仅把运算封装成一个类,把具体的各个运算也封装成一个类,需要哪个运算,就创建哪个运算的对象。

4.0版本

//4.0版本

#include<iostream>
using namespace std;
#include<string>

//业务逻辑
 
//异常类用于处理异常情况
class opeException
{
public:
	void getMessage()
	{
		cout << "您的输入有误!" << endl;
	}
};

//运算类
class Operation
{	
	//判断一个字符串是不是数字
	bool isStringNum(string& s)
	{
		bool flag = true;
		for (auto e : s)
			if (!(isdigit(e)))
			{
				flag = false;
				break;
			}
		return flag;
	}

protected:
//判断输入的操作数和操作符是否有误
	bool isError(string& _strNum1, string& _strNum2, string& _ope)
	{
		if (!(Operation::isStringNum(_strNum1) && Operation::isStringNum(_strNum2) && (_ope == "+" || _ope == "-" || _ope == "*" || _ope == "/")))
		{
			return false;
		}
	}
public:
	virtual int getResult() = 0;
};

//加法运算类
class addOperation:public Operation
{
private:
	string strNum1;
	string strNum2;
	string ope;
	int re;
public:
	addOperation(string& _strNum1, string& _strNum2, string& _ope) :strNum1(_strNum1),strNum2(_strNum2),ope(_ope),re(0) {}
	virtual int getResult() override
	{
		if (!isError(strNum1, strNum2, ope))
			throw opeException();
		else
			re = stoi(strNum1) + stoi(strNum2);

		return re;
	}
};

//减法运算类
class subOperation :public Operation
{
private:
	string strNum1;
	string strNum2;
	string ope;
	int re;
public:
	subOperation(string& _strNum1, string& _strNum2, string& _ope) :strNum1(_strNum1), strNum2(_strNum2), ope(_ope), re(0) {}
	virtual int getResult() override
	{
		if (!isError(strNum1, strNum2, ope))
			throw opeException();
		else
			re = stoi(strNum1) - stoi(strNum2);

		return re;
	}
};

//乘法运算类
class mulOperation :public Operation
{
private:
	string strNum1;
	string strNum2;
	string ope;
	int re;
public:
	mulOperation(string& _strNum1, string& _strNum2, string& _ope) :strNum1(_strNum1), strNum2(_strNum2), ope(_ope), re(0) {}
	virtual int getResult() override
	{
		if (!isError(strNum1, strNum2, ope))
			throw opeException();
		else
			re = stoi(strNum1) * stoi(strNum2);

		return re;
	}
};

//除法运算类
class divOperation :public Operation
{
private:
	string strNum1;
	string strNum2;
	string ope;
	int re;
public:
	divOperation(string& _strNum1, string& _strNum2, string& _ope) :strNum1(_strNum1), strNum2(_strNum2), ope(_ope), re(0) {}
	virtual int getResult() override
	{
		if (!isError(strNum1, strNum2, ope))
			throw opeException();
		else if (stoi(strNum2) != 0)
			re = stoi(strNum1) / stoi(strNum2);
		else
			throw opeException();

		return re;
	}
};

//界面逻辑
int main()
{
	try
	{
		string _strNum1 = " ";
		string _strNum2 = " ";
		string _ope = " ";

		cout << "请输入左操作数:" << endl;
		cin >> _strNum1;

		cout << "请输入右操作数:" << endl;
		cin >> _strNum2;

		cout << "请输入操作符:" << endl;
		cin >> _ope;

		if (_ope == "+")
		{
			addOperation addoperation(_strNum1, _strNum2, _ope);
			cout << addoperation.getResult() << endl;
		}
		else if (_ope == "-")
		{
			subOperation suboperation(_strNum1, _strNum2, _ope);
			cout << suboperation.getResult() << endl;
		}
		else if (_ope == "*")
		{
			mulOperation muloperation(_strNum1, _strNum2, _ope);
			cout << muloperation.getResult() << endl;
		}
		else if (_ope == "/")
		{
			divOperation muloperation(_strNum1, _strNum2, _ope);
			cout << muloperation.getResult() << endl;
		}
		else
			cout << "您的输入有误!" << endl;
			
	}
	catch (opeException ex)
	{
		cout << "您的输入有误" << endl;
	}

	return 0;
}

本版本计算器代码虽然充分利用了面向对象的三大特性:封装、继承和多态,但是还是有点问题,因为最后判断到底实例化哪种运算符,是通过多个if语句判断的,这是一个典型的面向过程思想,而且该思想将界面逻辑和业务逻辑又混合了,所以我们在新版本的代码中要修改这种面向过程思想,这里我们可以利用一个设计模式,即简单工厂模式,其核心思想就是考虑用单独的类根据实际情况来创造不同类的实例。

5.0版本

#include<iostream>
using namespace std;
#include<string>

//业务逻辑

//异常类用于处理异常情况
class opeException
{
public:
	void getMessage()
	{
		cout << "您的输入有误!" << endl;
	}
};

//运算类
class Operation
{
	//判断一个字符串是不是数字
	bool isStringNum(string& s)
	{
		bool flag = true;
		for (auto e : s)
			if (!(isdigit(e)))
			{
				flag = false;
				break;
			}
		return flag;
	}

protected:
	bool isError(string& _strNum1, string& _strNum2, string& _ope)
	{
		if (!(Operation::isStringNum(_strNum1) && Operation::isStringNum(_strNum2) && (_ope == "+" || _ope == "-" || _ope == "*" || _ope == "/")))
		{
			return false;
		}
	}
public:
	virtual int getResult() = 0;
};

//加法运算类
class addOperation :public Operation
{
private:
	string strNum1;
	string strNum2;
	string ope;
	int re;
public:
	addOperation(string& _strNum1, string& _strNum2, string& _ope) :strNum1(_strNum1), strNum2(_strNum2), ope(_ope), re(0) {}
	virtual int getResult() override
	{
		if (!isError(strNum1, strNum2, ope))
			throw opeException();
		else
			re = stoi(strNum1) + stoi(strNum2);

		return re;
	}
};

//减法运算类
class subOperation :public Operation
{
private:
	string strNum1;
	string strNum2;
	string ope;
	int re;
public:
	subOperation(string& _strNum1, string& _strNum2, string& _ope) :strNum1(_strNum1), strNum2(_strNum2), ope(_ope), re(0) {}
	virtual int getResult() override
	{
		if (!isError(strNum1, strNum2, ope))
			throw opeException();
		else
			re = stoi(strNum1) - stoi(strNum2);

		return re;
	}
};

//乘法运算类
class mulOperation :public Operation
{
private:
	string strNum1;
	string strNum2;
	string ope;
	int re;
public:
	mulOperation(string& _strNum1, string& _strNum2, string& _ope) :strNum1(_strNum1), strNum2(_strNum2), ope(_ope), re(0) {}
	virtual int getResult() override
	{
		if (!isError(strNum1, strNum2, ope))
			throw opeException();
		else
			re = stoi(strNum1) * stoi(strNum2);

		return re;
	}
};

//除法运算类
class divOperation :public Operation
{
private:
	string strNum1;
	string strNum2;
	string ope;
	int re;
public:
	divOperation(string& _strNum1, string& _strNum2, string& _ope) :strNum1(_strNum1), strNum2(_strNum2), ope(_ope), re(0) {}
	virtual int getResult() override
	{
		if (!isError(strNum1, strNum2, ope))
			throw opeException();
		else if (stoi(strNum2) != 0)
			re = stoi(strNum1) / stoi(strNum2);
		else
			throw opeException();

		return re;
	}
};

//运算工厂类
class OpeFactory
{
public:
	Operation& choose(string &_strNum1,string &_strNum2,string &_ope)
	{

		if (_ope == "+")
		{
			operation = new addOperation(_strNum1, _strNum2, _ope);
		}
		else if (_ope == "-")
			operation = new subOperation(_strNum1, _strNum2, _ope);
		else if (_ope == "*")
			operation = new mulOperation(_strNum1, _strNum2, _ope);
		else if (_ope == "/")
		{
			operation = new divOperation(_strNum1, _strNum2, _ope);
		}
		else
			operation = nullptr;
			
		return *operation;
	}
	
private:
	Operation* operation;
};

//界面逻辑
int main()
{
	try
	{
		string _strNum1 = " ";
		string _strNum2 = " ";
		string _ope = " ";

		cout << "请输入左操作数:" << endl;
		cin >> _strNum1;

		cout << "请输入右操作数:" << endl;
		cin >> _strNum2;

		cout << "请输入操作符:" << endl;
		cin >> _ope;

		OpeFactory factory;

		Operation* re = &factory.choose(_strNum1, _strNum2, _ope);
		if (re != nullptr)
			cout << (*re).getResult() << endl;
		else
			cout << "您的输入有误!" << endl;

	}
	catch (opeException ex)
	{
		ex.getMessage();
	}

	return 0;
}

5.0版本的计算器封装了7个类,其中四个加减乘除运算类继承自运算类,四则运算类根据自己的需要实现其基类的纯虚函数(多态),将界面逻辑与业务逻辑完美分隔开,充分利用了面向对象编程思想。

利用了面向对象编程思想的代码就是更加容易维护、拓展、复用。以后我们需要更改加法运算只需要更改AddOperation类即可,如果要修改界面,就直接去改界面,也不会影响到运算,以后我们想添加其他运算,比如开平方运算,立方运算等,只需要添加相应的子类,然后在工厂类中添加相应的分支即可,对其他代码没有任何影响。而如果是利用了面向过程思想的代码,我们在修改,拓展的时候,就要去程序中找到相应的位置,然后叠加代码,这很有可能会影响到前面的代码!

上述代码中,我们利用了简单工厂模式,我们已经知道了简单工厂模式的核心思想,但同时也要了解它的优缺点和使用场景。

优点:

工厂类是整个模式的关键,包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象,通过使用工厂类,外界可以从直接创建具体对象的尴尬局面摆脱出来,仅仅需要负责使用对象就可以了,而不必管这些对象究竟如何创建及如何组织的,明确了各自的职责和权利,将业务逻辑和界面逻辑进行了分离,有利于整个软件体系结构的优化。

缺点:

由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。

当系统中的类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求,这种对条件的判断和对具体类的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利;这些缺点在工厂方法模式中得到了一定的克服。

使用场景:

由于简单工厂很容易违反高内聚责任分配原则,因此一般只在工厂类负责创建的对象比较少的这种简单情况下使用。

本篇博客参考资料:《大话设计模式》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子非鱼Swx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值