大话设计模式有感

大话设计模式有感

计算器控制台程序

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

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. 判断分支,虽然根据字符可以准确的进入到每一个分支,但是每一个if都需要判断,导致每次都做了3次无用功
  3. 基础的逻辑。当除数为0时怎么办,用户输入的不是数字怎么办。
int main()
{
	double numberA, numberB;
	double result;
	string strOperate;
	map<string, int> 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;
}

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

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

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

复制 vs 复用

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

#Operation.h
#pragma once
#include <iostream>
#include <string>
using namespace std;
class Operation {
public:
	static map<string, int> operatorParam;

public:
	static double GetResult(double numberA, double numberB, string strOperator);
	
};

#Operation.cpp
#include <stdio.h>
#include <map>
#include <exception>
#include "Operation.h"
using namespace std;

map<string, int> 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;
}

客户端代码:

#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;
}

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

紧耦合 vs 松耦合

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

if(员工是 小王)
{
	salary = salary * 1.1;
}

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

Operation.h
class Operation {
public:
	static map<string, int> operatorParam;

	void setNumberA(double val);
	void setNumberB(double val);
	double getNumberA();
	double getNumberB();

	virtual double GetResult();
private:
	double numberA, numberB;
};

Operation.cpp
map<string, int> 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;
}

搭配简单的工厂模式

//工厂类
//OperationFactory.h
#include <string>
#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();

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值