• 大话设计模式有感


    大话设计模式有感

    计算器控制台程序

    最简单的计算器控制台程序很简单,通过打印文本跟用户交互,达到计算出结果的目的,代码如下:

    int main()
    {
    	double A, D, C;
    	string B;
    	cout<< "请输入数字A: ";
    	cin >> A;
    	cout << "请选择运算符号(+、-、*、/): ";
    	cin >> B;
    	cout << "请输入数字B: ";
    	cin >> C;
    	if (B == "+")
    		D = A + C;
    	if (B == "-")
    		D = A - C;
    	if (B == "*")
    		D = A * C;
    	if (B == "/")
    		D = A / C;
    	cout << "结果是: " << D << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    以上的代码非常的简单,所以需要改进的地方也有很多。

    1. 命名的不规范,虽然这只是一个简单的计算器控制台程序,但是良好的习惯是需要养成的。
    2. 判断分支,虽然根据字符可以准确的进入到每一个分支,但是每一个if都需要判断,导致每次都做了3次无用功
    3. 基础的逻辑。当除数为0时怎么办,用户输入的不是数字怎么办。
    int main()
    {
    	double numberA, numberB;
    	double result;
    	string strOperate;
    	map operatorParam = {
    		{"+", 1},
    		{"-", 2},
    		{"*", 3},
    		{"/", 4},
    	};
    	try
    	{
    		cout << "请输入数字A: ";
    		cin >> numberA;
    		while (cin.fail())
    		{
    			cin.clear();
    			cin.ignore();
    			cout << "请输入数字A:";
    			cin.ignore();
    			cin >> numberA;
    		}
    		cout << "请选择运算符号(+、-、*、/): ";
    		cin >> strOperate;
    		cout << "请输入数字B: ";
    		cin >> numberB;
    		while (cin.fail())
    		{
    			cin.clear();
    			cin.ignore();
    			cout << "请输入数字B:";
    			cin.ignore();
    			cin >> numberA;
    		}
    		int caseValue = operatorParam[strOperate];
    		switch (caseValue)
    		{
    		case 1:
    			result = numberA + numberB;
    			break;
    		case 2:
    			result = numberA - numberB;
    			break;
    		case 3:
    			result = numberA * numberB;
    			break;
    		case 4:
    			if (numberB != 0.0f)
    				result = numberA / numberB;
    			else
    				throw "除数不能为 0 ";
    			break;
    		default:
    			throw "请选择运算符号(+、-、*、/) ";
    			break;
    		}
    		cout << "结果是: " << result << endl;
    	}
    	catch (const char *e)
    	{
    		cout << e;
    	}
    	
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    现在这份代码虽然比前面更加复杂一点,但是涵盖的东西也不少,麻雀虽小,五脏俱全。可是缺点也很明显,这个程序要求用户输入两个数和运算符号进行运算,得到结果,这本身没有错,但是这样的思维使得我们只能够满足当前的需求,程序不容易维护、不容易拓展、更不容易服用,从而达不到高质量的代码要求。
    C++作为一种面向对象的语言,可以面向对象编程。以印刷术为例,如果一句一句的雕刻印刷,那么,一句话就要雕刻一版,如果发生两句话中只有一个字不一样,那么就要重新再雕刻一个。但是如果使用活字印刷,则只需要再雕刻一个字即可。

    1. 要改,只需要改要改的字,这就是可维护
    2. 可以重复使用这些字。这就是可复用
    3. 若要加字,只需要另刻字加入即可,这就是可拓展
    4. 随意组排,这就是灵活性好

    所以我们需要考虑的就是通过封装、继承、多态把程序的耦合度降低,使得程序更加的灵活、容易修改、并且易于复用。

    复制 vs 复用

    如果在写一个Windows的计算器,这个代码就不能复用了,但是可以重新写一个,使用ctrl+C 和 ctrl+v,这其实是一个不好的编码习惯(宏观),因为当重复代码多到一定的程度,维护就是一场灾难,也就是现在说的屎山代码。如果将业务逻辑和界面逻辑分开,让它们之间的耦合度降低,这样就容易维护和扩展了。下面尝试一下:
    首先封装运算类

    #Operation.h
    #pragma once
    #include 
    #include 
    using namespace std;
    class Operation {
    public:
    	static map operatorParam;
    
    public:
    	static double GetResult(double numberA, double numberB, string strOperator);
    	
    };
    
    #Operation.cpp
    #include 
    #include 
    #include 
    #include "Operation.h"
    using namespace std;
    
    map Operation::operatorParam = {
    		{"+", 1},
    		{"-", 2},
    		{"*", 3},
    		{"/", 4},
    };
    
    double Operation::GetResult(double numberA, double numberB, string strOperator)
    {
    	double result;//默认初始化
    	int caseValue = Operation::operatorParam[strOperator];
    	try
    	{
    		switch (caseValue)
    		{
    		case 1:
    			result = numberA + numberB;
    			break;
    		case 2:
    			result = numberA - numberB;
    			break;
    		case 3:
    			result = numberA * numberB;
    			break;
    		case 4:
    			if (numberB != 0.0f)
    				result = numberA / numberB;
    			else
    				throw "除数不能为 0 ";
    			break;
    		default:
    			throw "请选择运算符号(+、-、*、/) ";
    			break;
    		}
    	}
    	catch (const char* e)
    	{
    		cout << e;
    	}
    	return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    客户端代码:

    #include "Operation.h"
    int main()
    {
    	double numberA, numberB;
    	double result;
    	string strOperate;
    	try
    	{
    		cout << "请输入数字A: ";
    		cin >> numberA;
    		while (cin.fail())
    		{
    			cin.clear();
    			throw "输入的不是数字,请重新开始";
    		}
    		cout << "请选择运算符号(+、-、*、/): ";
    		cin >> strOperate;
    		cout << "请输入数字B: ";
    		cin >> numberB;
    		while (cin.fail())
    		{
    			cin.clear();
    			throw "输入的不是数字,请重新开始";
    		}
    		result = Operation::GetResult(numberA, numberB, strOperate);
    		cout << "结果是: " << result << endl;
    	}
    	catch (const char* e)
    	{
    		cout << e;
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    这样,如果我要开发需要运算的类,那我就可以复用这个Operation的运算类了。但是这样也仅仅使用了面向对象三大特性中的一个封装,其他的两个对于这个小小的计算器来讲,如何用到继承,至于多态,一直也不了解它到底有什么好处,只是运行时的自动匹配吗?

    紧耦合 vs 松耦合

    上文中的Operation类,如果需要加一个开根(sqrt)运算,是不是只需要更改Operation类,在switch中加一个分支就行了。浅层次来讲,这样没错,但是,在编译的时候却要整个Operation进行重新编译,万一把加法运算不小心改成了减法,这就不太好了。
    举个例子,如果公司要求你为公司的薪资管理系统做维护,原来只有技术人员(月薪),市场销售人员(底薪+提成),经理(年薪+股份)三种运算算法,现在要增加兼职工作人员(时薪)的算法,按照刚刚程序的写法,公司就必须要把包含原三种算法的运算类给你,让你修改,你如果心中小算盘一打,“TMD的,公司给我的工资这么低,我真是郁闷,这下机会来了”,于是你就

    if(员工是 小王)
    {
    	salary = salary * 1.1;
    }
    
    • 1
    • 2
    • 3
    • 4

    那这样就意味着我月薪每月都会增加10%,很快就会走上人生巅峰,迎娶白富美了。本来只是让你加一个功能,却使得原有的运行良好的功能代码产生了变化,这个风险太大了。
    所以,我们重写了Operation运算类,如下:

    Operation.h
    class Operation {
    public:
    	static map operatorParam;
    
    	void setNumberA(double val);
    	void setNumberB(double val);
    	double getNumberA();
    	double getNumberB();
    
    	virtual double GetResult();
    private:
    	double numberA, numberB;
    };
    
    Operation.cpp
    map Operation::operatorParam = {
    		{"+", 1},
    		{"-", 2},
    		{"*", 3},
    		{"/", 4},
    };
    
    void Operation::setNumberA(double val)
    {
    	numberA = val;
    }
    
    void Operation::setNumberB(double val)
    {
    	numberB = val;
    }
    
    double Operation::getNumberA()
    {
    	return numberA;
    }
    
    double Operation::getNumberB()
    {
    	return numberB;
    }
    
    double Operation::GetResult()
    {
    	double result = 0;
    	return result;
    	return 0.0;
    }
    
    #OperationAdd.h
    #include "Operation.h"
    class OperationAdd :public Operation{
    public:
    	double GetResult();
    };
    
    #OperationAdd.cpp
    #include "OperationAdd.h"
    double OperationAdd::GetResult()
    {
    	double result = 0;
    	result = getNumberA() + getNumberB();
    	return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    搭配简单的工厂模式

    //工厂类
    //OperationFactory.h
    #include 
    #include "Operation.h"
    class OperationFactory
    {
    public:
    	static Operation *createOperate(string operate);
    };
    
    //OperationFactory.cpp
    #include "OperationFactory.h"
    #include "OperationAdd.h"
    Operation* OperationFactory::createOperate(string operate)
    {
    	Operation *oper;
    	switch (operate[0])
    	{
    	case '+':
    		oper = new OperationAdd();
    		break;
    	default:
    		oper = new Operation();
    		break;
    	}
    	return oper;
    }
    
    //调用 main 部分
    Operation *oper;
    oper = OperationFactory::createOperate(strOperate);
    oper->setNumberA(numberA);
    oper->setNumberB(numberB);
    result = oper->GetResult();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    这样,如果需要改加法运算,则只需要修改OperationAdd类即可,如果需要添加其他的运算,比如平方根等等,只需要增加对应的子类和再switch中增加分支即可,如果需要修改界面,则只需要改界面。

    编程是一门技术,更加是一门艺术

  • 相关阅读:
    springcloud之项目实战服务治理
    蚂蚁一面凉经
    【Java精炼系列篇】【事务】【事务的使用】
    Jmeter 使用BeanShell断言,实现自动获取文章列表,并判断文章是否为当天发布的
    vue-组件通信(二)
    10-类加载器
    HarmonyOS—UI开发性能提升的推荐方法
    练[NCTF2019]Fake XML cookbook
    Loguru 源码中如何sink参数是日志路径时,rotation不能精确到天
    使用 Sealos 在离线环境中光速安装 K8s 集群
  • 原文地址:https://blog.csdn.net/a12345d132/article/details/126294770