思维方式

程序员作为物理世界与现实世界的桥梁,要有两种思维方式:底层思维与抽象思维。

  1. 底层思维:是一个向下的思维层次,决定了程序员与计算机的沟通能力,帮助程序员建立机器模型。
  2. 抽象思维:是一个向上的思维层次,帮助程序员抽象纷繁复杂的世界,决定程序员的代码复杂度。

image.png

深入理解面向对象

你虽然使用了封装、继承、多态这些面向对象的机制,但你代码或你的设计不一定是面向对象的,也不一定是优秀的面向对象的设计。而评判的标准即是抽象思维。
「你只是用了面向对象的方法,而没有用面向对象的思维」

向下 向上
深入理解三大面向对象机制
1. 封装:隐藏内部实现
1. 继承:复用现有代码
1. 多态:改写对象行为
深刻把握面向对象机制所带来的抽象设计意义,理解如何使用这些机制来表达显示世界,掌握什么是“好的面向对象设计”

软件设计固有的复杂性

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

「软件设计的复杂」建筑商从来不会这样做,但软件领域经常有这种冲动。因为每一个需求变更,都会冲击现有代码的体系结构,这个挑战会带来巨大的奉献。这个就是软件设计固有的复杂性。

「软件设计复杂的根本原因」变化。这些变化会摧毁之前的设计方案。

  1. 客户需求的变化
  2. 技术平台的变化
  3. 开发团队的变化
  4. 市场环境的变换

    如何解决这种复杂性

    人类面对复杂性问题时,有两种通用的思维模型:
思维模型 分解 抽象
背景 人们面对复杂性有一个常见的做法:即分而治之 更高层次来讲,人们处理复杂性有一个通用的技术,即抽象思维。
具体含义 将大问题分解为多个小问题,将复杂 问题分解为多个简单问题 由于不能掌握全部的复杂对象,我们选择忽略它的非本质细节,而去处理泛化和理想化了的对象模型
举例 如,行政机构,社会组织流程

接下来以一个画图工具为例,说明两种思维模型

  1. 一开始的需求:需要实现画线、画矩形两个方法
  2. 需求变更:需要在画线画矩阵的基础上,添加画圆

看两种思维模式在需求变更时,需要修改的地方。

第一种思维模型:分解(结构化)

分而治之,针对圆形怎么做、针对矩形怎么做,针对圆怎么做

  1. MainForm.cpp ```cpp class MainForm : public Form { private: Point p1; //鼠标点的第一个点 Point p2; //鼠标点的第二个点 //当下需求只有画线、画矩形 vector lineVector; vector rectVector; //需求变更后,新增画圆 vector 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;

  1. //...
  2. Form::OnMouseDown(e);

}

//鼠标抬起:取第二个点坐标 void MainForm::OnMouseUp(const MouseEventArgs& e){ p2.x = e.X; p2.y = e.Y;

  1. if (rdoLine.Checked){ //用户选择画线
  2. Line line(p1, p2);
  3. lineVector.push_back(line);
  4. }
  5. else if (rdoRect.Checked){ //用户选择画矩形
  6. int width = abs(p2.x - p1.x);
  7. int height = abs(p2.y - p1.y);
  8. Rect rect(p1, width, height);
  9. rectVector.push_back(rect);
  10. }
  11. //需求变更后,新增画圆
  12. else if (...){
  13. //...
  14. circleVector.push_back(circle);
  15. }
  16. //刷新则调用onPaint
  17. this->Refresh();
  18. 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); }

  1. //针对矩形
  2. for (int i = 0; i < rectVector.size(); i++){
  3. e.Graphics.DrawRectangle(Pens.Red,
  4. rectVector[i].leftUp,
  5. rectVector[i].width,
  6. rectVector[i].height);
  7. }
  8. //需求变更:添加针对圆形
  9. for (int i = 0; i < circleVector.size(); i++){
  10. e.Graphics.DrawCircle(Pens.Red, circleVector[i]);
  11. }
  12. //...
  13. Form::OnPaint(e);

}

  1. 2. shape.h
  2. ```cpp
  3. class Point{
  4. public:
  5. int x;
  6. int y;
  7. };
  8. class Line{
  9. public:
  10. Point start;
  11. Point end;
  12. Line(const Point& start, const Point& end){
  13. this->start = start;
  14. this->end = end;
  15. }
  16. };
  17. class Rect{
  18. public:
  19. Point leftUp;
  20. int width;
  21. int height;
  22. Rect(const Point& leftUp, int width, int height){
  23. this->leftUp = leftUp;
  24. this->width = width;
  25. this->height = height;
  26. }
  27. };
  28. //需求变更,增加代码:添加圆类
  29. class Circle{
  30. };

第二种思维模型:抽象(面向对象)

  1. MainFrom.cpp ```cpp class MainForm : public Form { private: Point p1; Point p2;

    //不需要一个类型一个数组,只需要一个父类指针就可对所有形状的描述 vector shapeVector; //说明:这里不能是vector,如果LineString赋值给Shape,会变成Shape结构空间 //这样就不能有多态性,Shape只能表示它自己,而不能表示子类

public: MainForm(){ //… } protected:

  1. virtual void OnMouseDown(const MouseEventArgs& e);
  2. virtual void OnMouseUp(const MouseEventArgs& e);
  3. virtual void OnPaint(const PaintEventArgs& e);

};

void MainForm::OnMouseDown(const MouseEventArgs& e){ p1.x = e.X; p1.y = e.Y;

  1. //...
  2. Form::OnMouseDown(e);

}

void MainForm::OnMouseUp(const MouseEventArgs& e){ p2.x = e.X; p2.y = e.Y;

  1. if (rdoLine.Checked){ //画线开关
  2. shapeVector.push_back(new Line(p1,p2));
  3. }
  4. else if (rdoRect.Checked){ //画矩形开关
  5. int width = abs(p2.x - p1.x);
  6. int height = abs(p2.y - p1.y);
  7. shapeVector.push_back(new Rect(p1, width, height));
  8. }
  9. //需求变更:添加画圆
  10. else if (...){
  11. //...
  12. shapeVector.push_back(circle);
  13. }
  14. //...
  15. this->Refresh();
  16. Form::OnMouseUp(e);

}

void MainForm::OnPaint(const PaintEventArgs& e){ //针对所有形状 for (int i = 0; i < shapeVector.size(); i++){ shapeVector[i]->Draw(e.Graphics); //多态调用,各负其责 }

  1. //...
  2. Form::OnPaint(e);

}

  1. 2. shape.h
  2. ```cpp
  3. class Shape{
  4. public:
  5. virtual void Draw(const Graphics& g)=0; //绘制
  6. virtual ~Shape() { } //析构
  7. };
  8. class Point{
  9. public:
  10. int x;
  11. int y;
  12. };
  13. class Line: public Shape{
  14. public:
  15. Point start;
  16. Point end;
  17. Line(const Point& start, const Point& end){
  18. this->start = start;
  19. this->end = end;
  20. }
  21. //实现自己的Draw,负责画自己
  22. virtual void Draw(const Graphics& g){
  23. g.DrawLine(Pens.Red,
  24. start.x, start.y,end.x, end.y);
  25. }
  26. };
  27. class Rect: public Shape{
  28. public:
  29. Point leftUp;
  30. int width;
  31. int height;
  32. Rect(const Point& leftUp, int width, int height){
  33. this->leftUp = leftUp;
  34. this->width = width;
  35. this->height = height;
  36. }
  37. //实现自己的Draw,负责画自己
  38. virtual void Draw(const Graphics& g){
  39. g.DrawRectangle(Pens.Red,
  40. leftUp,width,height);
  41. }
  42. };
  43. //需求变更所添加的代码
  44. class Circle : public Shape{
  45. public:
  46. //实现自己的Draw,负责画自己
  47. virtual void Draw(const Graphics& g){
  48. g.DrawCircle(Pens.Red,
  49. ...);
  50. }
  51. };

两者思维模型的对比

分解 抽象
做法 分而治之,针对线怎么做,针对圆怎么做,针对矩阵怎么做 抽象,把线、圆、矩阵归到一个抽象体系里面,统一的来处理
评论 不容易复用。在需求变更之后,你需要在每个分支上,多处理一种新情况 抽象通过通用的方法来进行统一处理。
在需求变更之后,不需要新增情况,只需扩展子类,然后统一处理即可。

抽象的扩展性更高,复用性强。