面向对象有两个思维模型:
- 底层思维:向下,如何把握机器底层从微观理解对象构造,封装、继承、多态
- 抽象思维:向上,如何将我们周围世界抽象为程序代码,包括面向对象、组件封装、设计模式、架构模式
软件设计的目标:复用!复用!复用!
重新认识面向对象:
传统的面向对象是:封装、继承、多态
从软件设计层面来看,面向对象是:
- 隔离变化:构建方式更能适应软件的变化,能将变化带来的影响减为最小
- 各司其责:由于需求变化导致的新增类型不应该影响原来类型的实现
面向对象设计原则:
- 依赖倒置原则:高层模块(稳定)应该依赖于抽象(稳定),实现细节也应该依赖于抽象(稳定)
- 开放封闭原则:对拓展开放,对更改封闭.类模块应该是可扩展的,但是不可修改
- 单一职责原则:一个类应该仅有一个引起它变化的原因。变化的方向隐含着类的责任
- Liskov替换原则:子类必须能够替换他们的基类(IS-A)。继承表达类型抽象
- 接口隔离原则:不应该强迫用户程序依赖他们不用的方法。接口应该小而完备
- 优先使用对象组合,而不是类继承
- 封装变化点:使用封装来创建对象之间的分界层,让设计者可以在分界层一侧进行修改,而不会对另一侧产生不良影响,从而实现层次间的松耦合
- 针对接口编程,而不是针对实现编程
23种设计模式的分类:
- 创建型:解决对象的创建工作
- 结构型:解决在需求变化时对对象的结构带来的冲击
- 行为型:应对需求变化为多个交互的类带来的冲击
老师的划分方法:
- 组件协作:Template Method、Strategy、Observer
- 单一职责:Decorator、Bridge
- 对象创建:Factory Method、Abstract Factory、Prototype、Builder
- 对象性能:Singleton、Flyweight
- 接口隔离:Facade、Proxy、Mediator、Adapter
- 状态变化:Memento、State
- 数据结构:Composite、Iterator、Chain of Resposibility
- 行为变化:Command、Vistor
- 领域问题:Interpreter
重构关键技法:
- 静态—动态
- 早绑定—晚绑定
- 继承—组合
- 编译时依赖—运行时依赖
- 紧耦合—松耦合
组件协作
通过晚绑定来实现框架与应用程序之间的松耦合
模板方法:Template Method
- 动机
在软件构建过程中,对于某一项任务,她常常有稳定的整体操作结构,但各个子步骤却又很多改变的需求,或者由于固有的原因而无法和任务的整体结构同时实现。
如何在确定稳定操作结构的前提下,来应对各个子步骤的变化或者晚期实现需求?
2.伪代码:
//程序库开发人员
class Library{
public:
//稳定 template method
void Run(){
Step1();
if (Step2()) { //支持变化 ==> 虚函数的多态调用
Step3();
}
for (int i = 0; i < 4; i++){
Step4(); //支持变化 ==> 虚函数的多态调用
}
Step5();
}
virtual ~Library(){ }
protected:
void Step1() { //稳定
//.....
}
void Step3() {//稳定
//.....
}
void Step5() { //稳定
//.....
}
virtual bool Step2() = 0;//变化
virtual void Step4() =0; //变化
};
//应用程序开发人员
class Application : public Library {
protected:
virtual bool Step2(){
//... 子类重写实现
}
virtual void Step4() {
//... 子类重写实现
}
};
int main()
{
Library* pLib=new Application();
lib->Run();
delete pLib;
}
}
3.理解:
从代码可以看出,其实就是在父类中定义了虚函数,子类中将其实现,这里的父类就可以看成是一个模板,后续使用中,照着这个模板进行改进就行。这利用了C++语言的虚函数特点实现了晚绑定的思想。“让我来调用你,你别调用我”。
父类的析构函数一般写成虚函数,这一点要注意
策略模式:Strategy
- 动机
在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。
如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
2.伪代码:
class TaxStrategy{
public:
virtual double Calculate(const Context& context)=0;
virtual ~TaxStrategy(){}
};
class CNTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class USTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class DETax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
//扩展
//******************************************************************
class FRTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//.........
}
};
//******************************************************************
class SalesOrder{
private:
TaxStrategy* strategy; // 用指针来实现多态性
public:
SalesOrder(StrategyFactory* strategyFactory){
this->strategy = strategyFactory->NewStrategy();
}
~SalesOrder(){
delete this->strategy;
}
public double CalculateTax(){
//...
Context context();
double val = strategy->Calculate(context); //多态调用
//...
}
};
3.理解
如果不使用Strategy模式,那么就要用if-else来实现上面代码的拓展,那么这就要去源码中修改判断条件,这个打破了开闭原则!所以,又把计算的功能作为一个纯虚函数,然后要拓展的时候,继承父类,重写计算方法,在调用的时候使用虚函数的调用特性,实现该模式。定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)。
所谓的策略,就是新增的一种方法,和原有的方法是同一级的,我要把它加进来,但是不能够影响原来的代码,就需要策略模式来实现。
有if-else的代码,就有使用策略模式的苗头!除非不是情况完全不可能改变,比如性别,星期几等,否则尽量少用if-else。从内存角度看,使用if-else会让内存加载一些可能不会用到的数据,从而降低性能。
4.类图
观察者模式:Observer
- 动机
在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系” ——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。
使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
2.伪代码:
class IProgress{
public:
virtual void DoProgress(float value)=0;
virtual ~IProgress(){}
};
class FileSplitter
{
string m_filePath;
int m_fileNumber;
List<IProgress*> m_iprogressList; // 抽象通知机制,支持多个观察者
public:
FileSplitter(const string& filePath, int fileNumber) :
m_filePath(filePath),
m_fileNumber(fileNumber){
}
void split(){
//1.读取大文件
//2.分批次向小文件中写入
for (int i = 0; i < m_fileNumber; i++){
//...
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
onProgress(progressValue);//发送通知
}
}
void addIProgress(IProgress* iprogress){
m_iprogressList.push_back(iprogress);
}
void removeIProgress(IProgress* iprogress){
m_iprogressList.remove(iprogress);
}
protected:
virtual void onProgress(float value){
List<IProgress*>::iterator itor=m_iprogressList.begin();
while (itor != m_iprogressList.end() )
(*itor)->DoProgress(value); //更新进度条
itor++;
}
}
};
class MainForm : public Form, public IProgress
{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;
public:
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
ConsoleNotifier cn;
FileSplitter splitter(filePath, number);
splitter.addIProgress(this); //订阅通知
splitter.addIProgress(&cn); //订阅通知
splitter.split();
splitter.removeIProgress(this);
}
virtual void DoProgress(float value){
progressBar->setValue(value);
}
};
class ConsoleNotifier : public IProgress {
public:
virtual void DoProgress(float value){
cout << ".";
}
};
3.理解:
使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。比如说鼠标监听事件等~
4.类图
单一职责
装饰模式:Decorator
组合优于继承
- 动机
在某些情况下我们可能会“过度地使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。
如何使“对象功能的扩展”能够根据需要来动态地实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响将为最低?
2.伪代码:
//业务操作
class Stream{
public:
virtual char Read(int number)=0;
virtual void Seek(int position)=0;
virtual void Write(char data)=0;
virtual ~Stream(){}
};
//主体类
class FileStream: public Stream{
public:
virtual char Read(int number){
//读文件流
}
virtual void Seek(int position){
//定位文件流
}
virtual void Write(char data){
//写文件流
}
};
class NetworkStream :public Stream{
public:
virtual char Read(int number){
//读网络流
}
virtual void Seek(int position){
//定位网络流
}
virtual void Write(char data){
//写网络流
}
};
class MemoryStream :public Stream{
public:
virtual char Read(int number){
//读内存流
}
virtual void Seek(int position){
//定位内存流
}
virtual void Write(char data){
//写内存流
}
};
//扩展操作
DecoratorStream: public Stream{
protected:
Stream* stream;//...
DecoratorStream(Stream * stm):stream(stm){
}
};
class CryptoStream: public DecoratorStream {
public:
CryptoStream(Stream* stm):DecoratorStream(stm){
}
virtual char Read(int number){
//额外的加密操作...
stream->Read(number);//读文件流
}
virtual void Seek(int position){
//额外的加密操作...
stream::Seek(position);//定位文件流
//额外的加密操作...
}
virtual void Write(byte data){
//额外的加密操作...
stream::Write(data);//写文件流
//额外的加密操作...
}
};
class BufferedStream : public DecoratorStream{
Stream* stream;//...
public:
BufferedStream(Stream* stm):DecoratorStream(stm){
}
//...
};
void Process(){
//运行时装配
FileStream* s1=new FileStream();
CryptoStream* s2=new CryptoStream(s1);
BufferedStream* s3=new BufferedStream(s1);
BufferedStream* s4=new BufferedStream(s2);
}
3.理解:
这个设计模式体现了“组合优于继承”的思想!Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。继承是为了完善接口的规范,组合是为了后期的装饰实现
4.类图:
桥模式:Bridge
- 动机
由于某些类型的固有的实现逻辑,使得它们具有两个维度的变化,乃至多个纬度的变化。如何应对这种“多维度的变化”?
如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个方向变化,而不引入额外的复杂度?
2.伪代码:
class Messager{
protected:
MessagerImp* messagerImp;//...
public:
virtual void Login(string username, string password)=0;
virtual void SendMessage(string message)=0;
virtual void SendPicture(Image image)=0;
virtual ~Messager(){}
};
class MessagerImp{
public:
virtual void PlaySound()=0;
virtual void DrawShape()=0;
virtual void WriteText()=0;
virtual void Connect()=0;
virtual MessagerImp(){}
};
//平台实现 n
class PCMessagerImp : public MessagerImp{
public:
virtual void PlaySound(){
//**********
}
virtual void DrawShape(){
//**********
}
virtual void WriteText(){
//**********
}
virtual void Connect(){
//**********
}
};
class MobileMessagerImp : public MessagerImp{
public:
virtual void PlaySound(){
//==========
}
virtual void DrawShape(){
//==========
}
virtual void WriteText(){
//==========
}
virtual void Connect(){
//==========
}
};
//业务抽象 m
//类的数目:1+n+m
class MessagerLite :public Messager {
public:
virtual void Login(string username, string password){
messagerImp->Connect();
//........
}
virtual void SendMessage(string message){
messagerImp->WriteText();
//........
}
virtual void SendPicture(Image image){
messagerImp->DrawShape();
//........
}
};
class MessagerPerfect :public Messager {
public:
virtual void Login(string username, string password){
messagerImp->PlaySound();
//********
messagerImp->Connect();
//........
}
virtual void SendMessage(string message){
messagerImp->PlaySound();
//********
messagerImp->WriteText();
//........
}
virtual void SendPicture(Image image){
messagerImp->PlaySound();
//********
messagerImp->DrawShape();
//........
}
};
void Process(){
//运行时装配
MessagerImp* mImp=new PCMessagerImp();
Messager *m =new Messager(mImp);
}
3.理解:
桥体现在哪里呢?代码中的MessagerImp就是这个桥,Messager中组合了这个桥,它的子类可以通过这个桥去实现Messager中的抽象方法。Bridge模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则(即一个类只有一个变化的原因),复用性比较差。Bridge模式是比多继承方案更好的解决方法。Bridge模式的应用一般在“两个非常强的变化维度”,有时一个类也有多于两个的变化维度,这时可以使用Bridge的扩展模式,用桥把这两个方向给连接起来。
4.类图:
对象创建
通过“对象创建” 模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。
工厂方法:Factory Method
- 动机
在软件系统中,经常面临着创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化。如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?
2.伪代码:
//抽象类
class ISplitter{
public:
virtual void split()=0;
virtual ~ISplitter(){}
};
//工厂基类
class SplitterFactory{
public:
virtual ISplitter* CreateSplitter()=0;
virtual ~SplitterFactory(){}
};
//------------------------------------------------------------------
//具体类
class BinarySplitter : public ISplitter{
};
class TxtSplitter: public ISplitter{
};
class PictureSplitter: public ISplitter{
};
class VideoSplitter: public ISplitter{
};
//具体工厂
class BinarySplitterFactory: public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new BinarySplitter();
}
};
class TxtSplitterFactory: public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new TxtSplitter();
}
};
class PictureSplitterFactory: public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new PictureSplitter();
}
};
class VideoSplitterFactory: public SplitterFactory{
public:
virtual ISplitter* CreateSplitter(){
return new VideoSplitter();
}
};
//------------------------------------------------------------------
class MainForm : public Form
{
SplitterFactory* factory;//工厂
public:
MainForm(SplitterFactory* factory){
this->factory=factory;
}
void Button1_Click(){
ISplitter * splitter=
factory->CreateSplitter(); //多态new
splitter->split();
}
};
3.理解:
称之为多态new。Factory Method模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱。Factory Method模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。Factory Method模式解决“单个对象”的需求变化。缺点在于要求创建方法/参数相同。
4.类图:
参考文章:侯捷C++八部曲笔记(三、设计模式)_侯捷c++设计模式-CSDN博客