1.面向对象的设计原则:
- 依赖倒置原本(DIP):
- 高层模块(稳定)不应该依赖底层模块(变化),二者都应该依赖于抽象(稳定)。
- 抽象(稳定)不应该依赖实现细节(变化),实现细节应该依赖抽象(稳定)。
- 开放封闭原则(OCP):
- 对扩展开放,对更改封闭。
- 类模块应该是可扩展的,但是不可以修改。
- 单一职责原则(SRP)
- 一个类应该仅有一个引起它变化的原因。
- 变化的方向隐含着类的责任。
- Liskov替换原则(LSP)
- 子类必须能够替换他们的基类(IS-A)。
- 继承表达类型抽象。
- 接口隔离原则(ISP)
- 不应该强迫客户程序依赖它们不用的方法。
- 接口应该小而完备。
- 优先使用对象组合,而不是类继承
- 类继承通常为“白箱复用”,对象组合通常为“黑箱复用”。
- 继承在某种程度上破坏了封装性,子类父类耦合度高。
- 而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。
- 封装变化点
- 使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层间的松耦合。
- 针对接口编程,而不是针对实现编程
2.单例模式
2.1 说说什么是单例设计模式,如何实现?
参考回答
-
单例模式定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
那么我们就必须保证:
(1)该类不能被复制。
(2)该类不能被公开的创造。
那么对于C++来说,它的构造函数,拷贝构造函数和赋值函数都不能被公开调用。
-
单例模式实现方式
单例模式通常有两种模式,分别为懒汉式单例和饿汉式单例。两种模式实现方式分别如下:
(1)懒汉式设计模式实现方式(2种)
a. 静态指针 + 用到时初始化
b. 局部静态变量
(2)饿汉式设计模式(2种)
a. 直接定义静态对象
b. 静态指针 + 类外初始化时new空间实现
懒汉模式:
懒汉模式的特点是延迟加载,比如配置文件,采用懒汉式的方法,配置文件的实例直到用到的时候才会加载,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化。
//代码示例(线程不安全),单线程可用
template<typename T>
class Singleton
{
public:
static T& getInstance() //静态成员函数
{
if(value_ == nullptr)
{
value_ = new T();
}
return *value_;
}
private:
Singleton();
~Singleten();
static T* value_; //静态变量
};
template <typename T>
T* Singleton<T>::value_ = NULL;
在单线程中,这样的写法是可以正确使用的,但是在多线程中就不行了,该方法是线程不安全的。
- 假如线程A和线程B, 这两个线程要访问getInstance函数,线程A进入getInstance函数,并检测if条件,由于是第一次进入,value为空,if条件成立,准备创建对象实例。
- 但是,线程A有可能被OS的调度器中断而挂起睡眠,而将控制权交给线程B。
- 线程B同样来到if条件,发现value还是为NULL,因为线程A还没来得及构造它就已经被中断了。此时假设线程B完成了对象的创建,并顺利的返回。
- 之后线程A被唤醒,继续执行new再次创建对象,这样一来,两个线程就构建两个对象实例,这就破坏了唯一性。
另外,还存在内存泄漏的问题,new出来的东西始终没有释放,下面是一种饿汉式的一种线程安全的改进。
//代码实例(线性安全)
template <typename T>
class Singleton
{
public:
static T& getInstance()
{
if(value_ == nullptr)
{
value_ = new T();
}
return *value_;
}
private:
class CGarbo
{
public:
~CGarbo()
{
if(Singleton::value_)
delete Singleton::value_;
}
}
static CGarbo Garbo; //一定要初始化,不然程序结束时不会析构garbo
Singleton();
~Singleton();
static T& value_;
};
template <typename T>
T* Singleton<T>::value_ = NULL:
在程序运行结束时,系统会调用Singleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。使用这种方法释放单例对象有以下特征:
- 在单例类内部定义专有的嵌套类;
- 在单例类内定义私有的专门用于释放的静态成员;
- 利用程序在结束时析构全局变量的特性,选择最终的释放时机。
加锁的方式
//C++ 11版本之后的跨平台实现(volatile)
std::atomic<Singleton*> Singleton::m_instace; //声明原子对象
std::mutex Singleton::m_mutex;
Singleton* Singleton :: getInstance()
{
Singleton *tep = m_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acqure); // 获取内存fence,可屏蔽编译器reorder
if(tmp == nullptr)
{
std::lock_guard<std::mutex> lock(m_mutex);//加锁
tmp = m_instance.load(std::memory_order_relaxed);
if(tmp == nullptr)
{
tmp = new Singleton;
std::atomic_throd_fence(std::memory_order_relaxed);//释放内存fence
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
饿汉模式
单例类定义的时候就进行实例化。因为main函数执行之前,全局作用域的类成员静态变量m_Instance已经初始化,故没有多线程的问题。
静态指针 + 类外初始化时new空间实现
class Singleton
{
private:
static Singleton *p;
protected:
Singleton(){}
public:
static Singleton *initance();
};
Singleton *SIngleton::p = new Single;
Singleton *Singleton::initance()
{
return p;
}
3.工厂模式
-
工厂设计模式的定义
定义一个创建对象的接口,让子类决定实例化哪个类,而对象的创建统一交由工厂去生产,有良好的封装性,既做到了解耦,也保证了最少知识原则。
-
工厂设计模式分类
工厂模式属于创建型模式,大致可以分为三类,简单工厂模式、工厂方法模式、抽象工厂模式。听上去差不多,都是工厂模式。下面一个个介绍:
- 简单工厂模式
- 它的主要特点是需要在工厂类中做判断,从而创造相应的产品。当增加新的产品时,就需要修改工厂类。
**举例:**有一家生产处理器核的厂家,它只有一个工厂,能够生产两种型号的处理器核。客户需要什么样的处理器核,一定要显示地告诉生产工厂。下面给出一种实现方案:
//程序实例(简单工厂模式)
enum CTYPE {COREA, COREB}
class SingleCore
{
public:
virtual void Show() = 0;
};
// 单核A
class SingleCoreA : public SingleCore
{
public:
void Show(){ cout << "SingleCore A" << endl;}
};
// 单核B
class SingleCoreB : public SingleCore
{
public:
void Show(){ cout << "SingleCore B" << endl;}
};
//唯一工厂,可以生产两种型号的处理器核,在内容中判断
class Factory
{
SingleCore *CreateSingleCore(enum CTYPE ctype)
{
if(ctype == COREA) //工厂内部判断
return new SingleCoreA(); //生产核A
else if(ctype == COREB)
return new SingleCoreB(); //生产核B
else
return NULL;
}
};
优点: 简单工厂模式可以根据需求,动态生成使用者所需类的对象,而使用者不用去知道怎么创建对象,使得各个模块各司其职,降低了系统的耦合性。
缺点:就是要增加新的核类型时,就需要修改工厂类。这就违反了开放封闭原则:软件实体(类、模块、函数)可以扩展,但是不可修改。
- 工厂方法模式
- 所谓工厂方法模式,是指定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
**举例:**这家生产处理器核的产家赚了不少钱,于是决定再开设一个工厂专门用来生产B型号的单核,而原来的工厂专门用来生产A型号的单核。这时,客户要做的是找好工厂,比如要A型号的核,就找A工厂要;否则找B工厂要,不再需要告诉工厂具体要什么型号的处理器核了。下面给出一个实现方案:
//程序实例,工厂方法模式
class SingleCore
{
public:
virtual void Show()=0;
};
// 单核A
class SingleCoreA : public SingleCore
{
void Show(){ cout << "SingleCoreA" << endl;}
};
//单核B
class SingleCoreB : public SingleCore
{
void Show(){ cout << "SingleCoreB" << endl;}
};
class Factory
{
public:
virtual SingleCore * CreateSingleCore() = 0;
};
//生产A核的工厂
class FactoryA : public Factory
{
public:
SingleCore *CreateSingleCore(){ return new SingleCoreA;}
};
//生产B核的工厂
class FactoryB : public Factory
{
public:
SingleCore *CreateSingleCore(){ return new SingleCoreB;}
};
优点: 扩展性好,符合了开闭原则,新增一种产品时,只需增加改对应的产品类和对应的工厂子类即可。
缺点: 每增加一种产品,就需要增加一个对象的工厂。如果这家公司发展迅速,推出了很多新的处理器核,那么就要开设相应的新工厂。在C++实现中,就是要定义一个个的工厂类。显然,相比简单工厂模式,工厂方法模式需要更多的类定义。
- 抽象工厂模式
举例: 这家公司的技术不断进步,不仅可以生产单核处理器,也能生产多核处理器。现在简单工厂模式和工厂方法模式都鞭长莫及。抽象工厂模式登场了。它的定义为提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。具体这样应用,这家公司还是开设两个工厂,一个专门用来生产A型号的单核多核处理器,而另一个工厂专门用来生产B型号的单核多核处理器,下面给出实现的代码:
//程序实例,抽象工厂模式
class SingleCore
{
public:
virtual void Show()=0;
};
//单核A
class SingleCoreA : public SingleCore
{
public:
void Show(){ cout << "SingleCore A" << endl;}
};
//单核B
class SingleCoreB : public SingleCore
{
public:
void Show(){ cout << "SingleCore B" << endl;}
};
class MultiCore
{
public:
virtual void Show() = 0;
};
//多核A
class MultiCoreA : public MultiCore
{
public:
void Show(){ cout << "MultiCOre A" << endl;}
};
//多核B
class MultiCoreB : public MultiCore
{
public:
void Show(){ cout << "MultiCore B" << endl;}
};
//工厂
class Factory
{
public:
virtual SingleCore *CreateSingleCore = 0;
virtual MultiCore * CreateMultiCore = 0;
};
//工厂A,专门用来生产A型号的处理器
class FactoryA : public Factory
{
public:
SingleCore * CreateSingleCore() { return new SingleCoreA();}
MultiCore * CreateMultiCore() { return new MultiCore();}
};
//工厂B,专门用来生产B信号的处理器
class FactoryB : public Factory
{
public:
SingleCore * CreateSingleCore() { return new SingleCoreB();}
MultiCore* CreateMultiCore() { return new MultiCoreB();}
};
优点: 工厂抽象类创建了多个类型的产品,当有需求时,可以创建相关产品子类和子工厂类来获取。
缺点: 扩展新种类产品时困难。抽象工厂模式需要我们在工厂抽象类中提前确定了可能需要的产品种类,以满足不同型号的多种产品的需求。但是如果我们需要的产品种类并没有在工厂抽象类中提前确定,那我们就需要去修改工厂抽象类了,而一旦修改了工厂抽象类,那么所有的工厂子类也需要修改,这样显然扩展不方便。
4.装饰模式
-
装饰器计模式的定义
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。关键在于:继承的同时组合基类。
-
优点
(1)装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用;
(2)通过使用不同装饰类及这些装饰类的排列组合,可以实现不同效果;
(3)装饰器模式完全遵守开闭原则。
-
缺点
装饰模式会增加许多子类,过度使用会增加程序得复杂性。
-
装饰模式的结构与实现
通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰模式的目标。下面来分析其基本结构和实现方法。
装饰模式主要包含以下角色:
(1)抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
(2)具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。
(3)抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
(4)具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
装饰模式的结构图如下图所示:
#include<string>
#include<iostream>
using namespace std;
//基础组件接口定义了可以被装饰器修改的操作
class Componet
{
public:
virtual ~Componet() {}
virtual std::string Operation() const = 0;
};
//具体组件提供了操作的默认实现。这些类在程序中可能会有几个变体
class ConcreteComponet : public Componet
{
public:
std::string Operation() const override{
return "ConcreteComponet";
}
};
//装饰器基类和其他组件遵循相同的接口。这个类的主要目的是为所有的具体装饰器定义封装接口。
//封装的默认实现代码中可能会包含一个保存被封装组件的成员变量,并且负责对齐进行初始化
class Decorator : public Componet
{
protected:
Componet* componet_;
public:
Decorator(Componet* componet) :componet_(componet) {}
//装饰器会将所有的工作分派给被封装的组件
std::string Operation() const override {
return this->componet_->Operation();
}
};
//具体装饰器必须在被封装对象上调用方法,不过也可以自行在结果中添加一些内容。
class ConcreteDecoratorA :public Decorator
{
//装饰器可以调用父类的是实现,来替代直接调用组件方法。
public:
ConcreteDecoratorA(Componet* componet) :Decorator(componet) {}
std::string Operation() const override {
return "ConcreteDecoratorA(" + Decorator::Operation() + ")";
}
};
//装饰器可以在调用封装的组件对象的方法前后执行自己的方法
class ConcreteDecoratorB :public Decorator
{
public:
ConcreteDecoratorB(Componet* componet) :Decorator(componet)
{}
std::string Operation() const override {
return "ConcreteDecoratorB(" + Decorator::Operation() + ")";
}
};
//客户端代码可以使用组件接口来操作所有的具体对象。这种方式可以使客户端和具体的实现类脱耦
void ClientCode(Componet* componet) {
cout << "Result:" << componet->Operation() << endl;
}
int main()
{
Componet* simple = new ConcreteComponet;
cout << "Client: I've got a simple componet" << endl;
ClientCode(simple);
cout << endl;
Componet* decorator1 = new ConcreteDecoratorA(simple);
Componet* decorator2 = new ConcreteDecoratorB(simple);
cout << "Client:Now I've got a decorated componet" << endl;
ClientCode(decorator2);
cout << endl;
delete simple;
delete decorator1;
delete decorator2;
system("pause");
return 0;
}
5.观察者模式
-
观察者设计模式的定义
指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
-
优点
(1)降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
(2)目标与观察者之间建立了一套触发机制。
-
缺点
(1)目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
(2)当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
-
观察者设计模式的结构与实现
观察者模式的主要角色如下:
(1)抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
(2)具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
(3)抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
(4)具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
可以举个博客订阅的例子(微信公众号),当博主发表新文章的时候,即博主状态发生了改变,那些订阅的读者就会收到通知,然后进行相应的动作,比如去看文章,或者收藏起来。博主与读者之间存在种一对多的依赖关系。下面给出相应的UML图设计:
观察者模式的结构图
可以看到博客类中有一个观察者链表(即订阅者),当博客的状态发生变化时,通过Notify成员函数通知所有的观察者,告诉他们博客的状态更新了。而观察者通过Update成员函数获取博客的状态信息。代码实现不难,下面给出C++的一种实现。
#include<list>
#include<string>
//观察者
class Observer
{
public:
Observer() {}
virtual ~Observer() {}
virtual void Update() {}
};
//博客
class Blog
{
public:
Blog() {}
virtual ~Blog() {}
void Attach(Observer* observer) { m_observers.push_back(observer); } //添加观察者
void Remover(Observer* observer) { m_observers.remove(observer); } //移除观察者
void Notify() //通知观察者
{
for (list<Observer*>::iterator it = m_observers.begin(); it != m_observers.end(); it++)
{
(*it)->Update();
}
}
virtual void SetStatus(string s) { m_status = s; } //设置状态
virtual string GetStatus() { return m_status; } //返回状态
private:
list<Observer*> m_observers; //观察者链表
protected:
string m_status; //状态
};
//以上是观察者和博客的基类,定义了通用接口。博客类主要完成观察者的添加、移除、通知操作,设置和获得状态仅仅是一个默认实现。
//下面给出它们相应的子类实现
//定义具体的博客类
class BlogCSDN : public Blog
{
public:
BlogCSDN(string name) : m_name(name){}
~BlogCSDN() {}
void SetStatus(string s) { m_status = "CSDN通知:" + m_name + s; } //具体的状态信息
string GetStatus() { return m_status; }
private:
string m_name; //博主名字
};
//具体观察者
class ObserverBlog : public Observer
{
private:
string m_name; //观察者名称
Blog* m_blog; //观察者博客,当然以链表形式更好,就可以观察更多个博客
public:
ObserverBlog(string name, Blog* blog) :m_name(name), m_blog(blog) {}
~ObserverBlog() {}
void Update() //获取更新状态
{
string status = m_blog->GetStatus();
cout << m_name << "-----" << status << endl;
}
};
int main()
{
Blog* blog = new BlogCSDN("wuzhekai1985");
Observer* observer = new ObserverBlog("tutupig", blog);
blog->Attach(observer);
blog->SetStatus("发表设计模式C++实现(15)——观察者模式");
blog->Notify();
delete blog;
delete observer;
system("pause");
return 0;
}