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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

一道经典的面试题如下

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

大部分人的代码如下

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)
	{
		cout << "您的输入有误" << endl;
	}

	return 0;
}

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

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

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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6