#include <iostream>
#include <string>
#include <string_view>
// assume you have a bit of code that needs to log something
// In this example I made a temperature sensor that needs to do some logging
// but the temperature sensor doesn't want to know anything about the logging system
// In that case make an interface that allows your temperature sensor to report something.
class temparature_sensor_report_itf
{
public:
virtual ~temparature_sensor_report_itf() = default;
virtual void report_temparature(int temp) = 0;
virtual void report_too_hot() = 0;
};
// Now you can write your code to use this interface
class logger_t
{
public:
// The initialization of the logger is to be done
// using a constructor
explicit logger_t(std::string_view filename)
: m_filename{filename}
{
}
void log(std::string_view msg)
{
std::cout << "Logging: " << msg << "\n";
}
private:
std::string m_filename;
};
// now make an adapter from report_itf to a specific logger_t
// this allows you to change the logger_t without changing the
// code that uses the temparature_sensor_report_itf (the temperature sensor class)
class temparature_sensor_logger_t :
public temparature_sensor_report_itf
{
public:
temparature_sensor_logger_t(logger_t& logger)
: m_logger{logger}
{
}
void report_temparature(int temp) override
{
m_logger.log("Temperature: " + std::to_string(temp));
}
void report_too_hot() override
{
m_logger.log("Too hot!");
}
private:
logger_t& m_logger;
};
// For unit testing you can now also make a reporter class that does nothing at all
class temperature_sensor_report_nothing_t :
public temparature_sensor_report_itf
{
public:
void report_temparature(int temp) override
{
// do nothing
}
void report_too_hot() override
{
// do nothing
}
};
// The temperature sensor class doesn't know anything about the logger
// it only has an injected dependency to the temparature_sensor_report_itf
class temparature_sensor_t
{
public:
explicit temparature_sensor_t(temparature_sensor_report_itf& reporter)
: m_sensor_reporter{reporter}
{
}
void read_temparature()
{
// read the temperature
int temp = 42;
// report the temperature
// but you don't know anything about the logger here.
m_sensor_reporter.report_temparature(temp);
if (temp > 40)
{
m_sensor_reporter.report_too_hot();
}
}
private:
temparature_sensor_report_itf& m_sensor_reporter;
};
int main()
{
// do NOT make a global logger initialize your logging system in main
// this also allows the destructor of your logger to do nice things
// like flushing the log file and closing it (something you cannot do
// when you use a singleton with a new but no delete)
logger_t logger{"log.txt"};
// now create a small adapter, this adapter allows you to change
// the logging infrastructure later without ever needing
// to change anything in the temparature_sensor_t class
temparature_sensor_logger_t reporter{logger};
// In unit tests you use this line instead of the previous one
// And now you can test without for example depending on the filestystem
// temperature_sensor_report_nothing_t reporter;
// now create the temparature sensor
// by injecting a class derived from temparature_sensor_report_itf
// For unit testing we will inject the temperature_sensor_report_nothing_t instance
temparature_sensor_t sensor{reporter};
// now read the temperature, if something needs to be reported
// it will be reported to the logger
sensor.read_temparature();
return 0;
}