Program to an interface, not an implementation

Eric Brunel eric.brunel at pragmadev.com
Mon Jun 10 11:48:55 EDT 2002


Egbert Bouwman wrote:
[snip]
> In Gordon's reply the venom is in the end:
>> For C++ interface inheritance REQUIRES class inheritance.  For
>> Python/Smalltalk, it does not.
> 
> This I read as:
> For Python interface_inheritance does not require class_inheritance.

Nope: it simply means that there's no such thing as an interface in Python, 
or more exactly that the interface of an object is figured out dynamically. 
For example, this code works:

class A:
  def m(self):
    print "I'm A"
class B:
  def m(self):
    print "I'm B"
def f(o): o.m()
f(A())
f(B())

The function "f" uses the method "m" in both classes. However, classes "A" 
and "B" are not related at all: neither do any inherits from the other, nor 
do they have a common ancestor. In C++ (or Java), the Python code above 
would be unwritable if the method "m" wasn't declared in a common ancestor 
of A and B. If you write:

public class A {
  public void m() {
    System.out.writeln("I'm A");
  }
}
public class B {
  public void m() {
    System.out.writeln("I'm B");
  }
}

you would not be able to write the equivalent of the method/function "f" 
than can act on either an instance of A or an instance of B. In C++ or 
Java, the interfaces *must* be declared, so you'll have to write:

public class I {
  public abstract void m();
}
public class A inherits I {
  public void m () {
    System.out.writeln("I'm A");
  }
}
public class B inherits I {
  public void m () {
    System.out.writeln("I'm B");
  }
}
...
public void f(I o) {
  o.m();
}

Now you can call f on an instance of A or an instance of B, because you 
have been able to *type* the object with the type (= interface) I.

> So you can inherit an interface without inheriting the class,
> but interface_inheritance by way of class_inheritance is possible as well.

Again, nope: you cannot *inherit* an interface without inheriting from the 
class. In fact, I find Gordon's sentence a bit unclear (sorry 
Gordon...). Maybe it would be a little clearer if you spoke about 
"interface *sharing*" instead of "interface inheritance":

"For C++ interface sharing REQUIRES class inheritance.  For 
Python/Smalltalk, it does not."

In Python or SmallTalk, you may have the same (dynamically figured out) 
interface without having to make classes inherit from a common ancestor. In 
C++ or Java, it would be impossible, as we saw above...

In fact, things are a bit unclear because the word "interface" may be used 
to represent two things:
- the actual set of methods with their signature (in the example, the 
single method "m" with no parameters and no return value);
- the type used to define this set (in the Java flavour of the example, the 
type "I")

The first exists in every OO language, but the second may not exist, like 
in Python or SmallTalk. But both are always inherited by class inheritance 
(at least in every OO language I know). In the GoF book, they only speak 
about the second. But you may of course choose to force yourself to always 
do interface sharing via inheritance. You may write the example above as 
follows:

class I:
  def m(self):
    raise NotImplementedError
class A(I):
  def m():
    print "I'm A"
class B(I):
  def m(self):
    print "I'm B"
def f(o):
  # Here, we may say the o has the "type" I
  o.m()
f(A())
f(B())

In fact, making things as if Python had interfaces is just a matter of 
discipline: in the function f, you choose to see the object o as an 
instance of class I, and not to call any method not defined in class I, 
even if you know the method will exist. For example, you may consider the 
following code incorrect:

class I:
  def m1(self):
    raise NotImplementedError
class A(I):
  def m1():
    print "I'm A"
  def m2(self):
    pass
class B(I):
  def m1(self):
    print "I'm B"
  def m2(self):
    pass
def f(o):
  # o is considered as an instance of I
  o.m1()  # is correct...
  o.m2()  # is incorrect...

Of course, the "incorrectness" of the second line only happens in your 
head, because the method "m2" exists on both classes A and B, so the Python 
interpreter would not complain at all. But you may want to consider it 
incorrect, because the method m2 does not exist in I... But to be able to 
achieve that, you'll need to carefully document your code to know what 
method calls are "legal" on the objects you handle. And this can only be 
achieved by comments in your code and/or code documentation via UML 
diagrams, for example. But it may be worth the effort, since, maybe, 
someday you'll want to write a class C whose instances can be passed to f. 
And at that time, it will be really easier to know which methods should be 
implemented on C just looking at I...

Hope it's a bit clearer now...
-- 
- Eric Brunel <eric.brunel at pragmadev.com> -
PragmaDev : Real Time Software Development Tools - http://www.pragmadev.com



More information about the Python-list mailing list