Bootstrap

C++ 设计模式

理解面向对象机制

  • 封装,隐藏内部实现
  • 继承,复用现有代码
  • 多态,改写对象行为

如何解决复杂性

分解:
人们面对复杂性有一个常见的做法:既分而治之,将大问题分解为多个小问题,将复杂问题分解为多个简单问题。
抽象:
更高层次来讲,人们处理复杂性有一个通用的技术,即抽象。由于不能掌握全部的复杂对象,我们选择忽视它的非本质细节,而去处理泛化和理想化了的对象模型。

例 当前代码实现了直线,矩形的绘制,但如果需要进行迭代更新,增加圆形根据设计模式优化相关实现:

class Point {
public:
    int getX() { return x; };
    int getY() { return y; };
private:
    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
{
public:
    MainForm(){};
protected:

	// 当鼠标被按下时触发
    virtual void OnMouseDown(const MouseEventArgs& e)
    {
		// 当鼠标被按下时记录第一个点 p1
		p1.x = e.X;
		p1.y = e.Y;

		//...
		Form::OnMouseDown(e);
    }
	// 当鼠标被抬起时出发
    virtual void OnMouseUp(const MouseEventArgs& e)
    {
		// 当鼠标抬起后取第二个点 p2
		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); 
    }
	// 当界面被刷新时触发
	virtual void 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); 
	}
private:
    Point p1;
    Point P2;
    std::vector<Line> lineVector;
    std::vector<Rect> rectVector;
};

MainForm是高层模块,它依赖于Line和Rect这些低层模块,这样就使得一个需要稳定的模块依赖于一个不稳定的模块,这样就不太好

通过设计模式修改后的类:

首先设置一个 Shape(形状)的父类,设置一个纯虚函数Draw,和一个虚析构函数。
在这里只有虚析构函数子类通过多态释放的时候,子类的虚析构函数才会被正确的调用到

class Shape {
public:
	virtual void Draw(const Graphics& g) = 0;
	virtual ~Shape() {}
};

然后使得类 Line 和 Rect 继承 Shape ,并且重载(Overload) 了父类的 Draw

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

对 MainForm 的设计,删除 lineVector 和 rectVector 只需要设计 vector 的 Shape 就可以应对所有形状。

在这里需要注意的是:

vector<Shape*> shapeVector; 

在这里使用指针,如果涉及到多态写这里就要使用指针Shape*如果不用多态会导致对象切割。
完整代码实现:


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) {
		// 这里需要在 MainForm 中的析构函数中去释放这部分内存
		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));
	}

	//...
	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);
}

原本的代码实现增加圆形:

新增圆形类:


class Circle {
public:
	Point start;
	Point end;

	Circle(const Point& start, const Point& end) {
		this->start = start;
		this->end = end;
	}
};
 

然后在 MainForm 中添加圆形类容器 std::vector circleVector;
在 OnMouseUp 中保存圆形类数据在容器中:

else if (rdoCircle.Checked)
		{ 
			Circle rect(p1, p2);
			circleVector.push_back(rect);
		}

最终在绘制时 OnPaint 添加针对圆形的绘制:

		// 针对圆形 - 绘制
		for (int i = 0; i < circleVector.size(); i++) {
			e.Graphics.DrawLine(Pens.Red,
				circleVector[i].start.x,
				circleVector[i].start.y,
				circleVector[i].end.x,
				circleVector[i].end.y);
		}

通过设计模式新增加的类:

修改,以添加圆这个类,继承 Shape

class Circle : public Shape {
public:
	Point start;
	Point end;

	Circle(const Point& start, const Point& end) {
		this->start = start;
		this->end = end;
	}
};

然后在 OnMouseUp 中添加判断增加即可

	else if (rdoCircle.Checked)
	{
		Circle rect(p1, p2);
		shapeVector.push_back(new Circle(p1, p2));
	}

最终在 OnPaint 会根据多态指针去调用。
MainForm依赖于Shape,Line和Rect也都依赖于Shape。这个Shape就是抽象。抽象不应该依赖于实现细节,也就是抽象不应该去依赖于子类,因为抽象是稳定的,不应该去依赖一个变化的东西
这是面向对象 依赖倒置原则(DIP) 的体现,既:

  • 高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定) 。
  • 抽象(稳定)不应该依赖于实现细节(变化) ,实现细节应该依赖于抽象(稳定)。
;