Let's say I am coding an C++ application which needs to drive some motors with some hardware interface (USB, Serial...).
There are several kinds of motors, which expose the same services, but with different implementations. I could connect a concrete motor with one hardware interface among several possible interfaces.
So I need an interface class for the motor, and an interface class for hardware communication.
How to modelize that ?
Should I have a class IMotor
with a IHWCom*
member ?
I don't know if it is a good practice: in Java, an interface cannot have any member.
But for me, as a implementation class deriving from IMotor
should always have a dependency on an hardware interface for the communication, that should appear in the base class. SRP is so not relevant, because it doesn't make sense to have a concrete class of IMotor
without hardware communication layer. The dependency could be added in each concrete class, but so we have some duplication, and a common feature is not visible anymore.
2 Answers 2
a common feature is not visible anymore.
This is an important thing to signal clearly. It's tempting to just create a bag of things you can tell a motor to do but some motors have different capabilities.
At the high software level you want interfaces that represent these different capabilities: Off, Forward, Reverse, Angle.
If your motor isn't a stepper motor that Angle thing is likely meaningless. So motors that don't offer that feature shouldn't be forced to provide an interface that includes it. That's not a single responsibility thing. It's an Interface Segregation Principle thing.
Now that said, motors that can provide these capabilities might have different implementation needs. That's fine, but at a certain level we'd like to not have to worry about what voltages are required or even if the motor is DC, AC, or diesel. And that includes whatever hardware communication protocol is required. There's no need for that to appear at the top of a hierarchy. It's simply another implementation detail that is good to hide from the rest of the code. A good interface is also a good abstraction, behind which you can hide details. By hiding the details they can be managed in one place without having knowledge of them spread throughout the code base.
That's more where the Single Responsibly Principle becomes important. Dykstra had a very similar idea that he called Separation of Concerns. When I look at a class it's nice if what I'm asked to worry about here is minimized. Push other concerns away behind abstractions (with good names please) and concentrate on one responsibility. Do that and I'll enjoy working in your code.
-
That's about splitting interfaces, and I agree with you about features specific to some kinds of motors. Anyway, there is also the substitution principles, and as all there motors are motors, i's not a bad idea they derive from
IMotor
, even if there are not many features. You sau say we don't have to know the required protocol. But in my original propossal, we don't know the protocol; we only know there is a protocol. We depend on an interface of protocol, not from an actual protocol.Oodini– Oodini2025年01月22日 17:00:14 +00:00Commented Jan 22 at 17:00 -
@Oodini my hope would be for interfaces that provide a high level abstraction under which hide whatever adapters are needed to talk to a variety of lower level hardware protocols. However, I acknowledge that every abstraction leaks. Anyway, is there something missing from my answer that you still require?candied_orange– candied_orange2025年01月22日 18:21:39 +00:00Commented Jan 22 at 18:21
-
OK, I propose an other solution. I was initially reluctant to it, maybe it is not so bad: the diamond. :)
MotorA
would inherit publicly fromIMotor
.IMotor
would inherit "protectedly" fromIHWCom
, so thatMotorA
should have an implementationIHWCom
's interface, but without being considered itself as an HW interface.Serial
(orCAN
...) would inherit publically fromIHWCom
, andMotorA
sould inherit privately fromSerial
. SoMotorA
should implement the interface ofIMotor
andIHWCom
, but expose only theIMotor
interface.Oodini– Oodini2025年01月23日 08:54:25 +00:00Commented Jan 23 at 8:54 -
@Oodini sounds like diamond inheritance. Yes you could do that. But why? What benefit are you getting from exposing two different ways to control the motor at the same level of abstraction? Why not just provide an adapter from
IMotor
toIHWCom
? Wrap it when you need it. When you don't, dont.candied_orange– candied_orange2025年01月23日 11:54:50 +00:00Commented Jan 23 at 11:54 -
Well, it is also possible. With the adapter, you have to provide it as a function argument. It may be to the constructor, but it is generally to save it as a member, and there is composition in each leaf class. So the UML diagram is more complex. You can also send it as reference/pointer to functions needing it, but the adapter has so be stored by the client. The adapter can be changed between functions and calls to functions. I don't think it is wishable here. I'm going to think about this adapter solution. Thanks.Oodini– Oodini2025年01月23日 14:57:16 +00:00Commented Jan 23 at 14:57
Naive answer
A first reading leads to the simplest possible design:
- An abstract
Motor
class, - A abstract
HWInterface
- an association between the two, implemented using a (smart?) pointer member from
Motor
toHWInterface
, and perhaps even a pointer member fromHWInterface
toMotor
if the association needs to be bidrectional.
In practice you'd have a scenario where you'd create separately the interface and the motor and connect the two, or you'd create the motor injecting the interface.
More elaborate answer where not any motor could work with any interface
But reading again, I understand that the motor implementation depends on the kind of interface it should use. So not every motor could work with every interface. Moreover, a motor implementation could need specific functionality of a specialised interface, that the abstract interface does not provide.
The more sophisticated approach would then be to use the bridge pattern. The aim of this pattern is to:
Decouple an abstraction from its implementation so that the two can vary independently.
There are several variants of this pattern, and it is not full clear which would be the most promising in your case. But the basic idea would be to have:
- an abstract
Motor
, - a
MotorImplementation
which would rely on an abstractImplementor
class that defines the basic services (and components) used by aMotorImplementation
, and among others the hardware communication interface. - The
Implementor
classes would then be specialised, one for each different type of interface.
This design lets you specialise further the Motor
or its general implementation (e.g. electric motor, diesel motor, warp engine ...) whereas the Implementor
could be specialised independently to take care of specific interfaces or components.
This flexibility comes at the cost of some complexity, one of it being the construction of a Motor assembling the different implementor services (e.g. a builder pattern could be used to keep it general enough yet assembling different parts). Moreover if you have many other elements like the interface, you might ending ump in deep interleaved hierarchies that are quite difficult to maintain.
Perhaps the simpler Entity Component system could be another alternative, where the motor would be the system, the hardware interface would be one component, and maybe some other component will be the element of liaison between a certain kind of hardware interface and a certain kind of motors. The advantage of this alternative is that the hierarchy remains very flat, and components are easily interchanged.
-
Thanks for your complete answer and its nice formatting. :) The Bridge has been discussed in this thread, but without being named. In this pattern, the only way for
Motor
to force its derived classes to use the HW interface abstraction, and so to respect the Bridge pattern, is to refer to this abstraction in the arguments of the constructor of the base (Motor
). Not so visible in class diagram, but it may be considered as OK. The other solution is to put this reference in the base class, and so we come back to the original post.Oodini– Oodini2025年01月24日 09:18:19 +00:00Commented Jan 24 at 9:18 -
Actually, there are flavors of Bridge which put the reference in the base class, as here: blog.bytebytego.com/p/ep17-design-patterns-cheat-sheetOodini– Oodini2025年01月24日 09:27:50 +00:00Commented Jan 24 at 9:27
-
@Oodini Yes, but the idea of this pattern is to be able to subclass the motor abstraction and the Implementor independently. THis gives you also the possibility to implement covariance, making sure that the HWinterface provided is a certain kind of downcast, and then using this this downcast safely. And this could be done in specialisation of the abstraction of specialisation of the Implementor.Christophe– Christophe2025年01月24日 12:46:50 +00:00Commented Jan 24 at 12:46
-
You could even use a template for the implementation, yet still keep the polymorphic motor. This would even allow partial specialisation to address specificities of a given implementor with a given interface. This being said, the ECS approach could do a better job here, as I assume that the HW interface is only one of the component of your motor and you might have other components with similar issues but orthogonal specialisations, which is then very difficult to handle with the bridge.Christophe– Christophe2025年01月24日 12:48:58 +00:00Commented Jan 24 at 12:48
Explore related questions
See similar questions with these tags.
GoForward()
,GoBackward()
, but the implementations will be different, because they will be connected to different hardware motors.IHWCom
and any other low level interfaces behind an adapter that implementsIMoter
.