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 that Logger only uses get_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;
}
#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(...)

Create Interface: Sensor

  • jjj begin sketch

  • Every interface has a virtual destructor that does nothing

  • Dynamic 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 the virtual = 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;
};