设计模式学习笔记(一)
最近在重新学习以前只是笼统写过一遍代码的设计模式,因为我觉得设计模式是一种思想,我们在学习它的过程中应该更加注重书中用到的思想,而不是只是把所有的模式用自己熟悉的语言都写一遍就代表自己会了。所以决定把书中的内容从头到尾重新系统的学习一遍,学习过程中的所有笔记和感悟都会总结分享到CSDN上,以便自己以后查阅。
本人学习设计模式所参考的书籍是亚历山大·什韦茨(Alexander Shvets)著作的《深入设计模式》。另外本书在网上也有部分内容可以免费查阅。
链接: https://refactoringguru.cn/design-patterns.
设计模式关系及原理介绍
①对象之间的关系
依赖:
依赖是类之间最基础的、也是最微弱的关系类型。如果修改一个类的定义可能会造成另一个类的变化,那么这两个类之间就存在依赖关系。UML图中的虚线箭头方向表示教授类依赖于课程类。
关联:
关联可以看作是一种强依赖,是“一个对象使用另一对象或与另一对象进行交互的关系”,UML图中关联是用一根实线从一个类指向另外一个类来表示的。关联可以是双向的。
C++代码演示依赖与关联的关系:
class Professor
{
public:
void teach(Course c)
{
//...
this.student.remember(c.getKnowledge());
}
private:
Student student;
};
本段伪代码模拟实现了以下场景:教授通过课程资料向学生授课。
在Professor类的teach方法中,Professor类通过传入的Course类向Student类授课。
如果Course类中的getKnowlege()方法被改变。(函数名或者形参)那么Professor类中的代码也会跟着崩溃,所以教授类依赖于课程类,又因为教授类仅有teach方法可以访问Course类的成员变量,并且不能管理Course类的声明周期,所以Professor类与Course类仅仅是依赖关系。
同样的,如果Student类中的remember方法被改变,Professor类中的代码同样会崩溃,所以Professor类与Student类之间存在依赖关系。但又因为Student类中的student对象为Professor类的成员变量,所以Professor类所有的方法都可以访问Student类对象中的成员变量,不仅如此而且还管理着student对象的声明周期。所以Professor类与Student类之间的关系为关联。
聚合
聚合就是一对多的依赖关系。
组合
组合就是一对多的关联。
②设计原则
什么是优秀的软件设计?如何对其进行评估?你需要遵循那些事件方式才能实现这样的方式?如何让你的架构灵活、稳定且易于理解?
这些都是好的问题。但不幸的是,根据应用类型的不同,这些问题的答案也不尽相同。不过对于你的项目来说,有几个通用的软件设计原则可能会对解决这些问题有所帮助。
1.封装变化的内容
找到程序中变化的内容并将其与不变的内容区分开。
该原则的主要目的是将变更造成的影响最小化。在程序设计之初,有的代码是会根据客户需求或者未来复杂的应用场景的改变而进行变更的。将变化的内容与不变的内容分开写可以降低这两个内容间的耦合性,可以让你在未来改代码的时候非常轻松的处理相应变化部分的内容。如果没有区分开的话,在变化与不变的代码中间找到特定的处理逻辑,再修改原有的逻辑,并且还有保证新的耦合方法不会影响到原来的代码-------这是一个巨大的工程,你肯定不想这么干。
方法层面的封装
假如你正在开发一个电子商务网站。代码中某处有一个getOrderTotal 获取订单总额 方法,用于计算订单的总价(包括税金在内)。
我们预计在未来可能会修改与税金相关的代码。税率会根据客户居住的国家/地区、州。省甚至城市而有所不同;而且一段时间后,实际的计算公式可能会由于新的法律或规定而修改。因此,你需要经常性地修改getOrderTotal 方法。不过仔细看看方法名称,连它都在暗示其并不关心税金是如何计算出来的。
你可以将计算税金的逻辑抽取到一个单独的方法中,并对原始方法隐藏该逻辑。
这样税率相关的修改就被隔离在单个方法内了。此外,如果税率计算逻辑变得过于复杂,你也能更方便地将其移动到独立地类中。
类层面的封装
一段时间后,你可能会在一个以前完成简单工作地方法中添加越来越多地职责。新增行为通常还会带来助手成员变量和方法,最终使得包含接纳它们的类的主要职责变得模糊。将所有这些内容抽取到一个新类中会让程序更加清晰简洁。
②面向接口开发,而不是面向实现
面向接口进行开发,而不是面向实现;依赖于抽象类型,而不是具体类。
示例
让我们来看这一个例子,它说明了通过接口与对象交互要比依赖于具体类的好处更多。假设你正在开发一款软件开发公司模拟器,而且使用了不同的类来代表各种类型的雇员。
刚开始时,公司, Company 类与具体雇员类紧密耦合。尽管各个雇员的实现不尽相同,但我们还是可以归纳出几个与工作相关的方法,并且将其抽取为所有雇员的通用接口。
此后,我们可以在 公司 类内应用多态机制,通过雇员Employee 接口来处理各类雇员对象。
示例代码:
#include <iostream>
using namespace std;
//interface
class Employee
{
public:
virtual void doWork(void) = 0;
};
class Designer :public Employee
{
public:
void doWork(void)
{
cout << "I'm a designer...." << endl;
}
};
class Programmer :public Employee
{
public:
void doWork(void)
{
cout << "I'm a Programmer...." << endl;
}
};
class Tester :public Employee
{
public:
void doWork(void)
{
cout << "I'm a Tester...." << endl;
}
};
class Company
{
public:
Company()
{
designer = new Designer;
programmer = new Programmer;
tester = new Tester;
}
void creatSoftware()
{
designer->doWork();
programmer->doWork();
tester->doWork();
}
private:
Employee* designer;
Employee* programmer;
Employee* tester;
};
int main()
{
Company myCompany;
myCompany.creatSoftware();
system("pause");
return 0;
}
不过经过了以上的处理,公司类仍与雇员类相耦合,这很糟糕,因为如果引入包含其他类型雇员的公司类型的话,我们就需要重写绝大部分的 公司类了,不能复用其代码。
为了解决这个问题,我们可以声明一个抽象方法来获取雇员。每个具体公司都将以不同方式实现该方法,从而创建自己所需的雇员。
示例代码:
#include <iostream>
#include <vector>
using namespace std;
//interface
class Employee
{
public:
virtual void doWork(void) = 0;
};
class Designer :public Employee
{
public:
void doWork(void)
{
cout << "I'm a designer...." << endl;
}
};
class Programmer :public Employee
{
public:
void doWork(void)
{
cout << "I'm a Programmer...." << endl;
}
};
class Tester :public Employee
{
public:
void doWork(void)
{
cout << "I'm a Tester...." << endl;
}
};
class Aritist :public Employee
{
public:
void doWork(void)
{
cout << "I'm a Aritist...." << endl;
}
};
class Company
{
public:
virtual vector<Employee*> getEmployees() = 0;
void creatSoftware(vector<Employee*> employ)
{
for (int i = 0; i < (int)employ.size(); ++i)
{
employ[i]->doWork();
}
}
};
class GameDevCompany :public Company
{
public:
vector<Employee*> getEmployees()
{
vector<Employee*> employeeGroup;
Employee* designer = new Designer;
Employee* artist = new Aritist;
employeeGroup.push_back(designer);
employeeGroup.push_back(artist);
return employeeGroup;
}
};
class OutsourcingCompany :public Company
{
public:
vector<Employee*> getEmployees()
{
vector<Employee*> employeeGroup;
Employee* programmer = new Programmer;
Employee* tester = new Tester;
employeeGroup.push_back(programmer);
employeeGroup.push_back(tester);
return employeeGroup;
}
};
int main()
{
GameDevCompany Gcompany;
vector<Employee*> group1 = Gcompany.getEmployees();
cout << "游戏公司:" << endl;
Gcompany.creatSoftware(group1);
OutsourcingCompany Ocompany;
vector<Employee*> group2 = Ocompany.getEmployees();
cout << "外包公司:" << endl;
Ocompany.creatSoftware(group2);
system("pause");
return 0;
}
修改后的 公司 类将独立于各种雇员类。现在你可以对该类进行扩展,并在复用部分公司基类的情况下引入新的公司和雇员类型。对公司基类进行扩展时无需修改任何依赖于基类已有代码。
④组合优于继承
链接: 组合优于继承博客.