前言
鉴于架构的重要性,在一个项目开始的时候,在b站上看了一个使用Java做架构的案例,但由于自己对java的语言并不是很熟悉,加上编译环境也没有,因此,就有了下面一个念头,使用c++,将该课程的一个设计模式进行复现。
项目背景
使用c++将完成一个计算器的功能。用户通过在键盘上输入运算符和两个数字,点击确认计算出该运算的结果。实际效果如图:
计算器运行结果
0基础的玩法
这个版本就如同绝大多数初学者一样,在hello world的基础上做了一个只是实现功能的,没有任何。算法或者说逻辑可言。
#include <iostream>
#include <sstream>
#include <iomanip>
using namespace std;
/*
实现一个简易的计算器
版本:v1.0
时间:2022年1月21日14:30:12
开发者:董康乐
陕西 西安
TODO v1.0 引出需求(计算器的设计)
TODO 问题:可扩展性、可维护性、高耦合性。代码不规范问题
*/
void main()
{
std::cout << "第一版计算器v1.0" << std::endl;
/*-----------------------------------------*/
//定义字符和数字
char Operator;//加减乘除四种
double aaa = 0.0;
double bbb = 0.0;
std::cout << "请输入运算符:" << std::endl;
cin >> Operator;
std::cout << "请输入第一个数字:" << std::endl;
cin >> aaa;
std::cout << "请输入第二个数字:" << std::endl;
cin >> bbb;
/*-----------------------------------------*/
//判断运算符 并在特定的运算符内部完成运算
double ccc;
if (Operator == '+')
{
ccc = aaa + bbb;
}
else
{
if (Operator == '-')
{
ccc = aaa - bbb;
}
else
{
if (Operator == '*')
{
ccc = aaa * bbb;
}
else
{
if (Operator == '/')
{
ccc = aaa / bbb ;
}
}
}
}
/*-----------------------------------------*/
cout << "运算结果为:" << std::to_string(ccc);//输出结果
std::system("pause");
}
总结:上面的代码存着很多问题,除了本身代码的相关性特别强,如果需要增加或者修改功能,需要动大量的代码以外,其命名的规范性以及容错性都没有得到解决,比如说之前要是除法中分母为零的情况。为此我们有了下面的一个升级版v1.1版本。
1 解决容错和代码命名规范
#include <iostream>
#include <sstream>
#include <iomanip>
using namespace std;
/*
实现一个简易的计算器
版本:v1.1
时间:2022年1月21日14:30:12
开发者:董康乐
陕西 西安
TODO v1.0 引出需求(计算器的设计)
解决 代码不规范 代码容错性极差的问题
TODO 问题:可扩展性、可维护性、高耦合性
*/
void main()
{
std::cout << "第一版计算器v1.0" << std::endl;
/*-----------------------------------------*/
try{
//定义字符和数字
char Operator;//加减乘除四种
double Numb0 = 0.0;
double Numb1 = 0.0;
std::cout << "请输入运算符:" << std::endl;
cin >> Operator;
std::cout << "请输入第一个数字:" << std::endl;
cin >> Numb0;
std::cout << "请输入第二个数字:" << std::endl;
cin >> Numb1;
/*-----------------------------------------*/
//判断运算符 并在特定的运算符内部完成运算
double result;
if (Operator == '+')
{
result = Numb0 + Numb0;
}
else
{
if (Operator == '-')
{
result = Numb0 - Numb0;
}
else
{
if (Operator == '*')
{
result = Numb0 * Numb1;
}
else
{
if (Operator == '/')
{
if (Numb1 == 0)
{
throw "Division by zero condition!";
}
result = Numb0 / Numb1 ;
}
}
}
}
/*-----------------------------------------*/
cout << "运算结果为:" << std::to_string(result);//输出结果
}
catch (const char* msg) {
cerr << msg << endl;
}
std::system("pause");
}
总结:这个版本中我们使用了try、throw、catch等方式。对代码的容错性进了进行提高,并且将命名的规范性。进一步修改。
2 客户端分离
在这个版本中,我们将客户端进行了分离。将客户端的功能在单独的一个类内实现。这么多可扩充性。高融合性等得到一定程度的解决。
main.cpp
/*
实现一个简易的计算器
版本:v1.2
时间:2022年1月21日13:50:19
开发者:董康乐
陕西西安
TODO v1.2 引出需求(计算器的设计)
使用 面向对象的思维解决 分离客户端和业务端 只是解决部分问题
TODO 问题:可扩展性、可维护性、高耦合性 解决70%
*/
#include <iostream>
#include <sstream>
#include <iomanip>
#include "Operator.h"
using namespace std;
void main()
{
std::cout << "第一版计算器v1.2" << std::endl;
/*-----------------------------------------*/
try{
//客户端
//定义字符和数字
char operatorFunction;//加减乘除四种
double Numb0 = 0.0;
double Numb1 = 0.0;
std::cout << "请输入运算符:" << std::endl;
cin >> operatorFunction;
std::cout << "请输入第一个数字:" << std::endl;
cin >> Numb0;
std::cout << "请输入第二个数字:" << std::endl;
cin >> Numb1;
/*-----------------------------------------*/
//业务端
double result = Operator::countResult(operatorFunction, Numb0, Numb1);
cout << "运算结果为:" << std::to_string(result);//输出结果
}
catch (const char* msg) {
cerr << msg << endl;
}
std::system("pause");
}
实际运算的类为Operator类
其中:Operator.h
#pragma once
#include <iostream>
#include <sstream>
#include <iomanip>
using namespace std;
class Operator
{
public:
Operator();
~Operator();
static double countResult(char Operator, double Numb0, double Numb1);
};
Operator.cpp如下
/*
功能:业务端的分离
*/
#include "Operator.h"
Operator::Operator()
{
}
Operator::~Operator()
{
}
double Operator::countResult(char Operator, double Numb0, double Numb1)
{
//判断运算符 并在特定的运算符内部完成运算
double result;
if (Operator == '+')
{
result = Numb0 + Numb0;
}
else
{
if (Operator == '-')
{
result = Numb0 - Numb0;
}
else
{
if (Operator == '*')
{
result = Numb0 * Numb1;
}
else
{
if (Operator == '/')
{
if (Numb1 == 0)
{
throw "Division by zero condition!";
}
result = Numb0 / Numb1;
}
}
}
}
return result;
}
3解决高耦合性问题
使用了对虚函数的继承的方式来实现用一个虚函数,完成整体的架构,在不同的基层函数里面完成独立的运算,解决高高耦合性问题。在这个基础上,如果我们要增加或者减少一个单独的功能,那么就会变得非常容易。在某个单独实现的内内也可以独立的进行增加内容。
主函数main.cpp
/*
实现一个简易的计算器
版本:v1.3
时间:2022年1月21日16:08:48
开发者:董康乐
陕西西安
TODO v1.3 引出需求(计算器的设计)
使用 解决可扩展性、可维护性问题
TODO 问题:高耦合性 解决70%
*/
#include <iostream>
#include <sstream>
#include <iomanip>
#include "Operator.h"
#include "OperatorMul.h"
#include "OperatorAdd.h"
#include "OperatorSub.h"
#include "OperatorDiv.h"
using namespace std;
void main()
{
std::cout << "第一版计算器v1.3" << std::endl;
/*-----------------------------------------*/
try{
//客户端
//定义字符和数字
char operatorFunction;//加减乘除四种
double Numb0 = 0.0;
double Numb1 = 0.0;
std::cout << "请输入运算符:" << std::endl;
std::cin >> operatorFunction;
std::cout << "请输入第一个数字:" << std::endl;
cin >> Numb0;
std::cout << "请输入第二个数字:" << std::endl;
cin >> Numb1;
/*-----------------------------------------*/
//业务端
//只面向抽象,面向高层 ,面向接口的感觉
Operator *oper =NULL;
//Operator *oper = new OperatorAdd();
switch (operatorFunction)
{
case '+':
oper = new OperatorAdd();
break;
case '-':
oper = new OperatorSub();
break;
case '*':
oper = new OperatorMul();
break;
case '/':
oper = new OperatorDiv();
break;
}
oper->setNumb0(Numb0);
oper->setNumb1(Numb1);
cout << "运算结果为:" << std::to_string(oper->countResult());//输出结果
}
catch (const char* msg) {
cerr << msg << endl;
}
std::system("pause");
}
operator类
#pragma once
class Operator
{
public:
Operator();
~Operator();
double getNumb0();
double getNumb1();
void setNumb0(double);
void setNumb1(double);
virtual double countResult()=0;
private :
double numb0;
double numb1;
};
/*
首先定义规则。【计算器业务的规则】
抽象的:numb0、numb1、计算使用 依赖导致原则
*/
#include "Operator.h"
Operator::Operator()
{
}
Operator::~Operator()
{
}
double Operator::getNumb0()
{
return numb0;
}
double Operator::getNumb1()
{
return numb1;
}
void Operator::setNumb0(double numb0)
{
this->numb0 = numb0;
}
void Operator::setNumb1(double numb1)
{
this->numb1 = numb1;
}
/*
使用 单一职责原则
加法业务的逻辑 具体细节
*/
#include "OperatorAdd.h"
#include "Operator.h"
OperatorAdd::OperatorAdd()
{
}
OperatorAdd::~OperatorAdd()
{
}
//以后随着项目越来越多,这里面代码会越来越多
double OperatorAdd::countResult()
{
//此处略去100万行代码
//......
double result = getNumb0() + getNumb1();
return result;
}
#pragma once
#include "Operator.h"
class OperatorAdd :
public Operator
{
public:
OperatorAdd();
~OperatorAdd();
virtual double countResult();
};
#include "OperatorDiv.h"
#include "Operator.h"
OperatorDiv::OperatorDiv()
{
}
OperatorDiv::~OperatorDiv()
{
}
//以后随着项目越来越多,这里面代码会越来越多
double OperatorDiv::countResult()
{
//此处略去100万行代码
//......
double result;
if (getNumb1() != 0)
{
result = getNumb0() / getNumb1();
}
else
{
throw "Division by zero condition!";
}
return result;
}
#pragma once
#include "Operator.h"
class OperatorDiv :
public Operator
{
public:
OperatorDiv();
~OperatorDiv();
virtual double countResult();
};
#include "OperatorMul.h"
#include "Operator.h"
OperatorMul::OperatorMul()
{
}
OperatorMul::~OperatorMul()
{
}
//以后随着项目越来越多,这里面代码会越来越多
double OperatorMul::countResult()
{
//此处略去100万行代码
//......
double result = getNumb0() * getNumb1();
return result;
}
#pragma once
#include "Operator.h"
class OperatorMul :
public Operator
{
public:
OperatorMul();
~OperatorMul();
virtual double countResult();
};
#include "OperatorSub.h"
#include "Operator.h"
OperatorSub::OperatorSub()
{
}
OperatorSub::~OperatorSub()
{
}
//以后随着项目越来越多,这里面代码会越来越多
double OperatorSub::countResult()
{
//此处略去100万行代码
//......
double result = getNumb0() - getNumb1();
return result;
}
#pragma once
#include "Operator.h"
class OperatorSub :
public Operator
{
public:
OperatorSub();
~OperatorSub();
virtual double countResult();
};
4 使用设计模式解决耦合问题
在实际项目中,我们使用了设计模式中的简单工厂模式进行代码的重新架构。在此,我们为了增加代码的健壮性,做了输入之后,判断运算符的输入个数,因此做了两种模式,一种是将输入的信息转化成cha型,另外一种是将输入的信息转化成string型。String的目的是为了读取键盘中敲入了运算符的个数是多少个,如果超过一个的话,那么我们会其是他的运算符,个数超限。如果是一个的话,那么我们再进行判断,看他是不是我们本次工程中需求的四种运算符及加减乘除四种运算符,如果不是这四种运算符中的字符,我们将会报错。而在数据输入中,我们为了检验输入的数据是不是double型,我们做了进行判断,其cin.good()这样来判断字符中会不会出现其他非法字符的情况,保证运算中我们对整个运算进行了Throw监测目的是确保每次出现错误后会被cache抓到,保证cache的抓取过程中我们抓取的几种类型,一种类型是int类型,一种类型则是string类型。整个代码在main函数中暴露出来的都是高层的抽象的。具体的实现则是在它的内部实现的,给用户暴露的是相对比较简单的内容。整体代码如下:
/*
实现一个简易的计算器
版本:v1.4
时间:2022年1月21日17:04:49
开发者:董康乐
陕西西安
TODO v1.4 引出需求(计算器的设计)
使用 设计模式 简单工厂
TODO 解决耦合度高的问题
更新:try catch throw
时间:2022年1月21日21:52:24
*/
#include <iostream>
#include <sstream>
#include <iomanip>
#include "OperatorFactory.h"
using namespace std;
int check(char buf, int len)
{
int i;
char tmp[1024];
memcpy(tmp, &buf, len);
for (i = 0; i<len; i++){
if ((tmp[i]>0x39 || tmp[i]<0x30) && (tmp[i] != '.'))
return 1;
}
return 0;
}
void main()
{
std::cout << "第一版计算器v1.4" << std::endl;
/*-----------------------------------------*/
//客户端
//定义字符和数字
double Numb0 = 0.0;
double Numb1 = 0.0;
try{
if (0)
{
char operatorFunction;//加减乘除四种
std::cout << "请输入运算符:" << std::endl;
std::cin >> operatorFunction;
if ((operatorFunction != '+'&&operatorFunction != '-'
&&operatorFunction != '/' && operatorFunction != '*'))
{
throw "运算符输入错误!";
}
std::cout << "请输入第一个数字:" << std::endl;
cin >> Numb0;
//if ()
std::cout << "请输入第二个数字:" << std::endl;
cin >> Numb1;
/*-----------------------------------------*/
//业务端//只面向抽象,面向高层 ,面向接口的感觉 //此工厂模式至面向高层 抽象
Operator *oper = OperatorFactory::CreatOperator(operatorFunction);
oper->setNumb0(Numb0);
oper->setNumb1(Numb1);
cout << "运算结果为:" << std::to_string(oper->countResult());//输出结果
}
else
{
string operatorFunction;//加减乘除四种
std::cout << "请输入运算符:" << std::endl;
std::cin >> operatorFunction;
if ((operatorFunction.size())>1)//判断运算符个数是否过多
{
throw "运算符个数过多!";
}
if ((operatorFunction != "+"&&operatorFunction != "-" //判断字符串是我为运算符
&&operatorFunction != "/" && operatorFunction != "*"))
{
throw "运算符输入错误!";
}
std::cout << "请输入第一个数字:" << std::endl;
cin >> Numb0;
//判断输入的是否为数字(如果含有两个小数点,第二个小数点及后面的数字后默认为0)
if (!cin.good()){
throw "输入的第一个数字为非数字!";
}
std::cout << "请输入第二个数字:" << std::endl;
cin >> Numb1;
if (!cin.good()){
throw "输入的第二个数字为非数字!";
}
/*-----------------------------------------*/
//业务端//只面向抽象,面向高层 ,面向接口的感觉 //此工厂模式至面向高层 抽象
Operator *oper = OperatorFactory::CreatOperator(operatorFunction);
oper->setNumb0(Numb0);
oper->setNumb1(Numb1);
cout << "运算结果为:" << std::to_string(oper->countResult());//输出结果
}
}
//此处的catch监测的是运算符输入是否正确,
//如果错误,则被判断运算符的throw监测,并输出字符串类型的异常信息,
//这个catch就会抓住,并显示这个字符串
catch (const char* e) {
cout << e << endl;
}
//此处的catch监测的是除法运算中分母为零的错误,
//如果错误,则被判断分母为零的throw监测(在OperatorDiv类中监测),并输出int类型的异常信息(0),
//这个catch就会抓住,并显示这个字符串
catch (int e) {
cout << "发生异常!监测到除法运算中分母为: " <<to_string(e) << endl;
}
//对于其他不好说的错误,则在这个catch中被检测
catch (...) {
cout << "发生其他未知异常!" << endl;
}
std::system("pause");
}
/*
关于try catch throw
一、基本理论
try用来保护特定行内的代码,确保其正确,一旦有异常,会被检测到
throw检测器所在上面的所有代码,
catch 监测异常的情况;根据不同的throw类型,来检测不同的异常;对于三不管的代码,则是catch(...)来输出
其中,一致的catch顺序只能是顺序执行,因此我们通常将catch(...)放在最后,把最后一道关
try{
//此处有十万行代码-------代码段1
throw xxx(类型);//此处的throw只负责监控上面10w行代码,下面的一百万行代码2,还是“黑跑”
//此处有一百万行代码-------代码段2
throw yyy(类型);//此处的throw只负责监控上面10w行代码1和一百万行代码2,下面的1000w行代码不保护
//此处有一千万行代码-------代码段3
throw zzz(类型);
}
catch(xxx(类型))
{
}
catch(yyy(类型))
{
}
catch(zzz(类型))
{
}
catch(...)//检测其他方面的
{
}
*/
/*
首先定义规则。【计算器业务的规则】
抽象的:numb0、numb1、计算使用 依赖导致原则
*/
#include "Operator.h"
Operator::Operator()
{
}
Operator::~Operator()
{
}
double Operator::getNumb0()
{
return numb0;
}
double Operator::getNumb1()
{
return numb1;
}
void Operator::setNumb0(double numb0)
{
this->numb0 = numb0;
}
void Operator::setNumb1(double numb1)
{
this->numb1 = numb1;
}
#pragma once
class Operator
{
public:
Operator();
~Operator();
double getNumb0();
double getNumb1();
void setNumb0(double);
void setNumb1(double);
virtual double countResult()=0;
private:
double numb0;
double numb1;
};
#include "OperatorFactory.h"
#include <iostream>
#include <sstream>
#include <iomanip>
#include "Operator.h"
#include "OperatorMul.h"
#include "OperatorAdd.h"
#include "OperatorSub.h"
#include "OperatorDiv.h"
#include <iostream>
#include <sstream>
#include <iomanip>
//前面应用了 依赖倒置原则 + 单一职责原则
//再次应用 简单工厂模式 来解决 客户端的解耦合
OperatorFactory::OperatorFactory()
{
}
OperatorFactory::~OperatorFactory()
{
}
//给用户暴露的永远是高层和抽象,而不是暴露细节,
//如果暴露工具类,那就是工具类了,就不再是我们这次的工厂模式
Operator* OperatorFactory::CreatOperator(char oper_char )
{
Operator *oper = NULL;
switch (oper_char)
{
case '+':
oper = new OperatorAdd();
break;
case '-':
oper = new OperatorSub();
break;
case '*':
oper = new OperatorMul();
break;
case '/':
oper = new OperatorDiv();
break;
}
return oper;
}
Operator* OperatorFactory::CreatOperator(string oper_string)
{
Operator *oper = NULL;
char oper_char = oper_string[0];
switch (oper_char)
{
case '+':
oper = new OperatorAdd();
break;
case '-':
oper = new OperatorSub();
break;
case '*':
oper = new OperatorMul();
break;
case '/':
oper = new OperatorDiv();
break;
}
return oper;
}
#pragma once
#include <iostream>
#include <sstream>
#include <iomanip>
#include "Operator.h"
#include "OperatorMul.h"
#include "OperatorAdd.h"
#include "OperatorSub.h"
#include "OperatorDiv.h"
using namespace std;
class OperatorFactory
{
public:
OperatorFactory();
~OperatorFactory();
/*传入 一个计算器的操作符号*/
static Operator* CreatOperator(char);
static Operator* CreatOperator(string);
};
/*
使用 单一职责原则
加法业务的逻辑 具体细节
*/
#include "OperatorAdd.h"
#include "Operator.h"
OperatorAdd::OperatorAdd()
{
}
OperatorAdd::~OperatorAdd()
{
}
//以后随着项目越来越多,这里面代码会越来越多
double OperatorAdd::countResult()
{
//此处略去100万行代码
//......
double result = getNumb0() + getNumb1();
return result;
}
#pragma once
#include "Operator.h"
class OperatorAdd :
public Operator
{
public:
OperatorAdd();
~OperatorAdd();
virtual double countResult();
};
#include "OperatorDiv.h"
#include "Operator.h"
OperatorDiv::OperatorDiv()
{
}
OperatorDiv::~OperatorDiv()
{
}
//以后随着项目越来越多,这里面代码会越来越多
double OperatorDiv::countResult()
{
//此处略去100万行代码
//......
double result;
if (getNumb1() != 0)
{
result = getNumb0() / getNumb1();
}
else
{
throw 0;//如果分母为零,则会再次被throw监测到,经这个int类型的变量甩出,并被第二个catch(int)监测
}
return result;
}
#pragma once
#include "Operator.h"
class OperatorDiv :
public Operator
{
public:
OperatorDiv();
~OperatorDiv();
virtual double countResult();
};
#include "OperatorMul.h"
#include "Operator.h"
OperatorMul::OperatorMul()
{
}
OperatorMul::~OperatorMul()
{
}
//以后随着项目越来越多,这里面代码会越来越多
double OperatorMul::countResult()
{
//此处略去100万行代码
//......
double result = getNumb0() * getNumb1();
return result;
}
#pragma once
#include "Operator.h"
class OperatorMul :
public Operator
{
public:
OperatorMul();
~OperatorMul();
virtual double countResult();
};
#include "OperatorSub.h"
#include "Operator.h"
OperatorSub::OperatorSub()
{
}
OperatorSub::~OperatorSub()
{
}
//以后随着项目越来越多,这里面代码会越来越多
double OperatorSub::countResult()
{
//此处略去100万行代码
//......
double result = getNumb0() - getNumb1();
return result;
}
#pragma once
#include "Operator.h"
class OperatorSub :
public Operator
{
public:
OperatorSub();
~OperatorSub();
virtual double countResult();
};