##Interfaces
So in ruby, we may have some method that takes an parameter duck
, and calls some method, quack
on that parameter.
class Duck
def quack
puts "Quack"
end
end
def duck_type(duck)
duck.quack
end
my_duck = Duck.new # => #<Duck:0x007f95b9824fa0>
duck_type(my_duck)
# >> Quack
But, we can pass anything we want into that method, and as long as it has a method defined on it called quack
, the code will run just fine. This is what we mean by duck typing.
class Duck
def quack
puts "Quack"
end
end
class Chicken
def quack
puts "Ummm, cluck, cluck, cluck"
end
end
def duck_type(duck)
duck.quack
end
my_duck = Duck.new # => #<Duck:0x007f91d46c4310>
duck_type(my_duck)
my_chick = Chicken.new # => #<Chicken:0x007f91d46cff30>
duck_type(my_chick)
# >> Quack
# >> Ummm, cluck, cluck, cluck
But, this is a lot harder to accomplish in a statically typed language like java. Java needs to know what type of object is being passed into and returned from any given method, but sometimes we may have a method which needs to be able to take two different types of objects. One way to handle this is inheritance.
public class DuckTyping {
public static void duckTyping(Duck duck) {
duck.quack();
}
}
public class Duck {
public void quack() {
System.out.println("Quack!");
}
}
public class Chicken extends Duck {
@Override
public void quack() {
System.out.println("Um, cluck, cluck!");
}
}
public class Main {
public static void main(String[] args) {
Duck myDuck = new Duck();
DuckTyping.duckTyping(myDuck);
Chicken myChicken = new Chicken();
DuckTyping.duckTyping(myChicken);
}
}
// => Quack!
// => Um, cluck, cluck!
But what if our Chicken
already inherits from something else? This is where interfaces come in.
public interface DuckType {
public void quack();
}
public class Duck implements DuckType {
// The Duck class has to implement all methods from any interface it
// implements. This way, the compiler knows what methods it can call on
// anything accepting a "DuckType" parameter.
@Override
public void quack() {
System.out.println("Quack!");
}
}
public class ChickenPrototype {
public void somethingImportantAndSpecificToChickens() {
System.out.println("Yo, Chickens have to do this thing.");
}
}
public class Chicken extends ChickenPrototype implements DuckType {
// The Chicken class BOTH inherits all the methods from ChickenPrototype
// And implements the methods in the DuckType interface. So it can be
// passed in as an argument to any method that accepts any of Chicken,
// DuckType or ChickenPrototype as a parameter.
@Override
public void quack() {
System.out.println("Um, cluck, cluck!");
}
}
public class DuckTyping {
// Notice we are now accepting "DuckType" as the param here, instead
// of "Duck".
public static void duckTyping(DuckType duck) {
duck.quack();
}
}
public class Main {
public static void main(String[] args) {
Duck myDuck = new Duck();
DuckTyping.duckTyping(myDuck);
Chicken myChicken = new Chicken();
DuckTyping.duckTyping(myChicken);
myChicken.somethingImportantAndSpecificToChickens();
}
}
// => Quack!
// => Um, cluck, cluck!
// => Yo, Chickens have to do this thing.
Where this is especially useful is when you have something like a listener, where the class using it will be likely to be writing a small amount of code on the fly that then needs to be run. We can demonstrate this easily with a tiny addition to our main class:
public class Main {
public static void main(String[] args) {
Duck myDuck = new Duck();
DuckTyping.duckTyping(myDuck);
Chicken myChicken = new Chicken();
DuckTyping.duckTyping(myChicken);
myChicken.somethingImportantAndSpecificToChickens();
DuckTyping.duckTyping(new DuckType() {
@Override
public void quack() {
System.out.println("Wassup dawg. This ain't your usual quack.");
}
});
}
}
// => Quack!
// => Um, cluck, cluck!
// => Yo, Chickens have to do this thing.
// => Wassup dawg. This ain't your usual quack.
We instantiate an Anonymous Inner Class (unnamed class written directly inside another class) that implements our DuckType interface right there, and pass it directly into DuckTyping#duckTyping instead of passing it a Duck or Chicken object.
In dynamic languages like ruby, we can figure all these types and method lookups at runtime, so we can have things like real duck-typing without all this rigamarole. But there's a tradeoff in terms of speed and typesafety. In java or other statically typed languages, we need something like interfaces to handle these kinds of situations.