1

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.

Greg Burghardt
45.7k8 gold badges85 silver badges149 bronze badges
asked Jan 22 at 15:14
15
  • "... which propose the same services, but with different implementations." -- did you mean they require the same services, but with different implementations? Commented Jan 22 at 15:25
  • No, the motors interfaces must propose the same services (GoForward(), GoBackward(), but the implementations will be different, because they will be connected to different hardware motors. Commented Jan 22 at 15:33
  • 1
    Yes, the services are for clients, so they must be exposed. PS: sorry, english is not my native tongue. :) Commented Jan 22 at 15:36
  • 1
    "Java, an interface cannot have any member" this is a quirk of Java, but even there, methods (including getters) can have a return type that is an interface, and in Java, you can have abstract classes (and concrete base classes) that absolutely can have members that are of an interface type. C++ simply doesn't have separate keywords for interfaces and base classes (abstract or otherwise). Weather or not this is a good design choice in this context is a different question, but a misconception about Java is not a reason to reject it. Commented Jan 22 at 19:44
  • 1
    @Oodini while the Strategy pattern allows changing behaviors at runtime it also allows changing, or simply injecting, behaviors at construction time which makes it relevant. It would allow you to hide IHWCom and any other low level interfaces behind an adapter that implements IMoter. Commented Jan 23 at 16:23

2 Answers 2

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.

answered Jan 22 at 15:51
6
  • 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. Commented 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? Commented 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 from IMotor. IMotor would inherit "protectedly" from IHWCom, so that MotorA should have an implementation IHWCom's interface, but without being considered itself as an HW interface. Serial (or CAN...) would inherit publically from IHWCom, and MotorA sould inherit privately from Serial. So MotorA should implement the interface of IMotor and IHWCom, but expose only the IMotor interface. Commented 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 to IHWCom ? Wrap it when you need it. When you don't, dont. Commented 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. Commented Jan 23 at 14:57
1

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 to HWInterface, and perhaps even a pointer member from HWInterface to Motor 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 abstract Implementor class that defines the basic services (and components) used by a MotorImplementation, 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.

answered Jan 24 at 0:06
4
  • 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. Commented 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-sheet Commented 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. Commented 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. Commented Jan 24 at 12:48

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.