一、设计模式的分类
创建型模式(五种):
工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式(七种):
适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式(十一种):
共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
二、设计模式的六大原则
总原则:开闭原则
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。
1、单一职责原则
不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。
2、里氏替换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科
里氏替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。
3、依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。
4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
5、迪米特法则(最少知道原则)(Demeter Principle)
就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。
6、合成复用原则(Composite Reuse Principle)
原则是尽量首先使用合成/聚合的方式,而不是使用继承。
三、设计模式
1. 工厂模式
工厂模式属于创建型模式,大致可以分为三类,简单工厂模式、工厂方法模式、抽象工厂模式。听上去差不多,都是工厂模式。下面一个个介绍,首先介绍简单工厂模式,它的主要特点是需要在工厂类中做判断,从而创造相应的产品。当增加新的产品时,就需要修改工厂类。有点抽象,举个例子就明白了。
1.1 简单工厂模式:
有一家生产处理器的厂家,它只有一个工厂,能够生产两种型号的处理器。客户需要什么样的处理器,一定要显示地告诉生产工厂。下面给出一种实现方案。
enum CTYPE {i5, i7};
//CPU基类
class CPU
{
public:
virtual void Show() = 0;
};
//CPU_i5 子类
class CPU_i5: public CPU
{
public:
void Show() { cout<<"CPU_i5"<<endl; }
};
//CPU_i7 子类
class CPU_i7: public CPU
{
public:
void Show() { cout<<"CPU_i7"<<endl; }
};
//唯一的工厂,可以生产两种型号的处理器,在工厂内部做判断
class Factory
{
public:
CPU* CreateCPU(enum CTYPE ctype)
{
if(ctype == i5) //工厂内部判断
return new CPU_i5(); //生产i5
else if(ctype == i7)
return new CPU_i7(); //生产i7
else
return NULL;
}
};
int main()
{
Factory* factory = new Factory;
CPU* cpu_i5 = factory->CreateCPU( i5);
cpu_i5->Show();
system("pause");
return 0;
}
这样设计的主要缺点之前也提到过,就是要增加新的核类型时,就需要修改工厂类。这就违反了开放封闭原则:软件实体(类、模块、函数)可以扩展,但是不可修改。于是,工厂方法模式出现了。
1.2 工厂方法模式:
所谓工厂方法模式,是指定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
听起来很抽象,还是以刚才的例子解释。Intel赚了不少钱,于是决定将i5和i7的生产分开到两个不同的工厂。这时,客户要做的是找好工厂,比如要i5,就找i5的工厂;否则找i7的工厂要,不再需要告诉工厂具体要什么型号的处理器了。下面给出一个实现方案。
//CPU基类
class CPU
{
public:
virtual void Show() = 0;
};
//CPU_i5 子类
class CPU_i5: public CPU
{
public:
void Show() { cout<<"CPU_i5"<<endl; }
};
//CPU_i7 子类
class CPU_i7: public CPU
{
public:
void Show() { cout<<"CPU_i7"<<endl; }
};
//总部工厂
class Factory
{
public:
virtual CPU* CreateCPU() = 0;
};
//生产i5的工厂
class i5Factory : public Factory
{
public:
virtual CPU* CreateCPU()
{
return new CPU_i5;
}
} ;
//生产i7的工厂
class i7Factory : public Factory
{
public:
virtual CPU* CreateCPU()
{
return new CPU_i7;
}
};
int main()
{
//实例化一个工厂的子类对象
Factory* i7factory = new i7Factory;
CPU* i7 = i7factory->createCPU();
i7->Show();
system("pause");
return 0;
}
工厂方法模式也有缺点,每增加一种产品,就需要增加一个对象的工厂。如果这家公司发展迅速,推出了很多新的处理器,那么就要开设相应的新工厂。在C++实现中,就是要定义一个个的工厂类。显然,相比简单工厂模式,工厂方法模式需要更多的类定义。
1.3 抽象工厂模式:
既然有了简单工厂模式和工厂方法模式,为什么还要有抽象工厂模式呢?它到底有什么作用呢?还是举这个例子,这家公司的技术不断进步,不仅可以生产CPU,也能生产GPU。现在简单工厂模式和工厂方法模式都鞭长莫及。抽象工厂模式登场了。它的定义为提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。具体这样应用,这家公司还是开设两个工厂,一个专门用来生产i5型号的CPU和GPU,而另一个工厂专门用来生产i7型号的CPU和GPU,下面给出实现的代码。
//CPU
class CPU
{
public:
virtual void Show() = 0;
};
class CPU_i5: public CPU
{
public:
void Show() { cout<<"CPU_i5"<<endl; }
};
class CPU_i7 :public CPU
{
public:
void Show() { cout<<"CPU_i7"<<endl; }
};
//GPU
class GPU
{
public:
virtual void Show() = 0;
};
class GPU_i5 : public GPU
{
public:
void Show() { cout<<"GPU_i5"<<endl; }
};
class GPU_i7 : public GPU
{
public:
void Show() { cout<<"GPU_i7"<<endl; }
};
//工厂
class Factory
{
public:
virtual CPU* CreateCPU() = 0;
virtual GPU* CreateGPU() = 0;
};
//i5工厂,专门用来生产i5型号的处理器
class i5Factory :public Factory
{
public:
CPU* CreateCPU() { return new CPU_i5(); }
GPU* CreateGPU() { return new GPU_i5(); }
};
//i7工厂,专门用来生产i7型号的处理器
class i7Factory : public Factory
{
public:
CPU* CreateCPU() { return new GPU_i5(); }
GPU* CreateGPU() { return new GPU_i7(); }
};
int main()
{
Factory * i5factory = new i5Factory;
CPU * i5CPU = i5factory->CreateCPU();
i5CPU->Show();
GPU * i5GPU = i5factory->CreateGPU();
i5GPU->Show();
system("pause");
return 0;
}
1.4 三种工厂模式的UML图
简单工厂模式:
工厂方法模式:
抽象工厂模式:
2. 观察者模式
观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。它还有两个别名,依赖(Dependents),发布-订阅(Publish-Subsrcibe)。可以举个博客订阅的例子,当博主发表新文章的时候,即博主状态发生了改变,那些订阅的读者就会收到通知,然后进行相应的动作,比如去看文章,或者收藏起来。博主与读者之间存在种一对多的依赖关系。下面给出相应的UML图设计。
可以看到博客类中有一个观察者链表(即订阅者),当博客的状态发生变化时,通过Notify成员函数通知所有的观察者,告诉他们博客的状态更新了。而观察者通过Update成员函数获取博客的状态信息。
观察者基类
//观察者基类
class Observer
{
public:
Observer(){}
virtual ~Observer() {}
virtual void Update() {}
};
具体观察者类
class ObserverA:public Observer
{
private:
string m_Name;//观察者姓名
Blog * m_Blog;//观察的博客, 以链表形式更好,可以观察多个博客
public:
//构造
ObserverA(string obs_name, Blog* obs_blog) :m_Name(obs_name), m_Blog(obs_blog)
{
}
//析构
~ObserverA()
{
}
//重写update
void Update()//获得更新
{
string status = m_Blog->GetStatus();
cout << this->m_Name << "--------" << status << endl;
}
};
博客基类
//博客基类
class Blog
{
public:
Blog() {}
virtual ~Blog() {}
//向观察者链表中添加观察者
void Attach(Observer* obs)
{
m_Observer.push_back(obs);
}
//移除观察者
void Remove(Observer* obs)
{
m_Observer.remove(obs);
}
//通知观察者
void Notify()
{
//遍历观察者链表
for (list<Observer*>::iterator it = m_Observer.begin(); it != m_Observer.end(); it++)
{
(*it)->Update();
}
}
//设置状态
virtual void setStatus(string s)
{
m_Status = s;
}
//获得状态
virtual string GetStatus()
{
return m_Status;
}
private:
list<Observer*> m_Observer;//观察者链表
protected:
string m_Status;//状态
};
具体博客类
//具体博客实现类
class MyBlog:public Blog
{
private:
string m_Name;//博客名称
public:
MyBlog(string name)
{
this->m_Name = name;
}
~MyBlog()
{
}
void setStatus(string s)
{
this->m_Status = "Blog通知: " + this->m_Name + s;
}
string GetStatus()
{
return this->m_Status;
}
};
main()
//创建一个blog子类对象
Blog * blogA = new MyBlog("C++设计模式");
//创建一个observer子类对象
Observer * obsA= new ObserverA("JiaoZJ", blogA);
//博客添加观察者
blogA->Attach(obsA);
//博客设置状态
blogA->setStatus("发表: 观察者模式实现");
//博客通知观察者
blogA->Notify();
delete blogA;
delete obsA;
3. 单例模式
通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。
黑马提供的思路:
- 创建私有的 静态 类指针 变量(静态变量要类外初始化)
- 构造函数私有化 构造函数中实例化对象
- 创建一个静态方法返回该实例