Exercise: Proxy (Remote Thermometer)

Network Communication

Consider a Sensor instance that is reachable over a network. That sensor sure lives outside the address space of a possible client, so a network protocol must be invented to ask it for its value.

In this exercise, the “network” is represented by the communication between a client thread and a “remote” thread in the same address space, expressed in the following class definitions,

#pragma once

#include "queue.h"

#include <string>
#include <thread>
#include <future>
#include <memory>


namespace jf::utilities
{

class ServerThread
{
public:
    // in the remote thread, receives the "request", does with it
    // whatever needs to be done, and returns a "response" that is
    // sent back across the "network"
    class RemoteAdapter
    {
    public:
        virtual ~RemoteAdapter() {}
        virtual std::string execute(const std::string& request) = 0;
    };

public:
    ServerThread(RemoteAdapter*);
    ~ServerThread();

    // clients send a "request" packet across the "network". on the
    // remote side, that request is handed over to a RemoteAdapter
    // instance. that instance's return value, the "response" is then
    // sent back and returned.
    std::string write(const std::string& request);

private:
    struct Packet
    {
        std::string request;
        std::promise<std::string> response_promise;
    };

    RemoteAdapter* _adapter;
    MTQueue<Packet*> _queue;
    std::thread _thread;
};

}

Requirements

Use the “remote” form of the Proxy design pattern to implement such a client, in the form of yet another Sensor implementation that fulfills the following requirements.

#include <gtest/gtest.h>

#include <server-thread.h>
#include <sensor-const.h>
#include <sensor-remote.h>
#include <sensor-remote-adapter.h>


struct proxy_remote_suite : ::testing::Test    // <--- fixture: server thread and sensor setup
{
    proxy_remote_suite()
    : _cs(42.0),
      _adapter(&_cs),
      server(&_adapter)
    {}

    ConstantSensor _cs;
    RemoteSensorAdapter _adapter;
    jf::utilities::ServerThread server;
};

TEST_F(proxy_remote_suite, protocol)
{
    RemoteSensor rs(&server);
    ASSERT_FLOAT_EQ(rs.get_temperature(), 42.0);
}

TEST_F(proxy_remote_suite, remote_sensor__is_a__sensor)
{
    RemoteSensor rs(&server);
    Sensor* s = &rs;
    ASSERT_FLOAT_EQ(s->get_temperature(), 42.0);
}

Implementation Hints

The requirements (a.k.a. unit tests) above push you towards an implementation that takes place in the following class diagram. It’s the green parts that need to be implemented.

Note that the RemoteAdapter is a specialized form of the Adapter pattern: implementations of the RemoteAdapter interface are supposed to adapt to something by implementing a protocol onto somthing that is already there. This is not a requirement though - a simplistic implementation may not only implement the protocol, but also the logic behind it (effectively turning it into an example of Command)

../../../../../_images/proxy-remote.png