From Concrete To Polymorphic (class Sensor
)¶
Very Concrete, And Old School¶
Main program calls
logger.log_one()
at a one-second interval (man -s3 sleep)
#include "sensor-mock.h"
#include "logger.h"
#include <unistd.h>
int main()
{
MockSensor sensor{42.666};
Logger logger(&sensor);
while (true) {
logger.log_one();
sleep(1);
}
return 0;
}
Logger::log_one()
takes the current time, and outputs the sensor value along with it (man -s2 time).
#pragma once
#include "sensor-mock.h"
#include <iostream>
#include <time.h>
class Logger
{
public:
Logger(MockSensor* sensor) : _sensor{sensor} {}
void log_one()
{
time_t now = time(nullptr);
double value = _sensor->get_value();
std::cout << now << ": " << value << std::endl;
}
private:
MockSensor* _sensor;
};
Mock sensor straightforward, with a method
set_value()
for test usage. (Note thatLogger
only usesget_value()
.)
#pragma once
class MockSensor
{
public:
MockSensor(double initial_value) : _value{initial_value} {}
double get_value() const { return _value; }
// for tests only
void set_value(double v) { _value = v; }
private:
double _value;
};
Use std::chrono
For Time¶
Main program
#include "sensor-mock.h"
#include "logger.h"
#include <chrono>
#include <thread>
using namespace std::chrono_literals;
int main()
{
MockSensor sensor{42.666};
Logger logger(&sensor);
while (true) {
logger.log_one();
std::this_thread::sleep_for(0.5s); // <-- cool
}
return 0;
}
Logger. Talk about Clock Domains
#pragma once
#include "sensor-mock.h"
#include <iostream>
#include <chrono>
class Logger
{
public:
Logger(MockSensor* sensor) : _sensor{sensor} {}
void log_one()
{
// std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
auto now = std::chrono::system_clock::now(); // <-- cool
double value = _sensor->get_value();
std::cout << now << ": " << value << std::endl;
}
private:
MockSensor* _sensor;
};
New Sensor Type: FileSensor
¶
Invent new sensor type:
FileSensor
Show usage in
main.cpp
, not passing it to logger
#pragma once
#include <string>
class FileSensor
{
public:
FileSensor(const std::string& filename) : _filename{filename} {}
double get_value() const;
private:
std::string _filename;
};
#include "sensor-file.h"
#include <stdexcept>
#include <fstream>
#include <string>
double FileSensor::get_value() const
{
std::ifstream f(_filename);
if (!f.is_open()) {
std::string msg = "Cannot open " + _filename;
throw std::runtime_error(msg);
}
double value;
f >> value;
return value;
}
Type Mismatch: Logger(...)
¶
Pass to
Logger
⟶ wantsMockSensor*
, notFileSensor*
What to do?
Interfaces,
virtual
See Inheritance And Object Oriented Design for the full story
Create Interface: Sensor
¶
jjj begin sketch
Every interface has a
virtual
destructor that does nothingDynamic dispatch: object carries its type, and that type’s
get_value()
is called.
#pragma once
class Sensor
{
public:
virtual ~Sensor() = default; // <-- dogmatic!
virtual double get_value() const = 0; // <-- dynamic dispatch
};
Derive Concrete Implementations From Sensor
¶
jjj finish sketch
Is-a relationship
override
thevirtual = 0
in interface (don’t ask)
#pragma once
#include "sensor.h"
#include <string>
class FileSensor : public Sensor // <-- is-a Sensor
{
public:
FileSensor(const std::string& filename) : _filename{filename} {}
double get_value() const override; // <-- argh
private:
std::string _filename;
};
#pragma once
#include "sensor.h"
class MockSensor : public Sensor // <-- is-a Sensor
{
public:
MockSensor(double initial_value) : _value{initial_value} {}
double get_value() const override { return _value; }
// for tests only
void set_value(double v) { _value = v; }
private:
double _value;
};