跳至主要內容

《C++设计模式》视频_李建忠

LincZero大约 8 分钟

《C++设计模式》视频_李建忠

目录

设计模式简介

学习

网络资料

推荐图书

  • 历史性著作《设计模式:可复用面向对象软件的基础》,英文名《Design Patterns》,作者GoF(Gang of Four 四巨头)
  • 《重构——改善既有代码的设计》,作者Martin Fowler(马丁 · 福勒)
  • 《重构与模式》,作者BufferedStream
  • 《Head First 设计模式》,这本书弹幕经常在说,豆瓣9.2,应该也是不错的

简概

课程目标

  • 理解松耦合设计思想
  • 掌握面向对象设计原则
  • 掌握重构技法改善设计
  • 掌握GOF核心设计模式

简概

注意点:设计模式与语言无关,面向对象语言都适用,Java和C++都能用

是什么

runoob.com中的解释

设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。 设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。 这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

作用

每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动

——Christopher

GOF设计模式(Gang Of Four 四人组)

历史性著作《设计模式:可复用面向对象软件的基础》一书中描述了23种经典面向对象设计模式,创立了模式在软件设计中的地位

(该书也是推荐教材,英文名《Design Patterns》)

由于《设计模式》一书确定了设计模式中的地位,通常所说的设计模式隐含地表示 ”面向对象设计模式“ 。但这并不意味 ”设计模式“ 就等于 ”面向对象设计模式“

动机(Motivation)、演化与思想

面向对象的底层与抽象

重新认识面向对象

从面向对象谈起

  • 底层思维(向下)
    • 语言构造
    • 编译转换
    • 内存模型
    • 运行时机制
  • 抽象思维(向上)
    • 面向对象
    • 组件封装
    • 设计模式
    • 架构模式

深入理解面向对象

  • 底层思维(向下):深入理解三大面向对象机制
    • 封装
    • 继承
    • 多态
  • 抽象思维(向上):深刻把握面向对象机制所带来的抽象意义
    • 理解隔离变化
      • 从宏观层面来看,面向对象的构建方式更能适应软件的变化,能将变化所带来的影响减为最小
    • 各司其职
      • 从微观层面来看,面向对象的方式更强调各个类的“责任”
      • 由于需求变化导致的新增类型不应该影响原来类型的实现——是所谓各负其责
    • 对象是什么?
      • 从语言实现层面来看,对象封装了代码和数据
      • 从规格层面讲,对象是一系列可被使用的公共接口
      • 从概念层面讲,对象是某种拥有责任的抽象

软件设计复杂性

软件设计固有

建筑商从来不会去想给一栋已建好的100层高的楼房底下再新修一个小地下室——这样做花费极大而且注定要失败。 然而令人惊奇的是,软件系统的用户在要求作出类似改变时却不会仔细考虑,而且他们认为这只是需要简单编程的事。

——《Object-Oriented Analysis and Designwith Applications》


软件设计复杂的根本原因——变化

  • 客户需求的变化
  • 技术平台的变化
  • 开发团队的变化
  • 市场环境的变化
  • ……

如何解决复杂性?

  • 分解
    • 分而治之
  • 抽象
    • 由于不能掌握全部的复杂对象,我们选择忽视它的非本质细节而去处理泛化和理想化了的对象模型

代码体现

一个绘图的代码实现

举例 - 分解写法

Shape.h,类

/* 伪代码 */
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;
	}
};

// 增加Circle类
class Circle{																	// 【改变】
    // ...
}

MainForm.cpp,使用代码,这里没用头文件、直接声明和定义写一块了

class MainForm : public Form{
private:
	Point p1;
    Point p2;

    vector<Line> lineVector;	// 装Line类型的容器
    vector<Rect> rectVector;	// 装Rect类型的容器
    vector<Circle> circleVector;												// 【改变】
    /** 复习下Vector
     * 向量(Vector)是一个封装了动态大小数组的顺序容器(Sequence Container),特性:
	 * 1.顺序序列
	 * 2.动态数组
	 * 3.能够感知内存分配器的(Allocator-aware)
	 */
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.Checke){
		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(){}																	// 【改变】
    
	// ...
    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);
}

举例 - 抽象写法

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) override{
		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);
	}
};

// 增加Circle类
class Circle: public shape{														// 【改变】
    // ...
    //实现自己的Draw,负责画自己
	virtual void Draw( const Graphics& g){
    // ...
    }
}

MainForm.cpp

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(){}																	// 【改变】

    // ...
    this->Refresh();
    Form::OnMouseUp(e);
}

void MainForm : : OnPaint( const PaintEventArgs& e){
	// 针对所有形状
	for (int i = 0; i < shapeVector.size(); i++){
		shapeVector[i]->Draw(e.Graphics);
        /** 多态调用,各负其责。
         * 注:这里的shapeVector类型是vector<Shape*>,掉用方法时,
         * 因为是纯虚函数,所以最后回调用派生对象中重写的方法
         */
	}
	//...
	Form: :OnPaint(e);
}

比较两种写法

哪种设计好?直接看貌似不相伯仲。但假设,有一种变化,比如客户需要新增一个绘制圆形的功能

  • 分解:修改4处,Shape.h 1处,MainForm.cpp 3处
  • 抽象:修改2处,Shape.h 1处,MainForm.cpp 1处,耦合更低(如果用到工厂设计模式,甚至在MainForm.cpp不需要改变)

第二种写法的同用性更好,需求变更时修改更少

学设计模式的一些个人体会

  • 设计模式用的好,代码就扩展性更好
  • 什么是好的软件设计?软件设计的金科玉律:复用!
  • 变化是复用的天敌,面向对象设计最大的优势在于:抵御变化!
  • 产业强盛的标志:接口标准化!
  • 设计模式本质 当算法全部都是不稳定的,或者全部都是稳定的,那设计模式就没有意义了 设计模式最大的作用是在变化和稳定之间寻找隔离点,分离他们,从而来管理变化
  • 复用的误解 复用指的是编译级别的原封不动,追加和复制源代码到别处都不算复用
  • 依赖的误解 A依赖B,是指A编译的时候B必须要存在才能编译通过。一般这里说的依赖特指编译时依赖而不是运行时依赖
  • 条件判断 条件判断是结构化编程的一种分而治之的策略,而设计模式提倡的是一种抽象的策略 消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式 看到if-else可能是一种bad smell,除非if-else是比较稳定不变的,比如一周只有七天,那可以用
  • C++不推荐多继承,多继承带来很多耦合性的问题。但是可以继承一个主基类,其他都是辅助抽象基类接口

我在实战中可能会接触得比较多的:

  • 单例模式,这个可以说是最常用的
  • 策略模式,之前弄ipv4和ipv6不同行为时用过 装饰模式,同上
  • 观察者模式,很多语言或框架都有这个
  • 适配器模式,很久以前弄Android程序时接触过
  • 代理模式,很多语言或框架都有这个,像 Vue3 使用的 js 的 proxy
  • 工厂模式?