设计模式面试重点
mid
单例模式
说说什么是单例设计模式,如何实现
1.单例模式定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
前提
- 该类不能被复制。
- 该类不能被公开的创造
那么对于C++来说,它的构造函数,拷贝构造函数和赋值函数都不能被公开调用。
2.单例模式实现方式
单例模式通常有两种模式,分别为懒汉式单例和饿汉式单例。两种模式实现方式分别如下
懒汉式设计模式实现方式(2种)
-
静态指针 + 用到时初始化
-
局部静态变量
饿汉式设计模式(2种)
-
直接定义静态对象
-
静态指针 + 类外初始化时new空间实现
3.具体解析
1.懒汉模式: 懒汉模式的特点是延迟加载,比如配置文件,采用懒汉式的方法,配置文件的实例直到用到的时候才会加载,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化。以下是懒汉模式实现方式
懒汉模式实现一:静态指针 + 用到时初始化
template<typename T>
class Singleton {
public:
static T& getInstance() {
if (!value_) {
value_ = new T();
}
return *value_;
}
private:
Singleton();
~Singleton();
static T* value_;
};
template<typename T>
T* Singleton<T>::value_ = nullptr;
在单线程环境中,上述代码可以正常工作。然而,在多线程环境中,这种实现方式是线程不安全的。下面是对多线程环境下可能发生的问题的分段解释:
-
假设线程A和线程B都要访问 getInstance 函数。线程A进入函数并检查 if 条件,由于是第一次进入,value 为空,if 条件成立,准备创建对象实例。
-
然而,线程A可能在创建对象实例之前被操作系统的调度器中断,并被挂起(睡眠),将控制权交给线程B。
-
线程B同样进入 if 条件,发现 value 仍然为 NULL,因为线程A还没有来得及构造它就被中断了。线程B完成对象的创建并成功返回。
-
之后,线程A被唤醒并继续执行 new 来再次创建对象。这样一来,两个线程都构建了自己的对象实例,破坏了单例模式的唯一性。
除了线程安全问题,该实现还存在内存泄漏的问题。通过 new 创建的对象始终没有被释放。
改进
template<typename T>
class Singleton {
public:
static T& getInstance() {
if (!value_) {
static CGarbo garbo; // 定义一个静态局部变量,利用静态局部变量的特性确保线程安全
value_ = new T();
}
return *value_;
}
private:
class CGarbo {
public:
~CGarbo() {
if (Singleton::value_) {
delete Singleton::value_;
}
}
};
Singleton();
~Singleton();
static T* value_;
};
template<typename T>
T* Singleton<T>::value_ = nullptr;
在程序运行结束时,系统会调用Singleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。使用这种方法释放单例对象有以下特征:
-
在单例类内部定义专有的嵌套类
-
在单例类内定义私有的专门用于释放的静态成员
-
利用程序在结束时析构全局变量的特性,选择最终的释放时机
懒汉模式实现二:局部静态变量 (略)
如果存在多个单例对象且这几个单例对象相互依赖,可能会出现程序崩溃的危险。原因:对编译器来说,静态成员变量的初始化顺序和析构顺序是一个未定义的行为
2.饿汉模式
单例类定义的时候就进行实例化。因为main函数执行之前,全局作用域的类成员静态变量m_Instance已经初始化,故没有多线程的问题。
饿汉模式实现一:直接定义静态对象
// .h文件
class Singleton {
public:
static Singleton& GetInstance();
private:
Singleton() {}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
private:
static Singleton m_Instance;
};
// CPP文件
Singleton Singleton::m_Instance; // 类外定义-不要忘记写
Singleton& Singleton::GetInstance() {
return m_Instance;
}
// 函数调用
Singleton& instance = Singleton::GetInstance();
缺点
在程序开始时,就创建类的实例,如果Singleton对象产生很昂贵,而本身有很少使用,这种方式单从资源利用效率的角度来讲,比懒汉式单例类稍差些。但从反应时间角度来讲,则比懒汉式单例类稍好些。
请说说工厂设计模式,如何实现,以及它的优点
1.工厂设计模式的定义
定义一个创建对象的接口,让子类决定实例化哪个类,而对象的创建统一交由工厂去生产,有良好的封装性,既做到了解耦
2.工厂设计模式分类
工厂模式属于创建型模式,大致可以分为三类,简单工厂模式、工厂方法模式、抽象工厂模式
1.简单工厂模式
它的主要特点是需要在工厂类中做判断,从而创造相应的产品。当增加新的产品时,就需要修改工厂类。
举例:有一家生产处理器核的厂家,它只有一个工厂,能够生产两种型号的处理器核。客户需要什么样的处理器核,一定要显示地告诉生产工厂。
点击查看代码
//程序实例(简单工厂模式)
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
{
public:
SingleCore* CreateSingleCore(enum CTYPE ctype)
{
if(ctype == COREA) //工厂内部判断
return new SingleCoreA(); //生产核A
else if(ctype == COREB)
return new SingleCoreB(); //生产核B
else
return NULL;
}
};
优点: 简单工厂模式可以根据需求,动态生成使用者所需类的对象,而使用者不用去知道怎么创建对象,使得各个模块各司其职,降低了系统的耦合性。
缺点:就是要增加新的核类型时,就需要修改工厂类。这就违反了开放封闭原则:软件实体(类、模块、函数)可以扩展,但是不可修改。
2.工厂方法模式
所谓工厂方法模式,是指定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
这家生产处理器核的产家赚了不少钱,于是决定再开设一个工厂专门用来生产B型号的单核,而原来的工厂专门用来生产A型号的单核。这时,客户要做的是找好工厂,比如要A型号的核,就找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 Factory
{
public:
virtual SingleCore* CreateSingleCore() = 0;
};
//生产A核的工厂
class FactoryA: public Factory
{
public:
SingleCoreA* CreateSingleCore() { return new SingleCoreA; }
};
//生产B核的工厂
class FactoryB: public Factory
{
public:
SingleCoreB* CreateSingleCore() { return new SingleCoreB; }
};
优点: 扩展性好,符合了开闭原则,新增一种产品时,只需增加改对应的产品类和对应的工厂子类即可。
缺点:每增加一种产品,就需要增加一个对象的工厂。如果这家公司发展迅速,推出了很多新的处理器核,那么就要开设相应的新工厂。在C++实现中,就是要定义一个个的工厂类。显然,相比简单工厂模式,工厂方法模式需要更多的类定义。
3.抽象工厂模式
抽象工厂模式
//单核
class SingleCore
{
public:
virtual void Show() = 0;
};
class SingleCoreA: public SingleCore
{
public:
void Show() { cout<<"Single Core A"<<endl; }
};
class SingleCoreB :public SingleCore
{
public:
void Show() { cout<<"Single Core B"<<endl; }
};
//多核
class MultiCore
{
public:
virtual void Show() = 0;
};
class MultiCoreA : public MultiCore
{
public:
void Show() { cout<<"Multi Core A"<<endl; }
};
class MultiCoreB : public MultiCore
{
public:
void Show() { cout<<"Multi Core B"<<endl; }
};
//工厂
class CoreFactory
{
public:
virtual SingleCore* CreateSingleCore() = 0;
virtual MultiCore* CreateMultiCore() = 0;
};
//工厂A,专门用来生产A型号的处理器
class FactoryA :public CoreFactory
{
public:
SingleCore* CreateSingleCore() { return new SingleCoreA(); }
MultiCore* CreateMultiCore() { return new MultiCoreA(); }
};
//工厂B,专门用来生产B型号的处理器
class FactoryB : public CoreFactory
{
public:
SingleCore* CreateSingleCore() { return new SingleCoreB(); }
MultiCore* CreateMultiCore() { return new MultiCoreB(); }
};
优点: 工厂抽象类创建了多个类型的产品,当有需求时,可以创建相关产品子类和子工厂类来获取。
缺点: 扩展新种类产品时困难。抽象工厂模式需要我们在工厂抽象类中提前确定了可能需要的产品种类,以满足不同型号的多种产品的需求。但是如果我们需要的产品种类并没有在工厂抽象类中提前确定,那我们就需要去修改工厂抽象类了,而一旦修改了工厂抽象类,那么所有的工厂子类也需要修改,这样显然扩展不方便。
请说说装饰器计模式,以及它的优缺点
装饰器计模式的定义
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式
优点
-
装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用;
-
通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果;
-
装饰器模式完全遵守开闭原则。
缺点
- 装饰模式会增加许多子类,过度使用会增加程序得复杂性。
装饰模式的结构与实现
通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰模式的目标。下面来分析其基本结构和实现方法。
装饰模式主要包含以下角色:
-
抽象构件角色:定义一个抽象接口以规范准备接收附加责任的对象。
-
具体构件角色:实现抽象构件,通过装饰角色为其添加一些职责。
-
抽象装饰角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
-
具体装饰角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
UML图
简单工厂模式UML

工厂方法的UML图

抽象工厂模式的UML图

装饰模式


浙公网安备 33010602011771号