Home > Back-end >  Policy based design combined with builder
Policy based design combined with builder

Time:12-29

I'm designing a logger library in C , and I've been stuck with my implementation of a formatter class.

My goal is to be able to create a message "formatter", which will take a string (something like "[A] [B]", or "[B]") and decide which policies to attach to my formatter.

Here is a simplified version of my policy implementation:

#include <iostream>
#include <string>
#include <memory>

template <typename PolicyA, typename PolicyB>
class Formatter : private PolicyA, private PolicyB {
    public:
    void Format(std::string& param_message) const {
        PolicyA::A(param_message);
        PolicyB::B(param_message);
    }
};

class Default {
protected:
    virtual void A(std::string& param_message) const = 0;
};

class ADefault : public virtual Default {
protected:
    void A(std::string& param_message) const {}
};

class AExplicit : public virtual Default {
protected:
    void A(std::string& param_message) const { param_message = "[A] "   param_message; }
};

class BDefault : public virtual Default {
protected:
    void B(std::string& param_message) const {}
};

class BExplicit : public virtual Default {
protected:
    void B(std::string& param_message) const { param_message = "[B] "   param_message; }
};

int main() {
    std::string message_1 = "message_1";
    std::string message_2 = "message_2";

    Formatter<ADefault, BDefault> default_message;
    Formatter<AExplicit, BExplicit> explicit_message;
    
    default_message.Format(message_1);
    explicit_message.Format(message_2);
    std::cout << message_1 << std::endl;
    std::cout << message_2 << std::endl;
}

The policy works as I intended, but how would I go about creating a sort of Builder function? One that could return a Fromatter from a set of parameters. I already have an implementation of the string interpreter, which returns an enum buffer of formats:

enum class Format : uint16_t {
    NONE = 0x000, 
    A    = 0x001,
    B    = 0x002,
};


Format ConfigFormatter(std::string& param_configuration) {
    Format formatter_configuration = Format::NONE;

    if (param_configuration.find("[A]") != std::string::npos) {
        formatter_configuration  = Format::A;
    }

    if (param_configuration.find("[B]") != std::string::npos) {
        formatter_configuration  = Format::B;
    }

    return formatter_configuration;
}

Is it possible to store the created Formatter as a member variable of another class? And should the Builder class be a variadic class with specializations for every type? I've looked for on variadic factories, but could not find anything that could help me.

CodePudding user response:

Maybe this could help:

#include <iostream>
#include <sstream>
#include <string>
#include <string_view>

enum class LogLevel { Debug, Info, Warn, Error, Fatal, Off };

template <typename... Fmts>
struct Logger {
    template <LogLevel Level>
    inline void Log(std::string_view msg) {
        if constexpr (Level == LogLevel::Off) {
            return;
        }

        std::stringstream out{};

        ((Fmts::template Format<Level>(out)), ...);

        std::clog << out.str() << msg << std::endl;
    }
};

template <typename... Fmts>
struct LoggerBuilder {
    template <typename Fmt>
    inline auto With() {
        return LoggerBuilder<Fmts..., Fmt>{};
    }

    inline auto Build() {
        return Logger<Fmts...>{};
    }
};

struct DefaultFormatter {
    template <LogLevel Level>
    static inline void Format(std::stringstream& buf) {
    }
};

struct ExplicitFormatter {
    template <LogLevel Level>
    static inline void Format(std::stringstream& buf) {
        using enum LogLevel;

        if constexpr (Level == Debug) {
            buf << "[DEBUG] ";
        } else if constexpr (Level == Info) {
            buf << "[INFO] ";
        } else if constexpr (Level == Warn) {
            buf << "[WARN] ";
        } else if constexpr (Level == Error) {
            buf << "[ERROR] ";
        } else if constexpr (Level == Fatal) {
            buf << "[FATAL] ";
        }
    }
};

struct LongTimeFormatter {
    template <LogLevel Level>
    static inline void Format(std::stringstream& buf) {
        buf << "[00:00:00.00 00/00/0000] ";
    }
};

int main() {
    auto logger = LoggerBuilder{}
                      .With<LongTimeFormatter>()
                      .With<ExplicitFormatter>()
                      .Build();

    logger.Log<LogLevel::Debug>("My Debug Message");
    logger.Log<LogLevel::Info>("My Info Message");
    logger.Log<LogLevel::Warn>("My Warn Message");
    logger.Log<LogLevel::Error>("My Error Message");
    logger.Log<LogLevel::Fatal>("My Fatal Message");
    logger.Log<LogLevel::Off>("Not Printed");

    return 0;
}

It uses templates rather than RTTI, so you get almost a zero-cost abstraction.

  • Related