一. 什么是设计模式
“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样你就能一次又一次地使用该方案而不必做重复劳动”。
——Christopher Alexander
>> 可复用是设计模式的目标;
>> 面向对象;
二. 从面向对象谈起
>> 底层思维:向下,如何把握机器底层,从微观理解对象构造;
>> 抽象思维:向上,如何将我们周围世界抽象为程序代码(其核心是帮我们管理代码复杂度);
仅有抽象思维而没有底层思维是不够的,缺乏底层思维会导致代码出错、性能差等不良结果。
三. 软件设计复杂的根本原因
变 化
· 客户需求的变化
· 技术平台的变化
· 开发团队的变化
· 市场环境的变化
······
1. 如何解决复杂性
>> 分解:分而治之,将大问题分解为多个小问题,将复杂问题分解为多个简单问题;
>> 抽象:忽视他的非本质细节而去处理泛化和理想化了的对象模型;
2. 伪码举例
假设要开发一个画图的程序,需要有绘制直线和矩形两种功能。
>> 分解的方式:
我们按分解的思想,设计了如下代码:
// Shape.h
// 伪码展示,请忽略C++的一些设计规范
class Point{
public:
int x;
int y;
};
class Line{
public:
Point start;
Point end;
Line(const Point& start, const Point& end){
this->start = start;
this->end = end;
}
};
class Rect{
public:
Point leftUp;
int width;
int height;
Rect(const Point& leftUp, int width, int height){
this->leftUp = leftUp;
this->width = width;
this->height = height;
}
};
逻辑业务处理如下(重点看OnPaint中的业务表现形式):
// MainForm.cpp
class MainForm : public Form{
private:
// 鼠标移动会留下点的轨迹,用p1、p2表示
Point p1;
Point p2;
vector<Line> lineVector; // 线的数据结构
vector<Rect> rectVector; // 矩形的数据结构
public:
MainForm(){
// ...
}
protected:
virtual void OnMouseDown(cosnt MouseEventArgs& e);
virtual void OnMouseUp(cosnt MouseEventArgs& e);
virtual void OnPaint(cosnt PaintEvent& e);
};
void MainForm::OnMouseDown(cosnt MouseEventArgs& e){
// 记录按下时点坐标
p1.x = e.x;
p1.y = e.y;
// ...
Form::OnMouseDown(e);
}
void MainForm::OnMouseUp(cosnt MouseEventArgs& e){
// 记录抬起时点坐标
p2.x = e.x;
p2.y = e.y;
if(直线按钮按下){
// 构建并储存直线数据
}
else if(矩形按钮按下){
// 构建并储存矩形数据
}
// ...
Form::OnMouseUp(e);
}
void MainForm::OnPaint(cosnt PaintEvent& e){
// 针对直线
for(int i = 0; i < lineVector.size(); i++){
// 直线业务逻辑
}
// 针对矩形
for(int i = 0; i < rectVector.size(); i++){
// 矩形业务逻辑
}
}
>> 抽象的方式:
下面代码与分解不同的是,在设计类的时候,增加了抽象类Shape,并且在子类中增加了Draw的实现方法:
// Shape.h
class Shape{
public:
virtual void Draw(const Graphics& g)=0;
// 只有写了虚析构,子类通过多态释放时,子类的析构函数才会被正确调用到
virtual ~Shape(){}
}
class Point{
public:
int x;
int y;
};
class Line : public Shape{
public:
Point start;
Point end;
Line(const Point& start, const Point& end){
this->start = start;
this->end = end;
}
// 实现自己的Draw,负责画自己
virtual void Draw(const Graphics& g){
// 绘制逻辑...
}
};
class Rect : public Shape{
public:
Point leftUp;
int width;
int height;
Rect(const Point& leftUp, int width, int height){
this->leftUp = leftUp;
this->width = width;
this->height = height;
}
// 实现自己的Draw,负责画自己
virtual void Draw(const Graphics& g){
// 绘制逻辑...
}
};
MainForm的属性中,不再维护具体的绘图类,而是维护了绘图类的抽象接口Shape类,在OnPaint函数中,通过多态的方式,调用子类的Draw方法:
// MainForm.cpp
class MainForm : public Form{
private:
// 鼠标移动会留下点的轨迹,用p1、p2表示
Point p1;
Point p2;
// 针对所有形状
// 这里存放的是指针类型,因为我们需要多态性
vector<Shape*> shapeVector;
public:
MainForm(){
// ...
}
protected:
virtual void OnMouseDown(cosnt MouseEventArgs& e);
virtual void OnMouseUp(cosnt MouseEventArgs& e);
virtual void OnPaint(cosnt PaintEvent& e);
};
void MainForm::OnMouseDown(cosnt MouseEventArgs& e){
// 记录按下时点坐标
p1.x = e.x;
p1.y = e.y;
// ...
Form::OnMouseDown(e);
}
void MainForm::OnMouseUp(cosnt MouseEventArgs& e){
// 记录抬起时点坐标
p2.x = e.x;
p2.y = e.y;
if(直线按钮按下){
// 构建并储存直线数据
// 这里参数不可以放栈对象,必须是堆对象
shapeVector.push_back(new Line(p1,p2));
}
else if(矩形按钮按下){
// 构建并储存矩形数据
int width = abs(p2.x - p1.x);
int height = abs(p2.y = p1.y);
shapeVector.push_back(new Rect(p1, width, height));
}
// ...
Form::OnMouseUp(e);
}
void MainForm::OnPaint(cosnt PaintEvent& e){
// 针对所有形状
for(int i = 0; i < shapeVector.size(); i++){
shapeVector[i]->Draw(e.Graphics); // 多态调用,各司其职
}
// ...
}
从静态的角度观察,两个方式都可以实现业务功能的需求,难以分辨那种方式更好,但从变化的角度来看,假如增加了圆形的画法,按照分解的思维,会有如下改动:
// Shape.h
// ...
// 增加
class Circle : public Shape{ // ...};
// MainForm.cpp
class MainForm : public Form{
private:
// ...
// 改动
vector<Circle> circleVector;
// ...
};
void MainForm::OnMouseDown(cosnt MouseEventArgs& e){ // ... }
void MainForm::OnMouseUp(cosnt MouseEventArgs& e){
// ...
if(...){ // ... }
else if(...){ // ... }
// 改动
else if(圆形按钮按下){ // 构建并储存圆形数据 }
// ...
}
void MainForm::OnPaint(cosnt PaintEvent& e){
// 针对直线
for(...){ // ... }
// 针对矩形
for(...){ // ... }
// 改动
// 针对圆形
for(int i = 0; i < rectVector.size(); i++){ // 圆形业务逻辑 }
}
按抽象思维,会有如下改动:
// Shape.h
// ...
// 增加
class Circle : public Shape{ // ...};
// MainForm.cpp
class MainForm : public Form{ // ... };
void MainForm::OnMouseDown(cosnt MouseEventArgs& e){ // ... }
void MainForm::OnMouseUp(cosnt MouseEventArgs& e){
// ...
if(直线按钮按下){ // ...}
else if(矩形按钮按下){ // ...}
// 此处还是需要做一些改动
// 以后用工厂模式可以消除此处的改动
else if(圆形按钮按下){ // ...}
// ...
}
void MainForm::OnPaint(cosnt PaintEvent& e){ // ... }
分解的思想,会随着业务的变更,使代码体系逐渐变得庞大,慢慢将难以维护,并且在业务增加的同时,需要对已有代码进行多处修改,这可能会导致多种不可预知的问题。