I have an ESP8266 project programmed in the Arduino framework that gathers data from the network and then displays on a display. The device can be built with a few different display hardware types (eink, led, oled). These are set at compile time with #defines. However there are also a few different type of data and data transport mechanisms that can be used. Some require hardware (LoRa TX/RX) and are enabled at compile time but some can be changed at runtime based on user settings (eg. HTTP or MQTT).
I'm already using a factory design pattern to instantiate the Data transport object at runtime but still use compile time build flags to select which display hardware to use. I have a Display class, a Datasource class and a Config class. This has worked well but is now reaching its limit as I try to add Cellular functionality.
I wonder if there is a good design pattern / architecture design that will facilitate this kind of flexibility without having to keep adding more and more intrusive #ifdef statements all over my code.
Attached is a little mind map of the basic layout of possibilities of this device.
CodePudding user response:
If you want to make a decision what algorithn should be injected at runtime, then you can try to use Strategy pattern.
As wiki says about strategy pattern:
In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use
So you can read your config file and choose what object should be instantiated. For example, you have many displays:
public enum DisplayMark
{
Samsung, Sony, Dell
}
and then yoy should create a base class Display
:
public abstract class Display
{
public abstract string Show();
}
And then you need concrete implementations of this base class Display
:
public class SamsungDisplay : Display
{
public override string Show()
{
return "I am Samsung";
}
}
public abstract class SonyDisplay : Display
{
public override string Show()
{
return "I am Sony";
}
}
public abstract class DellDisplay : Display
{
public override string Show()
{
return "I am Dell";
}
}
So far, so good. Now we need something like mapper which will be responsible to bring correct instance by selected display from config:
public class DisplayFactory
{
public Dictionary<DisplayMark, Display> DisplayByMark { get; private set; }
= new Dictionary<DisplayMark, Display>
{
{ DisplayMark.Sony, new SonyDisplay()},
{ DisplayMark.Samsung, new SamsungDisplay()},
{ DisplayMark.Dell, new DellDisplay()},
};
}
and then when you will know what display should be used from config file, then you can get desired instance:
public void UseDisplay(DisplayMark displayMark)
{
DisplayFactory displayFactory = new DisplayFactory();
Display display = displayFactory.DisplayByMark[displayMark];
// Here you can use your desired display class
display.Show();
}