赞
踩
原文链接:https://www.yuque.com/cppdev/patterns/ldbxv9#KafDI
笔记整理于:《设计模式》李建忠老师
程序员作为物理世界与现实世界的桥梁,要有两种思维方式:底层思维与抽象思维。
你虽然使用了封装、继承、多态这些面向对象的机制,但你代码或你的设计不一定是面向对象的,也不一定是优秀的面向对象的设计。而评判的标准即是抽象思维。
「你只是用了面向对象的方法,而没有用面向对象的思维」
向上 | 向下 |
---|---|
深入理解三大面向对象机制 1. 封装:隐藏内部实现 2. 继承:复用现有代码 3. 多态:改写对象行为 | 深刻把握面向对象机制所带来的抽象设计意义,理解如何使用这些机制来表达显示世界,掌握什么是“好的面向对象设计” |
「建筑商从来不会去想给一栋已建好的100层高的楼房底下再新修一个小地下室——这样做花费极大而且注定要失败。然而令人惊奇的是,软件系统的用户在要求作出类似改变时却不会仔细考虑,而且他们认为这只是需要简单编程的事。」——《Object-Oriented Analysis and Designwith Applications》(Grady Booch)
「软件设计的复杂」建筑商从来不会这样做,但软件领域经常有这种冲动。因为每一个需求变更,都会冲击现有代码的体系结构,这个挑战会带来巨大的奉献。这个就是软件设计固有的复杂性。
「软件设计复杂的根本原因」变化。这些变化会摧毁之前的设计方案。
人类面对复杂性问题时,有两种通用的思维模型:
思维模型 | 分解 | 抽象 |
---|---|---|
背景 | 人们面对复杂性有一个常见的做法:即分而治之 | 更高层次来讲,人们处理复杂性有一个通用的技术,即抽象思维。 |
具体含义 | 将大问题分解为多个小问题,将复杂 问题分解为多个简单问题 | 由于不能掌握全部的复杂对象,我们选择忽略它的非本质细节,而去处理泛化和理想化了的对象模型 |
举例 | 如,行政机构,社会组织流程 |
接下来以一个画图工具为例,说明两种思维模型
看两种思维模式在需求变更时,需要修改的地方。
分而治之,针对圆形怎么做、针对矩形怎么做,针对圆怎么做
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); } //刷新则调用onPaint 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); }
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<Shape*> shapeVector; //说明:这里不能是vector<Shape>,如果LineString赋值给Shape,会变成Shape结构空间 //这样就不能有多态性,Shape只能表示它自己,而不能表示子类 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); }
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, ...); } };
分解 | 抽象 | |
---|---|---|
做法 | 分而治之,针对线怎么做,针对圆怎么做,针对矩阵怎么做 | 抽象,把线、圆、矩阵归到一个抽象体系里面,统一的来处理 |
评论 | 不容易复用。在需求变更之后,你需要在每个分支上,多处理一种新情况 | 抽象通过通用的方法来进行统一处理。 在需求变更之后,不需要新增情况,只需扩展子类,然后统一处理即可。 |
抽象的扩展性更高,复用性强。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。