工具:动态类工厂和用配置文件存储属性

引言

本篇文章主要解决的问题是我们在开发一些软件的时候,当业务发生变化的时候,我们尽量要少改动代码,所以我们引入了配置文件,我们只需要读取对应的配置文件里面的信息,就可以改变一些变量的性质。当我们读取了配置文件之后,我们必然要找到对应的类函数,但是那么多的类函数我们应该如何找到对应的呢,因为我们是从配置里面读的数据,所以在代码里面我们也不知道该写什么具体的类,所以我们这里就引入了动态类工厂。

动态类工厂

首先我们要介绍一下这个架构,typedef void* (*createFun)(void)这个是这个动态类工厂的核心,类工厂里面有 prt_createFunMap,存储的是类名(字符串类型)和对应的构建这个类的函数。ptr_dyinstances是用来存储创建的函数,用栈是因为可以方便销毁。然后这里我们使用的是单例模式,也就是这个类工厂全局只有一个,因为只有这一个工厂才可以动态存储,如果有多个工厂,那么我们无法快速找到我们应该要的类函数。

我们为了快速调用,我们提供了三个宏:

DECLARE_CLASS是用在.h文件里面的,为的就是创建prt_dcInstance指针,还有一个createFun()函数就是为了提供一个接口创建一个我们传进来的类

IMPLEMENT_CLASS是在.cpp文件里面的,目的就是根据我们的把类转化成字符串和对应的创建函数存储在map里面。

GET_INSTANCE_BY_NAME是通过我们传入的类名字得到对应的类指针,当然我们需要对应的基类指针接受(一般都是这样,因为这样才有动态的意义)。

DynamicClass类主要就是动态类,顾名思义就是表示存储里面的类,而这个构造函数里面就是注册类

cpp 复制代码
#pragma once
#ifndef _CLASSHELPER_H
#define _CLASSHELPER_H

#include <map>
#include <string>
#include <stack>

typedef void* (*createFun)(void);

class ClassFactory {
private:
	ClassFactory();
	~ClassFactory();
	std::map<std::string, createFun> prt_createFunMap; // 存储类名和创建函数的映射
	std::stack<void*> ptr_dyinstances; // 用于存储创建的对象,方便销毁
	ClassFactory(const ClassFactory&) = delete;
	ClassFactory& operator=(const ClassFactory&) = delete;
	
public:
	// 获取单例
	void* getClassInstanceByName(std::string name);
	
	// 把类名和创建函数注册到工厂中
	void registerClass(std::string name, createFun method, void* dyinstance);
	static ClassFactory& getInstance();
};

// 动态类,用于向类工厂注册支持通过类名创建的类
class DynamicClass {
public:
	DynamicClass(std::string name, createFun method);
};

// 声明动态类
#define DECLARE_CLASS(className) \
private: \
static DynamicClass* prt_dcInstance; \
public: \
static void* createFun() {return new className();}

#define IMPLEMENT_CLASS(className) \
DynamicClass* className::prt_dcInstance = new DynamicClass(#className, className::createFun);

/*
typeName:告诉 C++ 返回什么类型
varName:给新对象起变量名字
className:告诉工厂创建哪个类
*/
#define GET_INSTANCE_BY_NAME(typeName, varName, className) \
typeName varName = (typeName)ClassFactory::getInstance().getClassInstanceByName(className)


#endif

这个就是实现的cpp

cpp 复制代码
#include "ClassHelper.h"

ClassFactory::ClassFactory()
{

}

void* ClassFactory::getClassInstanceByName(std::string name)
{
	auto iter = prt_createFunMap.find(name);
	if (iter != prt_createFunMap.end()) {
		return iter->second();
	}
	return nullptr;
}

void ClassFactory::registerClass(std::string name, createFun method, void* dyinstance)
{
	prt_createFunMap.insert(make_pair(name, method));
	ptr_dyinstances.push(dyinstance);
}


ClassFactory& ClassFactory::getInstance()
{
	static ClassFactory instance;
	return instance;
}

ClassFactory::~ClassFactory()
{
	while (!ptr_dyinstances.empty()) {
		delete ptr_dyinstances.top();
		ptr_dyinstances.pop();
	}
}

DynamicClass::DynamicClass(std::string name, createFun method)
{
	ClassFactory::getInstance().registerClass(name, method, this);
}

读取配置文件

这是读取配置文件的一个架构,这个文件里面的数据是key=value的形式,有着基本的功能,一个是加载文件里面的内容到数组里面,一个是获取对应key的value,一个是通过key,value,对进行设置,一个是通过key移除,一个是把所有的数组内容保存到文件里面。

我们存储信息用的是map,存储所有的key值用的是set,方便对map里面的value进行查找。

最后我们提供了一个宏CREATE_PROPERTIES,相当于是提供一个varName的钥匙来对文件进行操作,如果之后想得到value,就只需要varName.getProperties("key");

cpp 复制代码
#pragma once
#ifndef _PROPERTIES_H
#define _PROPERTIES_H

#include<iostream>
#include<fstream>
#include<map>
#include<string>
#include<algorithm>
#include<sstream>
#include<vector>
#include<set>

class Properties final {
private:
	std::map<std::string, std::string>* props = nullptr;
	std::set<std::string>* keys = nullptr;
	void trim(std::string& s); // 去除字符串两端的空格
	std::vector<std::string> split(const std::string& str, char pattern);
	void loadKeys();

public:
	Properties();
	~Properties();
	std::map<std::string, std::string>* getPorps() const;
	std::set<std::string>* getKeys() const;

	// 加载指定位置配置
	bool load(const std::string& path);
	// 通过key值获取属性
	std::string getProperty(std::string key);
	// 设置属性
	void setPoperty(const std::string& key, const std::string& value);
	// 移除属性
	void removeProperty(std::string& key);
	// 保存配置到指定的位置
	bool save(const std::string& path);
};

// 创建Properties对象
#define CREATE_PROPERTIES(varName, fileName) \
Properties varName; \
varName.load("config/"#fileName".properties")


#endif

移除空格

cpp 复制代码
void Properties::trim(std::string& s)
{
	if (!s.empty()) {
		s.erase(0, s.find_first_not_of(" "));
		s.erase(s.find_last_not_of(" ") + 1);
	}
}

通过传入的pattern来分割字符串,getline(input, temp, pattern)规定了读取的规则,就是从输入流input中读取字符串到temp中,然后遇到pattern停止。然后把对应的字符串放到res中,那么根据key = value的形式,res0就是key,res1就是value

cpp 复制代码
std::vector<std::string> Properties::split(const std::string& str, char pattern)
{
	std::vector <std::string> res;
	std::stringstream input(str);
	std::string temp;
	while (getline(input, temp, pattern)) {
		res.push_back(temp);
	}
	return res;
}
cpp 复制代码
void Properties::loadKeys()
{
	for (auto i : *props) {
		keys->insert(i.first);
	}
}

Properties::Properties()
{
	this->props = new std::map<std::string, std::string>();
	this->keys = new std::set<std::string>();
}

Properties::~Properties()
{
	delete props;
	delete keys;
}
std::map<std::string, std::string>* Properties::getPorps() const
{
	return props;
}

std::set<std::string>* Properties::getKeys() const
{
	return keys;
}

首先用ifstream读取文件,然后用line接受input的输入流,我们拿到line的时候先进行除空格和分割,分割成了两个之后按照pair的模板进行插入到props里面,不过为了考虑实际情况,我们还需要去除注释。

cpp 复制代码
bool Properties::load(const std::string& path)
{
	std::vector<std::string> paths = split(path, '.');
	if (paths.size() == 0 || paths[paths.size() - 1] != "properties") {
		throw std::runtime_error(path + " is not a properties file!");
	}
	std::ifstream input(path);
	if (!input) {
		throw std::runtime_error("can't load " + path);
	}
	std::string line;
	while (getline(input, line)) {
		trim(line);
		if (line.empty() || line == "\r" || line[0] == '#') {
			continue;
		}
		std::vector<std::string> res = split(line, '=');
		if (res.size() < 2) {
			std::cerr << "Properties format error!" << std::endl;
			throw std::runtime_error("Properties format error!");
		}
		// 删除注释
		size_t pos = res[1].find("#");
		if (pos != std::string::npos) {
			res[1].erase(pos);
		}
		for_each(res.begin(), res.end(),
			[=](std::string& s)-> void {
				trim(s);
			});
		props->insert(make_pair(res[0], res[1]));
	}
	input.close();
	loadKeys();
	return true;
}
cpp 复制代码
std::string Properties::getProperty(std::string key)
{
	if (props->find(key) == props->end()) {
		std::cerr << "can't find value with " << key << std::endl;
		return "";
	}
	std::string value = this->props->at(key);
	return value;
}

void Properties::setPoperty(const std::string& key, const std::string& value)
{
	this->props->insert(make_pair(key, value));
}

void Properties::removeProperty(std::string& key)
{
	auto iter = props->find(key);
	if (iter != props->end())
	{
		props->erase(iter);
	}
}

bool Properties::save(const std::string& path)
{
	std::ofstream output(path);
	if (!output) {
		throw std::runtime_error("can't construct the output file!");
	}
	for (auto item : *props) {
		output << item.first << "=" << item.second << std::endl;
	}
	output.close();
	return true;
}

总结

本篇文章到这里就结束了,这个是一个优化设计模式代码的工具,希望可以帮助大家理解~~~

相关推荐
脱胎换骨-军哥1 小时前
C++ 代码规范与格式化指南
开发语言·c++·代码规范
迷途之人不知返3 小时前
lambda表达式
c++
@三十一Y3 小时前
C++:红黑树的实现
开发语言·c++
_wyt0014 小时前
拓扑排序:有向无环图的排队艺术
c++·拓扑排序·队列
皓月斯语4 小时前
B2118 验证子串
c++·题解
乐观勇敢坚强的老彭4 小时前
C++信奥:开关门、开关灯问题
开发语言·c++·算法
2301_777998345 小时前
C/C++:预处理详解
c语言·c++
t-think5 小时前
C++类和对象详解(一)
开发语言·c++
阿米亚波7 小时前
【C++ STL】std::unordered_multiset
开发语言·数据结构·c++·笔记·stl