There is a design problem that I came upon while implementing an interface:
Let's say there is a Device
interface that promises to provide functionalities PerformA()
and GetB()
. This interface will be implemented for multiple models of a device. What happens if one model has an additional functionality CheckC()
which doesn't have equivalents in other implementations?
I came up with different solutions, none of which seems to comply with interface design guidelines:
- To add
CheckC()
method to the interface and leave one of its implementations empty:
interface ISomeDevice
{
void PerformA();
int GetB();
bool CheckC();
}
class DeviceModel1 : ISomeDevice
{
public void PerformA() { // do stuff }
public int GetB() { return 1; }
public bool CheckC() {
bool res;
// assign res a value based on some validation
return res;
}
}
class DeviceModel2 : ISomeDevice
{
public void PerformA() { // do stuff }
public int GetB() { return 1; }
public bool CheckC() {
return true; // without checking anything
}
}
This solution seems incorrect as a class implements an interface without truly implementing all the demanded methods.
- To leave out
CheckC()
method from the interface and to use explicit cast in order to call it:
interface ISomeDevice
{
void PerformA();
int GetB();
}
class DeviceModel1 : ISomeDevice
{
public void PerformA() { // do stuff }
public int GetB() { return 1; }
public bool CheckC() {
bool res;
// assign res a value based on some validation
return res;
}
}
class DeviceModel2 : ISomeDevice
{
public void PerformA() { // do stuff }
public int GetB() { return 1; }
}
class DeviceManager
{
private ISomeDevice myDevice;
public void ManageDevice(bool newDeviceModel)
{
myDevice = (newDeviceModel) ? new DeviceModel1() : new DeviceModel2();
myDevice.PerformA();
int b = myDevice.GetB();
if (newDeviceModel)
{
DeviceModel1 newDevice = myDevice as DeviceModel1;
bool c = newDevice.CheckC();
}
}
}
This solution seems to make the interface inconsistent.
- For the device that supports
CheckC()
: to add the logic ofCheckC()
into the logic of another method that is present in the interface. This solution is not always possible.
So, what is the correct design to be used in such cases? Maybe creating an interface should be abandoned altogether in favor of another design?
Check
? That way, anything that needs to check something can, and anything that doesn't can simply return true. An interface describes what an object can do, it doesn't describe how it does it, having no-op implementations is completely valid if they are unnecessary.true
. However, I've always thought about the interfaces as describing what an object must do, what an object can do.checkC
. To insist on treating all devices the same in situations where you need to make use of the differences between them will simply result in loss of information (you won't be able to tell which devices really implementcheckC
and which don't if you force that interface upon every device).