理解面向对象机制
- 封装,隐藏内部实现
- 继承,复用现有代码
- 多态,改写对象行为
如何解决复杂性
分解:
人们面对复杂性有一个常见的做法:既分而治之,将大问题分解为多个小问题,将复杂问题分解为多个简单问题。
抽象:
更高层次来讲,人们处理复杂性有一个通用的技术,即抽象。由于不能掌握全部的复杂对象,我们选择忽视它的非本质细节,而去处理泛化和理想化了的对象模型。
例 当前代码实现了直线,矩形的绘制,但如果需要进行迭代更新,增加圆形根据设计模式优化相关实现:
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
在 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) 的体现,既:
- 高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定) 。
- 抽象(稳定)不应该依赖于实现细节(变化) ,实现细节应该依赖于抽象(稳定)。