题目

  1. 单例模式。答案
  2. 装饰器模式。答案
  3. 观察者模式的多线程安全问题,写个医生,病人的观察者模式啊。答案
  4. 说一下OOP的设计模式五项原则。答案
  5. 生产者消费者的同步操作。
  6. 工厂模式?优点?答案

参考答案

    • 单例模式:一个类只能有一个对象被创建,如果有多个对象的话,可能会导致状态的混乱和不一致。通过单例模式,可以做到:
      • 确保一个类只有一个实例被建立;
      • 提供了一个对对象的全局访问指针;
      • 在不影响单例类的客户端的情况下允许将来有多个实例。
    • 三种单例模式的实现:懒汉式、饿汉式和多线程式;
      • 懒汉式:特点是延迟加载,比如配置文件的实例直到用到的时候才会被加载;
      • 饿汉式:特点是一开始就加载了,用“空间换时间”;
      • 多线程式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/********* 第一种:懒汉式 **********/
// GetInstance使用懒惰初始化,即它的返回值是当这个函数首次被访问时才创建的。
// 这是一种防弹设计,所有GetInstance()之后的调用都返回相同实例的指针。
// 缺陷:假设单例还未初始化,若有两个线程同时调用GetInstance方法,然后两个线程都初始化一个单例,最后得到的指针并不是指向同一个地方,不满足单例类的定义了。
class Singleton {
private:
Singleton() {};
static Singleton* m_pInstance; // 静态成员对象指针变量只会分配指针空间,而不是对象空间,所以不会调用构造函数

public:
static Singleton* GetInstance() {
if (m_pInstance == NULL)
m_pInstance = new Singleton();
return m_pInstance;
}
};

/********* 第二种:饿汉式 **********/
// 有两个状态:单例未初始化和单例已经初始化
class Singleton {
private:
Singleton() {};

public:
static Singleton* GetInstance() {
static Singleton instance; // 静态成员对象在运行之初就已经分配了空间,调用构造函数了
return &instance;
}
};

/********* 第三种:多线程式 **********/
class Singleton {
private:
static Singleton* m_instance;
Singleton() {};

public:
static Singleton* GetInstance() {
if (NULL == m_instance) {
Lock(); // 借助其他类来实现,如boost
if (NULL == m_instance) {
m_instance = new Singleton;
}
UnLock();
}
return m_instance;
}
}

返回原题

    • 装饰器模式:允许向一个现有的对象添加新的功能,同时又不改变其结构。属于结构型模式,是作为现有的类的一个包装。比生成子类更加灵活
    • 优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,可以动态扩展一个实现类的功能。
    • 缺点:多层装饰比较复杂。
      返回原题
    • 观察者模式:当对象间存在一对多关系时,则使用观察者模式。当一个对象的状态发生改变时,所有依赖它的对象都得到通知并被自动更新。观察者模式属于行为型模式。
    • 优点:
      • 观察者和被观察者是抽象耦合的;
      • 建立一套触发机制。
    • 缺点:
      • 如果一个被观察者有很多直接或间接的观察者,将所有的观察者都通知到位会花费很多时间;
      • 如果观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃;
      • 没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅是知道观察目标发生了变化。
Observer.h
  • cpp
1
2
3
4
5
6
7
class Observer {
public:
Observer() {};
virtual ~Observer() {};
// 定义纯虚函数,规范接口
virtual void update() = 0;
};
Subject.h
  • cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <vector>
#include "Observer.h"

using namespace std;

class Subject {
public:
Subject() {};
virtual ~Subject() {};
void addObserver(Observer *observer);
void delObserver(Observer *observer);
void notifyObservers();
virtual int getStatus() = 0;
virtual void setStatus(int status) = 0;
private:
vector<Observer*> m_observers;
};
Subject.cpp
  • cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#inlcude Subject.h

void Subject::addObserver(Observer *observer) {
m_observers.push_back(observer);
}
void Subject::delObserver(Observer *observer) {
for (auto iter = m_observers.begin(); iter != m_observers.end(); ++iter) {
if (*iter == observer) {
m_observer.erase(iter);
return;
}
}
}
void Subject::notifyObservers() {
for (auto iter = m_observers.begin(); iter != m_observers.end(); ++iter) {
(*iter)->update();
}
}
ConcreteObserver.h
  • cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <string>
#include "Observer.h"
#include "Subject.h"

using namespace std;

class ConcreteObserver: public Observer {
private:
string m_observerName;
Subject *m_subject;

public:
ConcreteObserver(string name, Subject* subject): m_observerName(name), m_subject(subject) {};
~ConcreteObserver() {};
void update();
};
ConcreteObserver.cpp
  • cpp
1
2
3
4
5
6
7
8
#include <iostream>
#include "ConcreteObserver.h"

using namespace std;

void ConcreteObserver::update() {
cout << "update observer[" << m_observerName << "] status: " << m_subject->getStatus() << endl;
}
ConcreteSubject.h
  • cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <string>
#include "Observer.h"
#include "Subject.h"

using namespace std;

class ConcreteSubject: public Subject {
private:
string m_subjectName;
int m_status;

public:
ConcreteSubject(string name): m_subjectName(name), m_status(0) {};
~ConcreteSubjec() {};

void setStatus(int status);
int getStatus();
};
ConcreteSubject.cpp
  • cpp
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include "ConcreteSubject.h"

using namespace std;

void ConcreteSubject::setStatus(int status) {
m_status = status;
cout << "setStatus subject[" << m_subjectName << "] status: " << status << endl;
}
int ConcreteSubject::getStatus() {
return m_status;
}
main.cpp
  • cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include "Subject.h"
#include "Observer.h"
#include "ConcreteObserver.h"
#include "ConcreteSubject.h"

int main(int argc, char *argv[]) {
Subject* subjectA = new ConcreteSubject("subjectA");
Subject* subjectB = new ConcreteSubject("subjectB");

Observer* observerA = new ConcreteObserver("observerA", subjectA);
Observer* observerB = new ConcreteObserver("observerB", subjectB);

subjectA->addObserver(observerA);
subjectB->addObserver(observerB);

subjectA->setStatus(1);
subjectA->notifyObservers();

subjectB->setStatus(2);
subjectB->notifyObservers();

subjectA->addObserver(observerB);
subjectA->setStatus(2);
subjectA->notifyObservers();

delete subjectA;
delete subjectB;
delete observerA;
delete observerB;

return 0;
}

返回原题

    • 单一职责原则:类的职责单一,对外只提供一种功能,而引起类变化的原因都应该只有一个;
    • 开闭原则:类的改动是通过增加代码进行的,而不是修改源代码;
    • 里氏替换原则:任何抽象类出现的地方都可以用它的实现类进行替换,实际就是虚拟机制,语言级别实现面向对象功能;
    • 依赖倒转原则:依赖于抽象(接口),不要依赖具体实现(类),也就是针对接口编程;
    • 接口隔离原则:不应该强迫用户的程序依赖他们不需要的接口方法,一个接口应该只提供一种对外功能,不应该把所有操作都封装到一个接口中去;
    • 合成复用原则:如果使用继承,会导致父类的任何变换都可能影响到子类的行为。如果使用对象组合,就降低了这种依赖关系。对于继承和组合,优先使用组合;
    • 迪米特原则:一个对象应当对其他对象尽可能少的了解,从而降低各个对象之间的耦合,提高系统的可维护性。
      返回原题
    • 工厂模式:属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
    • 优点:
      • 一个调用者想创建一个对象,只需知道其名称就可以了;
      • 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以;
      • 屏蔽产品的具体实现,调用者只关心产品的接口。
    • 缺点:
      • 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使用系统中类的个数成倍增加,在一定程序上增加了系统的复杂度,同时也增加了系统具体类的依赖。
        返回原题