What you're describing is the Adapter pattern (a way it can be realized). The interface (that your client code is written against) allows you to insert, if the need arises, an implementation that adapts the vendor-provided API (functions they provide) to what's expected by your client code.
E.g., your own (client) code calls Move_10_cm_left()
, but vendorX may provide a function Move(direction, amount)
, while vendorY may have something like send(actuatorID, controlSignal)
(I'm making these up, but you get the picture). The assumption is that you have no means/desire to change the source code of the vendor-provided functions; instead, you'll just write a special-purpose wrapper around them.
So, on your side, you write code that expresses some complex behavior of the robot in terms of your interface. Then, when you have to change the vendor, you can leave that code as is (or change it minimally - there could be unforeseen reasons for it to change), and only write an adapter that implements that interface:
// Your RobotControl interface is the (abstract) Adapter
// This is a ConcreteAdapter, written by you
class VendorYControl : RobotControl {
public override bool Move_10_cm_left() {
send(xyzID, signalSpec); // this is the vendor-provided API
}
// ... other methods ...
}
Then you inject that into your client code; something along these lines:
RunControlLoopWith(new VendorYControl());
// Where the signature of the function is
public void RunControlLoopWith(RobotControl control) {
// do stuff using 'control'
}
So now you have a library that supports two vendors, with an adapter for each. The code that defines the core behavior for both is the same, and you can inject more adapters in the future.
The interface allows you to keep the different adapter implementations, and to inject one of your choosing. If you had a concrete class instead, you'd still be able to change the implementation behind the public methods & properties, by changing the "guts" of the class, but you'd then be throwing away the old implementation.
Patterns can be realized in a number of different ways; it's not the exact details of their UML diagram that define them, they are about the intent, and the roles and interrelationships of the elements that comprise them.
So here are some variations. If there's some high-level behavior shared by all adapters, you can turn the interface into an abstract class. Alternatively, you could keep the interface, and use the Decorator pattern to transparently add that behavior on top. If you're using a duck-typed language like JavaScript, or more likely, Python, you may not have an explicit abstract adapter (interface or class) defined anywhere in the code - it could all just be defined in the specification/documantation for the RunControlLoopWith
method and its control
parameter. It's still the same pattern.
Move_10_cm_left
to illustrate something that is likely to change in the future, but I just can't get over the name. Why notmove(direction : Direction, distance : Distance)
? Then you can have a Direction enum (left, right, up, down, whatever you need) and a Distance type (which takes a number and unit of measure). No adapter needed.vendorX
andvendorY
classes? If you own them, they are the adapters. If you do not own them, you obviously cannot add an interface to them. Am I missing something?