转载自:https://blog.csdn.net/q5707802/article/details/91355508
什么是设计模式
每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案。这样,你就能一次又一次地使用该方案而不必做重复劳动。
——Christopher Alexander
在软件开发过程中,软件开发人员会遇到很多问题,发现有些问题是类似,是会不断重复的发生的,经过长时间的试验,总结出针对这一类似的问题的解决方案,总结出一套模式。
由此可见,设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。
在《设计模式 - 可复用的面向对象软件的基础》一书中描述了23种经典面向对象的设计模式,创立了模式在软件设计中的地位。
这本书的主标题是“设计模式”,副标题是“可复用的面向对象软件的基础”,很多人看这本书时很容易抓住它的主标题,但是其实它的副标题更重要。
副标题有两个关键词,一个关键词是“可复用”,可复用是设计模式的目标,初学者很容易忘记“可复用”的这一个目标,这样就南辕北辙。另一个关键词是“面向对象”,面向对象是一种具体方法。
由于《设计模式》一书确定了设计模式地位,通常所说的设计模式隐含的表示“面向对象设计模式”。但是,到今天,软件行业已经发生了翻天覆地的变化,而这本书是写于199年的,所以在今天设计模式并不意味着等于“面向对象设计模式”,模式在别的领域也有别的模式,非面向对象的书寂寞,比如在架构方面有架构模式,在数据库领域里有数据库的模式。
不过,在这里讲的是指“面向对象设计模式”。
既然谈的是面向对象的设计模式,那么先从面向对象谈起。
面向对象,隐含了两种思维:底层思维和抽象思维。
在上面的图中,左边是电脑,右边是一副清明河上图,代表人类世界的一种场景,中间是程序员,程序员负责打通计算机世界和人类世界这两部分,把人类的需求表达给计算机,让计算机可以理解,帮助我们执行。
底层思维,向下的思维,和计算机的沟通,需要我们把握机器底层的微观构造,比如说,语言构造、编译转换、内存模型、运行时机制。
良好的底层思维,能帮助我们写出高质量的健壮的代码。
抽象思维,向上的思维,与人类世界沟通,将我们的周围世界抽象为程序代码,这里面涉及到:面向对象、组件封装、设计模式、架构模式。
抽象思维能帮助我们管理代码的复杂。
抽象思维,在某种程序比底层思维更重要。
而面向对象的过程,其实就是希望模拟人类这样一种抽象的思维,通过抽象思维来解释这样的世界。
而为了更好的能够用计算机解释好这个世界,程序员必须要有一套很好的抽象思维。
三大面向对象机制:封装、继承、多态。
深刻把握面向对象机制所带来的抽象意义,理解如何使用这些机制来表达现实世界,掌握什么是“好的面向对象设计”。
那么,抽象思维在帮我们更好的解释这个世界的同时,他还会给我们带来其他的帮助吗?
对于软件设计的来说,最为头疼的地方就是,他无时无刻不再面对者一个次——变化。不论从客户的需求层面、技术平台层面、开发团队层面还是市场环境层面,都在面对着巨大的变化。而对应着每次变化,那么就需要面对代码的变更,这些变化会摧毁代码的体系结构的设计。
因此,变化,是软件设计复杂的根本原因。
那么为了解决遇到问题的复杂性,我们通常会用两种方式来解决,一种是把分解,另外一种是抽象。
对于分解来说,其实就是分而治之,把大问题不断的划分为一个又一个的小问题,通过解决分解开的每一个小问题来解决整体的大问题。也就是不断的分工,各司其职来解决问题。
对于抽象来说,属于更高的层次,这其实是一种解决问题的通用技术。由于抽象就是不具体,不能掌握全部的复杂对象,所以也就忽视了大量的细节,而抓住一些主要细节,如果我们过于具体,对于中学所学习的物理就是难以进行的,因为我们都知道,中学的物理都建立在一套几乎完全理想化的条件下,比如摩擦力不变,空气没有阻力,温度不影响某一个参数的变化等等。这样抽象的结果,也是为了方便我们来解决主要的问题,而不至于陷入到细节的泥潭里无法自拔。
对于所有的软件思想都必须要落实在具体代码实现上,所以,还是落在一些伪码的描述层面来解释抽象思维的好处和优点。
那么先来看看这样一段伪码描述吧,它希望表达的是一种分而治之的思想,模拟了一套类似图形绘制软件的代码。
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;
}
};
class MainForm : public Form {
private:
Point p1;
Point p2;
vector<Line> lineVector;
vector<Rect> rectVector;
public:
MainForm(){
//...
}
protected:
virtual void OnMouseDown(const MouseEventArgs& e);
virtual void OnMouseUp(const MouseEventArgs& e);
virtual void OnPaint(const PaintEventArgs& e);
};
void MainForm::OnMouseDown(const MouseEventArgs& e){
p1.x = e.X;
p1.y = e.Y;
//...
Form::OnMouseDown(e);
}
void MainForm::OnMouseUp(const MouseEventArgs& e){
p2.x = e.X;
p2.y = e.Y;
if (rdoLine.Checked){
Line line(p1, p2);
lineVector.push_back(line);
}
else if (rdoRect.Checked){
int width = abs(p2.x - p1.x);
int height = abs(p2.y - p1.y);
Rect rect(p1, width, height);
rectVector.push_back(rect);
}
//...
this->Refresh();
Form::OnMouseUp(e);
}
void MainForm::OnPaint(const PaintEventArgs& e){
//针对直线
for (int i = 0; i < lineVector.size(); i++){
e.Graphics.DrawLine(Pens.Red,
lineVector[i].start.x,
lineVector[i].start.y,
lineVector[i].end.x,
lineVector[i].end.y);
}
//针对矩形
for (int i = 0; i < rectVector.size(); i++){
e.Graphics.DrawRectangle(Pens.Red,
rectVector[i].leftUp,
rectVector[i].width,
rectVector[i].height);
}
//...
Form::OnPaint(e);
}
上面这个伪代码可以用来解决一些图形的绘制,比如矩形,直线和点的形状。在MainForm的类里面对显示界面的每个形状都进行了维护和处理。
但是,如果客户想增加些形状,比如说“园”,现在遇到这个变化,我们应该如何去修改代码呢?
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;
}
};
//增加
class Circle{
};
class MainForm : public Form {
private:
Point p1;
Point p2;
vector<Line> lineVector;
vector<Rect> rectVector;
//改变
vector<Circle> circleVector;
public:
MainForm(){
//...
}
protected:
virtual void OnMouseDown(const MouseEventArgs& e);
virtual void OnMouseUp(const MouseEventArgs& e);
virtual void OnPaint(const PaintEventArgs& e);
};
void MainForm::OnMouseDown(const MouseEventArgs& e){
p1.x = e.X;
p1.y = e.Y;
//...
Form::OnMouseDown(e);
}
void MainForm::OnMouseUp(const MouseEventArgs& e){
p2.x = e.X;
p2.y = e.Y;
if (rdoLine.Checked){
Line line(p1, p2);
lineVector.push_back(line);
}
else if (rdoRect.Checked){
int width = abs(p2.x - p1.x);
int height = abs(p2.y - p1.y);
Rect rect(p1, width, height);
rectVector.push_back(rect);
}
//改变
else if (...){
//...
circleVector.push_back(circle);
}
//...
this->Refresh();
Form::OnMouseUp(e);
}
void MainForm::OnPaint(const PaintEventArgs& e){
//针对直线
for (int i = 0; i < lineVector.size(); i++){
e.Graphics.DrawLine(Pens.Red,
lineVector[i].start.x,
lineVector[i].start.y,
lineVector[i].end.x,
lineVector[i].end.y);
}
//针对矩形
for (int i = 0; i < rectVector.size(); i++){
e.Graphics.DrawRectangle(Pens.Red,
rectVector[i].leftUp,
rectVector[i].width,
rectVector[i].height);
}
//改变
//针对圆形
for (int i = 0; i < circleVector.size(); i++){
e.Graphics.DrawCircle(Pens.Red,
circleVector[i]);
}
//...
Form::OnPaint(e);
}
假设以上的伪码已经实现了对圆形的功能的扩展,那么在扩展的过程中遇到了什么问题吗?其实问题还是很明显的,就是我们需要改变的地方非常的零散,不但需要添加一个Circle的类,还需要在MainForm中进行业务的修改,通过这些修改才能改完成圆形功能的扩展。而如果站在软件工程的角度上来看,对于修改过的部分都需要在重新测试等一些列的操作,其实对于功能扩充来说其实面对着后期大量的工作。这对于我们管理和维护我们的代码是相当不利的。同时对于多人的协作也是不利的,每个人修改完代码,还需要通知另外的人也需要修改代码,这不但不方便,同时也增加了团队出错的概率。
如果有一种方法,在我们遇到扩展的时候,只需要 增加我们的扩展内容,而不修改之前我们代码就好了。那么我们先来看看关于抽象的设计思路吧。
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){
g.DrawLine(Pens.Red,
start.x, start.y,end.x, end.y);
}
};
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){
g.DrawRectangle(Pens.Red,
leftUp,width,height);
}
};
class MainForm : public Form {
private:
Point p1;
Point p2;
//针对所有形状
vector<Shape*> shapeVector;
public:
MainForm(){
//...
}
protected:
virtual void OnMouseDown(const MouseEventArgs& e);
virtual void OnMouseUp(const MouseEventArgs& e);
virtual void OnPaint(const PaintEventArgs& e);
};
void MainForm::OnMouseDown(const MouseEventArgs& e){
p1.x = e.X;
p1.y = e.Y;
//...
Form::OnMouseDown(e);
}
void MainForm::OnMouseUp(const MouseEventArgs& e){
p2.x = e.X;
p2.y = e.Y;
if (rdoLine.Checked){
shapeVector.push_back(new Line(p1,p2));
}
else if (rdoRect.Checked){
int width = abs(p2.x - p1.x);
int height = abs(p2.y - p1.y);
shapeVector.push_back(new Rect(p1, width, height));
}
else if (...){
//...
shapeVector.push_back(circle);
}
//...
this->Refresh();
Form::OnMouseUp(e);
}
void MainForm::OnPaint(const PaintEventArgs& e){
//针对所有形状
for (int i = 0; i < shapeVector.size(); i++){
shapeVector[i]->Draw(e.Graphics); //多态调用,各负其责
}
//...
Form::OnPaint(e);
}
对以上的代码,可以看出,对于每个形状,都增加了一个共同的父类Shape,同时对于图形来说应该如何绘制自己的这样的形状的函数都是由自己来实现的(Draw(...)函数)。对于MainForm来说他管理的容器也发生了改变,只需要管理Shape的指针,而不再具体管理某一个具体的形状,实现了一次向上抽象的管理。同时onPaint的方法来说,也不在直接管理绘制的问题,而是调用了Shape中的虚函数Draw,通过多态调用来实现了MainForm来调用了自己绘制自己的方法。
假设现在在这个架构中需要增加一个圆形的画法。那么代码会发生以下的变化。
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){
g.DrawLine(Pens.Red,
start.x, start.y,end.x, end.y);
}
};
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){
g.DrawRectangle(Pens.Red,
leftUp,width,height);
}
};
//增加
class Circle : public Shape{
public:
//实现自己的Draw,负责画自己
virtual void Draw(const Graphics& g){
g.DrawCircle(Pens.Red,
...);
}
};
class MainForm : public Form {
private:
Point p1;
Point p2;
//针对所有形状
vector<Shape*> shapeVector;
public:
MainForm(){
//...
}
protected:
virtual void OnMouseDown(const MouseEventArgs& e);
virtual void OnMouseUp(const MouseEventArgs& e);
virtual void OnPaint(const PaintEventArgs& e);
};
void MainForm::OnMouseDown(const MouseEventArgs& e){
p1.x = e.X;
p1.y = e.Y;
//...
Form::OnMouseDown(e);
}
void MainForm::OnMouseUp(const MouseEventArgs& e){
p2.x = e.X;
p2.y = e.Y;
if (rdoLine.Checked){
shapeVector.push_back(new Line(p1,p2));
}
else if (rdoRect.Checked){
int width = abs(p2.x - p1.x);
int height = abs(p2.y - p1.y);
shapeVector.push_back(new Rect(p1, width, height));
}
//改变
else if (...){
//...
shapeVector.push_back(circle);
}
//...
this->Refresh();
Form::OnMouseUp(e);
}
void MainForm::OnPaint(const PaintEventArgs& e){
//针对所有形状
for (int i = 0; i < shapeVector.size(); i++){
shapeVector[i]->Draw(e.Graphics); //多态调用,各负其责
}
//...
Form::OnPaint(e);
}
由以上的代码可以看出,变化的部分,不会有之前的代码变化的部分那么零散,不需要在各种地方做改变,那么也正是由于这样的抽象的思想,使得代码的变更更加容易,重用性得到了提升。