问题引入:为什么要使用面向对象的设计?
面向对象设计最大的优势在于抵御变化,变化是复用的天敌!
如何解决复杂性?
下面通过代码来理解分解和抽象:
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 MainForm : public Form {
private:
Point p1;
Point p2;
vector<Line> lineVector;
vector<Rect> rectVector;
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);
}
this->Refresh();
Form::OnMouseUp(e);
}
void MainForm::OnPaint(const MouseEventArgs& 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.DrawRect(Pens.Red,
rectVector[i].leftUp,
rectVector[i].width,
rectVector[i].height);
}
Form::OnPaint(e);
}
class Shape {
virtual Doraw(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;
}
virtual void Draw(const Graphics& g) {
g.DrawLine(Pen.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;
}
virtual void Draw(const Graphics& g) {
g.DrawRect(Pen.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) {
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 MouseEventArgs& e) {
for(int i = 0; i < shapeVector.size(); i++) {
shapeVector[i]->Draw(e.Graphics);
}
Form::OnPaint(e);
}
重新认识面向对象
- 隔离变化
从宏观层面来看,面向对象的构建的构建方式更能是适应软件的变化,能将变化所带来的影响减为最小。 - 各司其职
从微观层面来讲,面向对象的方式更强调各个类的“职责”,由于需求变化导致的新增类型不应该影响原来类型的实现——是所谓各负其责。 - 对象是什么
从语言层面来讲:对象封装了代码和数据。 从规格层面来讲:对象是一系列可被使用的公共接口。 从概念层面来讲:对象是拥有某种责任的抽象
设计原则
1、依赖倒置原则
- 高层模块(稳定)不应该依赖底层模块(变化),二者都应该依赖抽象(稳定)
- 抽象(稳定)不应该依赖实现细节(变化),实现细节应该依赖于抽象(稳定)
总而言之,是易变化的依赖于稳定的,如果反过来,稳定的依赖变化的,稳定也就变得不稳定了。 上述分解的做法中: 高层模块(MainForm)依赖低层模块(Line,Rect),违背了依赖倒置原则。 上述抽象做法中: 高层模块(MainForm)和低层模块(Line,Rect)均依赖抽象(Shape), 实现细节(Line,Rect)依赖于抽象(Shape) 符合依赖倒置原则。
2、开闭原则
- 对扩展开放,对更改关闭
- 类模块应该是可扩展的,但是不可修改
上述分解的做法中: MainForm中,如果增加一个圆,那么需要在MainForm中增加circleVector,这样的话就对MainForm需要进行修改,违背了开闭原则。
3、单一职责原则
- 一个类应该仅有一个引起它变化的原因
- 变化的方向隐含着类的责任
4、里氏替换原则
- 子类只能扩展父类的功能,而不能改写父类的方法,子类必须保证替换父类的时候不能改变原有的逻辑。
- 继承表达类型抽象
5、接口隔离原则
- 不应该强迫客户程序依赖他们不用的方法。
- 接口应该小而完备。该public,piublic,不需要的就protect,private
6、优先使用对象组合,而不是继承
- 类继承通常为“白象复用”,对象组合通常为“黑箱复用”,
- 继承在某种程度上破坏了封装性,子类父类耦合度高,
- 而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。
7、封装变化点
- 使用封装来创建对象的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良影响,从而下实现层次间的松耦合
8、针对接口编程,而不是针对实现编程
- 不将变量类型声明为某个特定的具体类,而是声明为某个接口,
- 客户程序无需知道对象的具体类型,只需要知道对象所具有的接口,
- 减少系统中各部分的依赖,从而实现“高内聚,低耦合”的类型设计方案。
上述分解做法中,MainForm依赖Line,Rect,就是针对实现了,违背了此原则。而抽象做法中,MainForm依赖Shape,就是针对接口了。
|