接口?什么是针对接口编程?
- 接口从语法层面上来说,是一种特殊的抽象类,是一个纯虚的类。从软件设计的意义上来说,我们通常用接口来定义实现类的外观,就相当于一份契约,根据外部应用需要的功能,约定了实现类应该要实现的功能。
- 软件开发中永恒的主题是“变化”,“只有变化才是永恒不变!“,接口最重要的一个设计语意就是封装变化。所谓”封装变化“就是隔离变化。
- 从软件的整体结构上看,只要接口不变,内部实现的变化就不会影响到外部应用,从而使得系统更灵活,具有更好的扩展性和可维护性。
场景
我们设计了一组API,一个程序需要使用这些API完成功能,如何规划API的设计?
首先简单画出UML图:
对应的代码:
#include <iostream>
#include <string>
using namespace std;
class Api {
public:
virtual void test(string s) = 0;
protected:
Api() {}//屏蔽构造函数,体现接口
};
class Impl :public Api {
public:
void test(string s) {
cout << "现在是Impl类,正在执行功能方法" << s << endl;
}
};
//测试用客户端
int main() {
Api* pApi = new Impl();
pApi->test("test!");
return 0;
}
运行顺利,但有什么问题呢?
答:封装性被破坏!
客户在使用过程中知道了有Impl,而且还知道Impl有test这个功能!也就是说客户知道了内部的使用细节。
那么什么是简单工厂?它用来做什么?
简单工厂的做法是不想让客户知道内部的细节,我们只想让他知道运作方式就可以了。
也就是说,客户只需要知道一个Factory就可以了。
#include <iostream>
#include <string>
using namespace std;
class Api {
public:
virtual void test(string s) = 0;
protected:
Api() {}//屏蔽构造函数,体现接口
};
class ImplOne :public Api {
public:
void test(string s) {
cout << "现在是One在执行" << endl << s;
}
};
class ImplTwo :public Api {
public:
void test(string s) {
cout << "现在是Two在执行" << endl << s;
}
};
class Factory {
public:
static Api* createApi(int type) {
Api* pApi = nullptr;
if (type == 1) {
pApi = new ImplOne();
}
if (type == 2) {
pApi = new ImplTwo();
}
return pApi;
}
};
/*
传入参数1,可以实现从数据库读入的功能
传入参数2,可以实现从文本文件读入的功能
*/
int main() {
Api* pApi = Factory::createApi(1);
pApi->test("现在是使用简单工厂方法重构");
return 0;
}
现在客户可以根据我们接口的规约,来使用这部分接口。
实现了客户端调用,和ImplOne和ImplTwo的解耦合。
Factory类实现了变化隔离,这是简单工厂的核心所在。
但是还是有问题!客户端还是知道工厂的细节!
这是简单工厂的一个重要扩展:我们能否动态地、任意地创建我们的工厂?从而实现客户与接口1、接口2的完全解耦?
可以!但需要辅助工具,利用宏定义!
新定义一个DynOBj.h:
#ifndef IBJDYN_H_
#define IBJDYN_H_
#include <string>
#include <map>
typedef void* (*Constructor)();
class CObjectFactory {
public:
static void registerClass(std::string className, Constructor constructor) {
constructors()[className] = constructor;
}
static void* createObject(const std::string& className) {
Constructor constructor = nullptr;
if (constructors().find(className) != constructors().end()) {
constructor = constructors().find(className)->second;
}
if (constructor == nullptr) {
return nullptr;
}
return (*constructor)();
}
private:
//string->key,动态创建的类名,value是构建
inline static std::map<std::string, Constructor>& constructors() {
static std::map<std::string, Constructor>instance;
return instance;
}
};
#define REG_CLASS(class_name)\
class class_name##Helper {\
public:\
class_name##Helper() {\
CObjectFactory::registerClass(#class_name, class_name##Helper::createObjFunc);\
}\
static void* createObjFunc() {\
return new ImplTwo;\
}\
};\
class_name##Helper class_name##helper;
#endif // !IBJDYN_H_
SimpleFactoryRefactor.cpp
#include <iostream>
#include <string>
#include "DynObj.h"
using namespace std;
class Api {
public:
virtual void test(string s) = 0;
protected:
Api() {}//屏蔽构造函数,体现接口
};
class ImplOne :public Api {
public:
void test(string s) {
cout << "现在是One在执行" << endl << s;
}
};
class ImplTwo :public Api {
public:
void test(string s) {
cout << "现在是Two在执行" << endl << s;
}
};
class Factory {
public:
static Api* createApi(int type) {
Api* pApi = nullptr;
if (type == 1) {
pApi = new ImplOne();
}
if (type == 2) {
pApi = new ImplTwo();
}
return pApi;
}
};
class AutoFactory {
public:
static Api* createApi() {
Api* pApi = nullptr;
pApi = static_cast<Api*>(CObjectFactory::createObject("ImplTwo"));
return pApi;
}
};
REG_CLASS(ImplTwo)
int main() {
Api* pApi = AutoFactory::createApi();
pApi->test("完全不知道里面的东西!");
return 0;
}
在cpp中添加了AutoFactory类,其中很重要的一点是:
我们将要实现的接口通过string的方式传入。 这个字符串的传入方式有很多,如果我们通过配置文件传入,那就意味着我们以后如果更改接口只需要更改传入的配置文件参数就好了,同时也意味着客户端完完全全不知道内部的细节!
这就是简单工厂传达的一种编程思想:面向接口编程。
最后我们再来看简单工厂的定义:
提供一个创建对象实例的功能,而无需关心其具体实现。被创建的类型可以是接口、抽象类,也可以是具体的类。