目录
在 其他工具类三 中我们介绍了线程安全相关的一些类和接口,今天我们正式进入最后一个部分的分析: 设计理念篇。
在 总体设计 中我们从软件开发的流程的角度分析了 easylogging++的相关设计,今天我们结合前面的源码分析从编程范式的角度来观察其设计理念。
面向过程
面向过程最明显的就是模块化,自定向下的设计思路。通过将一个复杂的问题逐步拆解为一个个更简单的子问题,然后逐步解决子问题,最终将这个复杂的问题给解决了。
计算机世界的任何问题都可以通过引入中间层来解决,一个中间层如果解决不了,就再加一个中间层。
在 总体设计 中 easylogging++的框架设计部分已经有了部分介绍。
再比如TypedConfigurations
类中FORMAT
配置项的部分的解析工作相对复杂,又被拆分为LogFormat
类来单独进行处理。
另外,这其中设计到很多特定的操作,为了相关操作的便利性,内部实现了一系列相关的工具类:
1. 文件操作类File
2. 字符串工具类Str
3. 操作系统相关工具类OS
4. 日期时间工具类DateTime
5. 解析命令行参数的工具类CommandLineArgs
6. 线程安全的一系列类: 通用互斥量类Mutex
、通用区间锁类ScopedLock
和通用线程安全基类ThreadSafe
7. 操作el::Level
的工具类LevelHelper
8. 操作el::ConfigurationType
的工具类ConfigurationTypeHelper
这样每个类的功能更单一,避免设计大而全的类,也更加符合单一职责
原则。
面向对象
面向对象的特性(封装、抽象、继承、多态)天然让代码具有复用的特点, 让我们能够更轻松写出易复用、易扩展、易维护的代码。
面向对象编程因为其具有丰富的特性,可以实现很多复杂的设计思路,是很多设计原则、设计模式等编码实现的基础。Writer 类
writer
类主要用于写日志,但writer
类两种使用场景(日志记录宏以及类printf
接口)中,都必须基于以下调用形式来使用:el::base::Writer(xxx).construct(xxx) << "log";
Writer
类的实例只有在调用完construct
接口后才能真正完成初始化的工作。
从Writer
类的应用场景来说,这是建造者模式的一种体现。PErrorWriter 类
PErrorWriter
类在日志内容里面添加errno
错误信息,是对Writer
类的增强。
从PErrorWriter
类的应用场景来说,这是装饰器模式的一种体现。Helpers 和 Loggers 类
Helpers
和Loggers
类对库使用的使用者而言提供了一系列更易用的接口。
从PErrorWriter
类的应用场景来说,这是门面模式的一种体现。
泛型编程
模板将类型泛型化,在基本面向对象的基础上更将代码复用带到了一个全新的高度,通过类型参数化,同一份代码可以支持不同的类型。
如果在加上继承的特性,代码复用将会更加灵活。IterableContainer 抽象类模板, IterablePriorityQueue 子类模板, IterableQueue 子类模板和 IterableStack 子类模板
/// @brief Workarounds to write some STL logs /// /// @detail There is workaround needed to loop through some stl containers. In order to do that, we need iterable containers /// of same type and provide iterator interface and pass it on to writeIterator(). /// Remember, this is passed by value in constructor so that we dont change original containers. /// This operation is as expensive as Big-O(std::min(class_.size(), base::consts::kMaxLogPerContainer)) namespace workarounds { /// @brief Abstract IterableContainer template that provides interface for iterable classes of type T // 迭代特定类型的抽象类,提供begin()和end()接口用于迭代,用于el::base::MessageBuilder的writeIterator接口 template <typename T, typename Container> class IterableContainer { public: typedef typename Container::iterator iterator; typedef typename Container::const_iterator const_iterator; IterableContainer(void) {} virtual ~IterableContainer(void) {} iterator begin(void) { return getContainer().begin(); } iterator end(void) { return getContainer().end(); } private: // 派生类需要实现getContainer接口,提供底层真正的容器类型 virtual Container &getContainer(void) = 0; }; /// @brief Implements IterableContainer and provides iterable std::priority_queue class // Container为std::vector<T> template <typename T, typename Container = std::vector<T>, typename Comparator = std::less<typename Container::value_type>> class IterablePriorityQueue : public IterableContainer<T, Container>, public std::priority_queue<T, Container, Comparator> { public: IterablePriorityQueue(std::priority_queue<T, Container, Comparator> queue_) { std::size_t count_ = 0; while (++count_ < base::consts::kMaxLogPerContainer && !queue_.empty()) { this->push(queue_.top()); queue_.pop(); } } private: inline Container &getContainer(void) { return this->c; } }; /// @brief Implements IterableContainer and provides iterable std::queue class // Container为std::deque<T>,而std::deque<T>默认的底层容器为std::vector<T> template <typename T, typename Container = std::deque<T>> class IterableQueue : public IterableContainer<T, Container>, public std::queue<T, Container> { public: IterableQueue(std::queue<T, Container> queue_) { std::size_t count_ = 0; while (++count_ < base::consts::kMaxLogPerContainer && !queue_.empty()) { this->push(queue_.front()); queue_.pop(); } } private: inline Container &getContainer(void) { return this->c; } }; /// @brief Implements IterableContainer and provides iterable std::stack class // Container为std::deque<T>,而std::deque<T>默认的底层容器为std::vector<T> template <typename T, typename Container = std::deque<T>> class IterableStack : public IterableContainer<T, Container>, public std::stack<T, Container> { public: IterableStack(std::stack<T, Container> stack_) { std::size_t count_ = 0; while (++count_ < base::consts::kMaxLogPerContainer && !stack_.empty()) { this->push(stack_.top()); stack_.pop(); } } private: inline Container &getContainer(void) { return this->c; } }; } // namespace workarounds
相关模板子类主要用于
el::base::MessageBuilder
的writeIterator
接口,可以让 easylogging++对于STL
的容器相关类型得到相应支持。
IterableContainer
抽象类模板实现了begin
和end
接口,用于迭代。
通过将容器类型作为参数让 easylogging++适配具有begin
和end
接口的容器类型。
从这些类的应用场景来说,这是迭代器模式和适配器模式的一种体现。上面的代码本身不复杂,这里就不作过多解释了。
AbstractRegistry 抽象类模板,Registry 模板子类以及 RegistryWithPred 模板子类
/// @brief Abstract registry (aka repository) that provides basic interface for pointer repository specified by T_Ptr type. /// /// @detail Most of the functions are virtual final methods but anything implementing this abstract class should implement /// unregisterAll() and deepCopy(const AbstractRegistry<T_Ptr, Container>&) and write registerNew() method according to container /// and few more methods; get() to find element, unregister() to unregister single entry. /// Please note that this is thread-unsafe and should also implement thread-safety mechanisms in implementation. template <typename T_Ptr, typename Container> class AbstractRegistry : public base::threading::ThreadSafe { public: typedef typename Container::iterator iterator; typedef typename Container::const_iterator const_iterator; /// @brief Default constructor AbstractRegistry(void) {} /// @brief Move constructor that is useful for base classes AbstractRegistry(AbstractRegistry &&sr) { if (this == &sr) { return; } unregisterAll(); m_list = std::move(sr.m_list); } bool operator==(const AbstractRegistry<T_Ptr, Container> &other) { if (size() != other.size()) { return false; } for (std::size_t i = 0; i < m_list.size(); ++i) { if (m_list.at(i) != other.m_list.at(i)) { return false; } } return true; } bool operator!=(const AbstractRegistry<T_Ptr, Container> &other) { if (size() != other.size()) { return true; } for (std::size_t i = 0; i < m_list.size(); ++i) { if (m_list.at(i) != other.m_list.at(i)) { return true; } } return false; } /// @brief Assignment move operator AbstractRegistry &operator=(AbstractRegistry &&sr) { if (this == &sr) { return *this; } unregisterAll(); m_list = std::move(sr.m_list); return *this; } virtual ~AbstractRegistry(void) { } /// @return Iterator pointer from start of repository virtual inline iterator begin(void) ELPP_FINAL { return m_list.begin(); } /// @return Iterator pointer from end of repository virtual inline iterator end(void) ELPP_FINAL { return m_list.end(); } /// @return Constant iterator pointer from start of repository virtual inline const_iterator cbegin(void) const ELPP_FINAL { return m_list.cbegin(); } /// @return End of repository virtual inline const_iterator cend(void) const ELPP_FINAL { return m_list.cend(); } /// @return Whether or not repository is empty virtual inline bool empty(void) const ELPP_FINAL { return m_list.empty(); } /// @return Size of repository virtual inline std::size_t size(void) const ELPP_FINAL { return m_list.size(); } /// @brief Returns underlying container by reference virtual inline Container &list(void) ELPP_FINAL { return m_list; } /// @brief Returns underlying container by constant reference. virtual inline const Container &list(void) const ELPP_FINAL { return m_list; } /// @brief Unregisters all the pointers from current repository. virtual void unregisterAll(void) = 0; protected: virtual void deepCopy(const AbstractRegistry<T_Ptr, Container> &) = 0; void reinitDeepCopy(const AbstractRegistry<T_Ptr, Container> &sr) { unregisterAll(); deepCopy(sr); } private: Container m_list; }; /// @brief A pointer registry mechanism to manage memory and provide search functionalities. (non-predicate version) /// /// @detail NOTE: This is thread-unsafe implementation (although it contains lock function, it does not use these functions) /// of AbstractRegistry<T_Ptr, Container>. Any implementation of this class should be /// explicitly (by using lock functions) template <typename T_Ptr, typename T_Key = const char *> class Registry : public AbstractRegistry<T_Ptr, std::unordered_map<T_Key, T_Ptr *>> { public: typedef typename Registry<T_Ptr, T_Key>::iterator iterator; typedef typename Registry<T_Ptr, T_Key>::const_iterator const_iterator; Registry(void) {} /// @brief Copy constructor that is useful for base classes. Try to avoid this constructor, use move constructor. Registry(const Registry &sr) : AbstractRegistry<T_Ptr, std::vector<T_Ptr *>>() { if (this == &sr) { return; } this->reinitDeepCopy(sr); } /// @brief Assignment operator that unregisters all the existing registries and deeply copies each of repo element /// @see unregisterAll() /// @see deepCopy(const AbstractRegistry&) Registry &operator=(const Registry &sr) { if (this == &sr) { return *this; } this->reinitDeepCopy(sr); return *this; } virtual ~Registry(void) { unregisterAll(); } protected: virtual void unregisterAll(void) ELPP_FINAL { if (!this->empty()) { for (auto &&curr : this->list()) { base::utils::safeDelete(curr.second); } this->list().clear(); } } /// @brief Registers new registry to repository. virtual void registerNew(const T_Key &uniqKey, T_Ptr *ptr) ELPP_FINAL { unregister(uniqKey); this->list().insert(std::make_pair(uniqKey, ptr)); } /// @brief Unregisters single entry mapped to specified unique key void unregister(const T_Key &uniqKey) { T_Ptr *existing = get(uniqKey); if (existing != nullptr) { this->list().erase(uniqKey); base::utils::safeDelete(existing); } } /// @brief Gets pointer from repository. If none found, nullptr is returned. T_Ptr *get(const T_Key &uniqKey) { iterator it = this->list().find(uniqKey); return it == this->list().end() ? nullptr : it->second; } private: virtual void deepCopy(const AbstractRegistry<T_Ptr, std::unordered_map<T_Key, T_Ptr *>> &sr) ELPP_FINAL { for (const_iterator it = sr.cbegin(); it != sr.cend(); ++it) { registerNew(it->first, new T_Ptr(*it->second)); } } }; /// @brief A pointer registry mechanism to manage memory and provide search functionalities. (predicate version) /// /// @detail NOTE: This is thread-unsafe implementation of AbstractRegistry<T_Ptr, Container>. Any implementation of this class /// should be made thread-safe explicitly template <typename T_Ptr, typename Pred> class RegistryWithPred : public AbstractRegistry<T_Ptr, std::vector<T_Ptr *>> { public: typedef typename RegistryWithPred<T_Ptr, Pred>::iterator iterator; typedef typename RegistryWithPred<T_Ptr, Pred>::const_iterator const_iterator; RegistryWithPred(void) { } virtual ~RegistryWithPred(void) { unregisterAll(); } /// @brief Copy constructor that is useful for base classes. Try to avoid this constructor, use move constructor. RegistryWithPred(const RegistryWithPred &sr) : AbstractRegistry<T_Ptr, std::vector<T_Ptr *>>() { if (this == &sr) { return; } this->reinitDeepCopy(sr); } /// @brief Assignment operator that unregisters all the existing registries and deeply copies each of repo element /// @see unregisterAll() /// @see deepCopy(const AbstractRegistry&) RegistryWithPred &operator=(const RegistryWithPred &sr) { if (this == &sr) { return *this; } this->reinitDeepCopy(sr); return *this; } friend base::type::ostream_t &operator<<(base::type::ostream_t &os, const RegistryWithPred &sr) { for (const_iterator it = sr.list().begin(); it != sr.list().end(); ++it) { os << ELPP_LITERAL(" ") << **it << ELPP_LITERAL("\n"); } return os; } protected: virtual void unregisterAll(void) ELPP_FINAL { if (!this->empty()) { for (auto &&curr : this->list()) { base::utils::safeDelete(curr); } this->list().clear(); } } virtual void unregister(T_Ptr *&ptr) ELPP_FINAL { if (ptr) { iterator iter = this->begin(); for (; iter != this->end(); ++iter) { if (ptr == *iter) { break; } } if (iter != this->end() && *iter != nullptr) { this->list().erase(iter); base::utils::safeDelete(*iter); } } } virtual inline void registerNew(T_Ptr *ptr) ELPP_FINAL { this->list().push_back(ptr); } /// @brief Gets pointer from repository with specified arguments. Arguments are passed to predicate /// in order to validate pointer. template <typename T, typename T2> T_Ptr *get(const T &arg1, const T2 arg2) { iterator iter = std::find_if(this->list().begin(), this->list().end(), Pred(arg1, arg2)); if (iter != this->list().end() && *iter != nullptr) { return *iter; } return nullptr; } private: virtual void deepCopy(const AbstractRegistry<T_Ptr, std::vector<T_Ptr *>> &sr) { for (const_iterator it = sr.list().begin(); it != sr.list().end(); ++it) { registerNew(new T_Ptr(**it)); } } };
AbstractRegistry
抽象类模板基于底层容器实现了容器相关的一系列方法:
1. 用于迭代的begin
、end
,cbegin
、cend
。
2. 判断容器是否为空的empty
3. 获取容器大小的size
4. 获取底层容器的list
而将与容器类型相关的差异性的操作留给子类去实现:
子类需要实现unregisterAll
和deepCopy
方法。
由于容器类型不同,添加和获取元素的方法不一定一致,故这些接口没有成为纯虚方法,直接让子类自己去实现。子类实现了以下方法:
1.registerNew
:添加元素
2.unregister
:删除元素
3.get
:获取元素从这些类的应用场景来说,这是模板模式的一种体现。
基于模板参数类型
Container
(底层容器类型)定义了类的迭代器别名iterator
和const_iterator
:typedef typename Container::iterator iterator; typedef typename Container::const_iterator const_iterator;
Registry
(非谓词版本) 公有继承AbstractRegistry <T_Ptr, std::unordered_map<T_Key, T_Ptr*> >
。
底层容器为std::unordered_map <T_Key, T_Ptr*>
。
基于AbstractRegistry
的iterator
别名定义了自身的迭代器类型别名iterator
和const_iterator
:// 实际iterator就是std::unordered_map<T_Key, T_Ptr*>::iterator typedef typename Registry<T_Ptr, T_Key>::iterator iterator; // 实际const_iterator就是std::unordered_map<T_Key, T_Ptr*>::iconst_iterator typedef typename Registry<T_Ptr, T_Key>::const_iterator const_iterator;
这个类非线程安全,原因:没有使用继承来的
base::threading::ThreadSafe
当中的锁相关的接口。
如果想使这个类保证线程安全,必须在这个类的基础上使用组合或者继承方式,将相关需要加锁的接口包装成已加锁。
RegistryWithPred
(谓词版本) 公有继承AbstractRegistry <T_Ptr, std::vector<T_Ptr*> >
。
底层容器为std:: vector <T_Ptr*>
。
基于AbstractRegistry
的iterator
别名定义了自身的迭代器类型别名iterator
和const_iterator
:typedef typename RegistryWithPred<T_Ptr, Pred>::iterator iterator;(实际iterator就是std::vector<T_Ptr*>::iterator) typedef typename RegistryWithPred<T_Ptr, Pred>::const_iterator const_iterator;(实际const_iterator就是std::vector<T_Ptr*>::const_iterator)
这个类也非线程安全,原因:也是没有使用继承来的
base::threading::ThreadSafe
当中的锁相关的接口。
如果想使用这个类保证线程安全,必须在这个类的基础上使用组合或者继承方式,将相关需要加锁的接口包装成已加锁。
谓词主要用于get
接口。上面三个类的代码本身不复杂,这里就不作过多解释了。
函数式编程
一种越来越流行的编程范式,适当的使用能够使代码更简洁可读,写出更灵活、紧凑、优雅的代码。
lambda
表达式是一个变量,所以,我们就可以 "按需分配",随时随地在调用点 "就地" 定义函数,限制它的作用域和生命周期,实现函数的局部化。
如下面接口当中的conditionalAddFlag
这个lambda
表达式,我们仅仅只在当前这个函数内部使用:/// @brief Updates format to be used while logging. /// @param userFormat User provided format void LogFormat::parseFromFormat(const base::type::string_t &userFormat) { // We make copy because we will be changing the format // i.e, removing user provided date format from original format // and then storing it. base::type::string_t formatCopy = userFormat; m_flags = 0x0; auto conditionalAddFlag = [&](const base::type::char_t *specifier, base::FormatFlags flag) { std::size_t foundAt = base::type::string_t::npos; while ((foundAt = formatCopy.find(specifier, foundAt + 1)) != base::type::string_t::npos) { if (foundAt > 0 && formatCopy[foundAt - 1] == base::consts::kFormatSpecifierChar) { if (hasFlag(flag)) { // If we already have flag we remove the escape chars so that '%%' is turned to '%' // even after specifier resolution - this is because we only replaceFirst specifier formatCopy.erase(foundAt - 1, 1); ++foundAt; } } else { if (!hasFlag(flag)) addFlag(flag); } } }; conditionalAddFlag(base::consts::kAppNameFormatSpecifier, base::FormatFlags::AppName); conditionalAddFlag(base::consts::kSeverityLevelFormatSpecifier, base::FormatFlags::Level); conditionalAddFlag(base::consts::kSeverityLevelShortFormatSpecifier, base::FormatFlags::LevelShort); conditionalAddFlag(base::consts::kLoggerIdFormatSpecifier, base::FormatFlags::LoggerId); conditionalAddFlag(base::consts::kThreadIdFormatSpecifier, base::FormatFlags::ThreadId); conditionalAddFlag(base::consts::kLogFileFormatSpecifier, base::FormatFlags::File); conditionalAddFlag(base::consts::kLogFileBaseFormatSpecifier, base::FormatFlags::FileBase); conditionalAddFlag(base::consts::kLogLineFormatSpecifier, base::FormatFlags::Line); conditionalAddFlag(base::consts::kLogLocationFormatSpecifier, base::FormatFlags::Location); conditionalAddFlag(base::consts::kLogFunctionFormatSpecifier, base::FormatFlags::Function); conditionalAddFlag(base::consts::kCurrentUserFormatSpecifier, base::FormatFlags::User); conditionalAddFlag(base::consts::kCurrentHostFormatSpecifier, base::FormatFlags::Host); conditionalAddFlag(base::consts::kMessageFormatSpecifier, base::FormatFlags::LogMessage); conditionalAddFlag(base::consts::kVerboseLevelFormatSpecifier, base::FormatFlags::VerboseLevel); // For date/time we need to extract user's date format first std::size_t dateIndex = std::string::npos; if ((dateIndex = formatCopy.find(base::consts::kDateTimeFormatSpecifier)) != std::string::npos) { while (dateIndex != std::string::npos && dateIndex > 0 && formatCopy[dateIndex - 1] == base::consts::kFormatSpecifierChar) { dateIndex = formatCopy.find(base::consts::kDateTimeFormatSpecifier, dateIndex + 1); } if (dateIndex != std::string::npos) { addFlag(base::FormatFlags::DateTime); updateDateFormat(dateIndex, formatCopy); } } m_format = formatCopy; updateFormatSpec(); }
至此,easylogging++设计理念的内容就介绍到这里,同时这也是 easylogging++系列文章的最后一篇。
第一次写这种系列的源码分析文章,难免有所不足,算是抛砖引玉了。
欢迎大家在留言区提出建议,我们一起共同成长!