C++设计模式介绍
一、深入理解面向对象
向下:深入理解三大面向对象机制。
- 封装,隐藏内部实现
- 继承,复用现有代码
- 多态,改写对象行为
向上:深刻把握面向对象机制所带来的抽象意义,理解如何使用这些机制来表达现实世界,掌握什么是“好的面向对象设计”。
1.软件设计复杂的根本原因:变化
- 客户需求的变化
- 技术平台的变化
- 开发团队的变化
- 市场环境的变化
2.如何解决复杂性?
①分解
人们面对复杂性有一个常见的做法:即分而治之,将大问题分解为多个小问题,将复杂问题分解为多个简单问题。
②抽象
更高层次来讲,人们处理复杂性有一个通用的技术,即抽象。由于不能掌握全部的复杂对象,我们选择忽视它的非本质细节,而去处理泛化和理想化了的对象模型。
什么是好的软件设计?软件设计的金科玉律:复用!
变化是复用的天敌!面向对象设计最大的优势在于:抵御变化!
3.重新认识面向对象
①理解隔离变化
从宏观层面来看,面向对象的构建方式更能适应软件的变化,能将变化所带来的影响减为最小。
②各司其职
从微观层面来看,面向对象的方式更强调各个类的“责任”。
由于需求变化导致的新增类型不应该影响原来类型的实现——是所谓各负其责。
对象是什么?
- 从语言实现层面来看,对象封装了代码和数据。
- 从规格层面讲,对象是一系列可被使用的公共接口。
- 从概念层面讲,对象是某种拥有责任的抽象。
二、八大原则
设计思想核心:高内聚,低耦合。
重点:关注变化点和稳定点。
1.单一职责原则( SRP)
- 一个类应该仅有一个引起它变化的原因。
这意味着一个类应该只负责一件事,这样可以降低类的复杂度,提高内聚性。
2.开放封闭原则(OCP)
- 对扩展开放,对更改封闭。
- 类模块应该是可扩展的,但是不可修改。
可以通过封装和抽象在现有代码的基础上添加新功能,从而适应需求的变化。
3.依赖倒置原则(DIP)
- 高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定)。
- 抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)。
这个原则要求我们更多地依赖于抽象和接口,而不是具体的实现类,从而降低模块之间的耦合度。
4.里氏替换原则(LSP)
- 子类必须能够替换它们的基类(IS-A)。
- 继承表达类型抽象。
这个原则强调了子类与父类之间的替换关系,确保子类的行为不会违反父类的约定。
5.接口隔离原则(ISP )
- 不应该强迫客户程序依赖它们不用的方法。
- 接口应该小而完备。
这个原则要求我们只提供必要的方法,避免提供不必要的接口功能。
6.优先使用对象组合,而不是类继承
- 类继承通常为“白箱复用”,即子类可以访问父类的所有实现细节。
- 继承在某种程度上破坏了封装性,子类父类耦合度高。
- 对象组合通常为“黑箱复用”。即只暴露必要的接口,减少类之间的依赖。
- 对象组合只要求被组合的对象具有良好定义的接口,耦合度低。
7.封装变化点
- 使用封装来创建对象之间的分界层,让设计者可以在分界层的—侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合。
8.针对接口编程,而不是针对实现编程
- 不应将变量类型声明为某个特定的具体类,而是声明为某个接口。
- 客户程序无需获知对象的具体类型,只需要知道对象所具有的接口。
- 减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案。
三、关于重构
1.重构获得模式(Refactoring to Patterns)
面向对象设计模式是“好的面向对象设计”,所谓“好的面象对象设计”指的是那些可以满足“应对变化,提高复用”的设计。
现代软件设计的特征是“需求的频繁变化”。设计模式的要点是“寻找变化点,然后在变化点处应用设计模式,从而来更好地应对需求的变化”。“什么时候、件么地点应用设计模式”比“理解设计模式结构本身”更为重要。
设计模式的应用不宜先入为主,一上来就使用设计模式是对设计模式的最大误用。没有一步到位的设计模式。敏捷软件开发实践提倡的“Refactoring to Patterns”是目前普遍公认的最好的使用设计模式的方法。
2.重构关键技法
- 静态→动态
- 早绑定→ 晚绑定
- 继承 → 组合
- 编译时依赖→ 运行时依赖
- 紧耦合 → 松耦合
3.什么时候不用模式
- 代码可读性很差时
- 需求理解还很浅时
- 变化没有显现时
- 不是系统的关键依赖点
- 项目没有复用价值时
- 项目将要发布时
4.经验之谈
- 不要为模式而模式
- 关注抽象类和接口
- 理清变化点和稳定点
- 审视依赖关系
- 要有Framework和Application的区隔思维
- 良好的设计是演化的结果
四、23种设计模式
常见的设计模式可以分为三大类:
1. 创建型模式(Creational):关注对象的实例化过程,包括如何实例化对象、隐藏对象的创建细节等。
- Factory Method(工厂模式):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。
- Abstract Factory(抽象工厂模式):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
- Singleton(单例模式):保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- Builder(建造者模式):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
- Prototype(原型模式):用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。
2. 结构型模式(Structural):关注对象之间的组合方式,以达到构建更大结构的目标。这些模式帮助你定义对象之间的关系,从而实现更大的结构。
- Bridge(桥接模式):将抽象部分与它的实现部分分离,使它们都可以独立地变化。
- Adapter(适配器模式):将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
- Decorator(装饰器模式):动态地给一个对象添加一些额外的职责。就扩展功能而言,Decorator模式比生成子类方式更为灵活。
- Composite(组合模式):将对象组合成树形结构以表示“部分一整体”的层次结构。Composite 使得客户对单个对象和组合对象的使用具有一致性。
- Flyweight(享元模式):运用共享技术有效地支持大量细粒度的对象。
- Facade(外观模式):为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
- Proxy(代理模式):为其他对象提供一个代理以控制对这个对象的访问。
3. 行为型模式(Behavioral):关注对象之间的通信方式,以及如何合作共同完成任务。这些模式涉及到对象之间的交互、责任分配等。
- Template Method(模板方法模式):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类不改变一个算法的结构即可重定义该算法的某些特定步骤。
- Strategy(策略模式):定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。该模式使得算法的变化可独立于使用它的客户。
- State(状态模式):允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。
- Observer(观察者模式):定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。
- Memento(备忘录模式):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。
- Mediator(中介者模式):用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
- Command(命令模式):将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。
- Visitor(访问者模式):表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
- Chain of Responsibility(责任链模式):解除请求的发送者和接收者之间的耦合,使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。
- Iterator(迭代器模式):提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
- Interpreter(解释器模式):给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
示例一:
class Point {
public:
int x;
int y;
};
class Line {
public:
Point start;
Point end;
Line(const Point& st, const Point& ed)
:start(st), end(ed) {}
};
//新增Circle类
class Circle {
//...
};
class Rect {
public:
Point leftUp;
int width;
int height;
Rect(const Point& leftup, int wid, int hei)
:leftUp(leftup), width(wid), height(hei) {}
};
class Form {
//..
};
class MainForm :public Form {
private:
Point p1;
Point p2;
vector<Line> lineVector;
vector<Rect> rectVector;
//改变
vector<Circle> circleVector;
public:
MainForm(const Point& p1, const Point& p2)
:p1(p1), p2(p2) {}
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 (rdoCircle.checked()) {
//...
circleVector.push_back(circle);
}*/
//...
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.DrawLine(Pens, Red,
rectVector[i].leftUp,
rectVector[i].width,
rectVector[i].height);
}
//改变
/*针对圆形
for (int i = 0; i < circleVector.size(); i++) {
e.Graphics.DrawLine(Pens, Red,
circleVector[i]);
}*/
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& st, const Point& ed)
:start(st), end(ed) {}
//实现自己的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 wid, int hei)
:leftUp(leftup), width(wid), height(hei) {}
//实现自己的Draw,负责画自己
virtual void Draw(const Graphics& g) {
g.DrawLine(Pens, Red,
leftUp, width, height);
}
};
//新增Circle类
class Circle :public Shape {
public:
//实现自己的Draw,负责画自己
virtual void Draw(const Graphics& g) {
g.DrawLine(Pens, Red
//...
);
}
};
class Form {
//...
};
class MainForm : public Form {
private:
Point p1;
Point p2;
//针对所有形状
vector<Shape*> shapeVector;
public:
MainForm(const Point& p1, const Point& p2)
:p1(p1), p2(p2) {}
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 (rdoCircle.checked()) {
//...
shapeVector.push_back(new 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);
}
结构方面:
1.第一段代码采用了直接继承的方式,每个形状类(Line、Rect、Circle)都单独管理,MainForm中分别维护了不同形状的vector集合。这种方式导致代码在处理形状相关的操作时显得分散,不利于统一管理。
2.第二段代码通过定义一个抽象基类Shape,实现了继承和多态。所有形状类都继承自Shape,并通过vector<Shape*>集中管理。这种结构利用多态简化了代码,使得对所有形状的操作都可以通过基类指针统一处理,提高了代码的模块化和可维护性。
可拓展性方面:
1.第一段代码在添加新形状时需要修改MainForm类,添加新的vector和处理逻辑,这降低了代码的可扩展性。
2.第二段代码在添加新形状时只需创建新的类并重写Shape基类中的Draw方法,然后在创建新形状对象时将其添加到vector<Shape*>中,具有更好的可扩展性。
综上,第二段代码在结构上更加清晰,易于维护,在可拓展性方面优于第一段代码。通过使用抽象类和多态,第二段代码提供了一种更加灵活和可扩展的设计方案。