In Java, it is very intuitive how interfaces are defined and used. You just create an interface in a similar way you would create a class and derive the classes, implementing the interface.
interface Drivable {
public void drive(int meters);
public void stop();
}
class Car implements Drivable {
public void drive(int meters) {
//start the engine
//go for it
}
public void stop() {
//stop the engine
}
}
class Bagger implements Drivable {
public void drive(int meters) {
//start engine
//start left track
//start right track
}
public void stop() {
//stop left track
//stop right track
}
}
class AutomatedDriver {
public void forward(Drivable vehicle, int meters) {
vehicle.drive();
vehicle.stop();
}
}
This results in a exlplicit class structure as depicted in the following class diagram.
However, in languages like ruby, interfaces are defined implicitly, which means that two classes implement the same interface as soon as they respond to the same interface. Take a look at our example as implemented in ruby:
class Car
public void drive(meters)
# start the engine
# go for it
end
def stop
# stop the engine
end
end
class Bagger
def drive(meters)
# start engine
# start left track
# start right track
end
def stop
# stop left track
# stop right track
end
end
class AutomatedDriver
def forward(vehicle, meters)
vehicle.drive();
vehicle.stop();
end
end
The classes Car and Bagger still both implement the Drivable interface. But as in ruby you use the so-called ducktyping, they both implement it implicitly by just responding to the same API, consisting of drive and stop. However, even in duck-typed languages, you might want to define and document your interfaces in a central point to make sure once you change it, all implementing classes do as well. You can do this by implementing unit tests to ensure the interface is fulfilled.
Following is an example of a rspec test to ensure our Drivable interface is implemented correctly.
shared_examples "a Drivable" do
it { expect(subject).to respond_to(:drive).with(1).argument }
it { expect(subject).to respond_to(:stop).with.no_args }
end
describe Car do
it_behaves_like "a Drivable"
end
describe Bagger do
it_behaves_like "a Drivable"
end
If the developer now changes something in the interface Drivable, he does so in the rspec test ensuring the interface. This test will fail for all classes that are expected to implement it but not yet do.
Even if it is not as intuitiv as it is in java, where your code just doesn’t compile if you fail to implement the interface, it is possible to define an interface and ensure it is implemented correctly in duck typed languages.
You might argue that you lose a bit of the flexibility of duck typing if you implement this for all your interfaces, and you are right! But in many cases, for example if the one defining the interface and the ones implementing it are different people, this is a very useful tool.
For example, imagine you are the author of a ruby library. A shared_example is a good and straight forward way to tell the users of your ruby gem what you expect their classes to behave like. Also, this will make them confident that if they upgrade to a newever version of your library, they will notice changes in the API by executing their test suite.
Categorised in: Uncategorized
This post was written by Manuel Dewald