设计模式简介

转载自: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);

}

由以上的代码可以看出,变化的部分,不会有之前的代码变化的部分那么零散,不需要在各种地方做改变,那么也正是由于这样的抽象的思想,使得代码的变更更加容易,重用性得到了提升。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • //出自51博客:www.Amanda0928.51.com 第一章 一、选择题 1.B; (typedef ,t...
    Damongggggg阅读 11,101评论 0 1
  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile丽语阅读 3,826评论 0 6
  • /*类和对象 1.类和实例化定义类声明类定义成员函数数据成员的赋值使用类的对象。 2.构造函数数据封装默认构造函数...
    aofeilin阅读 1,038评论 1 2
  • created by Dejavu(不断更新中) 简介 地面信息的提取对于车形的智能机器人来说十分重要,之前一直采...
    ericdejavu阅读 1,075评论 0 1
  • 两年前就买了李笑来老师写的《把时间当做朋友》,买时是心潮澎湃向往之,看时亦觉当头棒喝貌似受益匪浅,可是为什么两...
    素朴之行阅读 156评论 0 0