SOLID Principle - S


In object oriented programming, multiple objects are written in one project. Each object should have a single responsibility. This is the first principle of SOLID.
CommunicationInterface is abstract class that can support different types of communications such as UART, I2C, SPI and etc.
class CommunicationInterface
{
public:
virtual void Init() = 0;
virtual void Write(int device_address, int command) = 0;
virtual void Read(int device_address, int payload[], int payload_length) = 0;
};
Q1: CommunicationInterface violates S?
A1: No, the device_address for UART could be COM Port number. I2C and SPI device has its own address. Init function to configure GPIOs and peripheral clock source. Write takes charge of Tx and Read takes charge of Rx.
I2CInterface is a derived class from CommunicationInterface class. All pure virtual function has to be implemented with override keyword.
class I2CInterface : public CommunicationInterface
{
public:
void Init() override
{
//Init I2C CLK
//Init I2C SCK pin, SDA pin
}
void Write(int device_address, int command) override
{
//Send I2C message
}
void Read(int device_address, int payload[], int payload_length) override
{
//Read I2C message
}
};
Q2: I2CInterface violates S?
A2: No, there is no extra functions from CommunicationInterface.
TemperatureSensor can be communicated via I2CInterface so that the class is derived from I2CInterface. This class takes charge of sending read request and retrieve the data to convert back to Celsius.
class TemperatureSensor : public I2CInterface
{
public:
static const int OVERHEAT_THRESHOLD = 45;
TemperatureSensor() : temperature_in_celsius_m(25){}
void init()
{
I2CInterface::Init();
//Enable temperature sensor - I2CInterface::Write()
}
void RequestTemperature()
{
//Read temperature sensor - I2CInterface::Read()
//Convert payload to set temperature_in_celsius_m
}
int GetTemperatureInCelsius()
{
return temperature_in_celsius_m;
}
bool IsOverheat()
{
bool result = false;
if(GetTemperatureInCelsius() > OVERHEAT_THRESHOLD)
{
result = true;
}
return result;
}
private:
int temperature_in_celsius_m;
};
Q3: TemperatureSensor violates S?
A3: Yes, IsOverheat function detects if system experiences the overheat. This is not a temperature sensor’s responsibility.
Before refactoring:
#include <iostream>
using namespace::std;
class CommunicationInterface
{
public:
virtual void Init() = 0;
virtual void Write(int device_address, int command) = 0;
virtual void Read(int device_address, int payload[], int payload_length) = 0;
};
class I2CInterface : public CommunicationInterface
{
public:
void Init() override
{
//Init I2C CLK
//Init I2C SCK pin, SDA pin
}
void Write(int device_address, int command) override
{
//Send I2C message
}
void Read(int device_address, int payload[], int payload_length) override
{
//Read I2C message
}
};
class TemperatureSensor : public I2CInterface
{
public:
static const int OVERHEAT_THRESHOLD = 45;
TemperatureSensor() : temperature_in_celsius_m(25){}
void init()
{
I2CInterface::Init();
//Enable temperature sensor - I2CInterface::Write()
}
void RequestTemperature()
{
//Read temperature sensor - I2CInterface::Read()
//Convert payload to set temperature_in_celsius_m
}
int GetTemperatureInCelsius()
{
return temperature_in_celsius_m;
}
bool IsOverheat()
{
bool result = false;
if(GetTemperatureInCelsius() > OVERHEAT_THRESHOLD)
{
result = true;
}
return result;
}
private:
int temperature_in_celsius_m;
};
int main()
{
TemperatureSensor ts;
ts.init();
ts.RequestTemperature();
if(ts.IsOverheat())
{
cout << "Overheat!" << endl;
}
else
{
cout << "Normal" << endl;
}
}
Refactoring idea:
Move IsOverheat function to another class called SystemMonitor. SystemMonitor reference TemperatureSensor to make sure it refers to the same memory address.
After refactoring:
#include <iostream>
using namespace::std;
class CommunicationInterface
{
public:
virtual void Init() = 0;
virtual void Write(int device_address, int command) = 0;
virtual void Read(int device_address, int payload[], int payload_length) = 0;
};
class I2CInterface : public CommunicationInterface
{
public:
void Init() override
{
//Init I2C CLK
//Init I2C SCK pin, SDA pin
}
void Write(int device_address, int command) override
{
//Send I2C message
}
void Read(int device_address, int payload[], int payload_length) override
{
//Read I2C message
}
};
class TemperatureSensor : public I2CInterface
{
public:
static const int OVERHEAT_THRESHOLD = 45;
TemperatureSensor() : temperature_in_celsius_m{25}{}
void init()
{
I2CInterface::Init();
//Enable temperature sensor - I2CInterface::Write()
}
void RequestTemperature()
{
//Read temperature sensor - I2CInterface::Read()
//Convert payload to set temperature_in_celsius_m
}
int GetTemperatureInCelsius()
{
return temperature_in_celsius_m;
}
private:
int temperature_in_celsius_m;
};
class SystemMonitor
{
public:
SystemMonitor(TemperatureSensor& obj) : ts{obj}{}
bool IsOverheat()
{
bool result = false;
if(ts.GetTemperatureInCelsius() > TemperatureSensor::OVERHEAT_THRESHOLD)
{
result = true;
}
return result;
}
private:
TemperatureSensor& ts;
};
int main()
{
TemperatureSensor ts;
SystemMonitor sm(ts);
ts.init();
ts.RequestTemperature();
if(sm.IsOverheat())
{
cout << "Overheat!" << endl;
}
else
{
cout << "Normal" << endl;
}
}
To demonstrate the single responsibility, the example code shows couple good examples and one bad example. Then, refactor bad one to not violate the principle S. If project is simple enough, this refactoring process may add overhead. Refactoring is always judgement call.
Subscribe to my newsletter
Read articles from Hyunwoo Choi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
