2 + 3
5
Introduction to Software Engineering (CSSE 1001)
Interface Versus Implementation
Here the method area
is the interface.
1.0, 4.0).area() rectangle(
4.0
1.0).area() circle(
3.14159
Notice both shapes have the same interface (area
) despite requiring different implementations of the area calculation.
How the area was calculated is (mostly) irrelevant at the user-level.
The word polymorphism means to take on many forms.
In computer science, an interface that works over different underlying data-types is called polymorphic.
The plus operator is polymorphic – it works on numbers, strings, lists, and any other class that has implemented __add__
.
2 + 3
5
"Drop" + " Bear"
'Drop Bear'
1, 2] + [3, 4] [
[1, 2, 3, 4]
1,2) + Point(3,4) # Supposing point was implemented Point(
Point(4, 6)
Exercise 1 What is another example polymorphic function that we have encountered that is not arithmetic?
Consider a Cat
and Dog
class that provide the same methods (i.e. have the same interface).
= Cat("Maru", 14)
cat = Dog("Bluey", 6) dog
cat.speak()
'Meow'
dog.speak()
'Bark'
cat.info()
'Maru the cat is 14 years old'
dog.info()
'Bluey the dog is 6 years old'
Implement the Cat
and Dog
class so that it provides this interface.
class Cat():
def __init__(self, name: str, age: int) -> None:
self._name, self._age = name, age
def speak(self) -> str:
return "Meow" # not the same as printing
def info(self) -> str:
return f"{self._name} the cat is {self._age} years old"
class Dog():
def __init__(self, name: str, age: int) -> None:
self._name, self._age = name, age
def speak(self) -> str:
return "Bark"
def info(self) -> str:
return f"{self._name} the dog is {self._age} years old"
There are only differences in the Dog
and Cat
class are what sound is returned by speak
and the species of the animal in the info
return string.
Notice both of these classes have very similar implementations of their interfaces. It would be best to reuse the code by having generic methods instead.
For instance, Cat
and Dog
have identical __init__
methods and therefore it is overkill to copy-paste this for them and every other animal we intend to instantiate.
Objects are already grouped by their classes. For instance, we could instantiate many Dog
objects from the class Dog
.
In maths say: \[
{\rm Bluey} \in Dog.
\] In CS we say: \[
{\rm Bluey} \text{ is a } Dog
\] We can also group classes themselves into super-classes \[
{\rm Dog} \text{ is a } Animal
\] \[
{\rm Cat} \text{ is a } Animal
\] and have Animal
define the generic interface that Dog
and Cat
share.
But notice both Cat
and Dog
require Speak
, albeit a custom one. We can express this in Animal
by implementing a Speak
method that throws the NotImplementedError
.
class Animal():
def speak(self) -> str:
raise NotImplementedError
This abstract class is essentially a blueprint for what subclasses of Animal
must implement in order to share the common Animal
-interface.
class Turtle(Animal):
pass
= Turtle("Yertle", 101) yertle
yertle.info()
'Yertle the turtle is 101 years old'
yertle.speak()
Error: NotImplementedError
class Fox(Animal):
def speak(self) -> str:
return "Ring-ding-ding-ding-dingeringeding!"
= Fox("Kyuubi", 1432) kyuubi
kyuubi.info()
'Kyuubi the fox is 1432 years old'
kyuubi.speak()
'Ring-ding-ding-ding-dingeringeding!'
We are able to overwrite methods by declaring them (as we normally would).
class Fox(Animal):
def speak(self) -> str:
return "Ring-ding-ding-ding-dingeringeding!"
def info(self) -> str:
return "Foxes are better than cats and dogs."
fox.info() 'Foxes are better than cats and dogs'
We are also able to extend methods.
class Fox(Animal):
def __init__(self, name: str, age: int, nationality: str) -> None:
super().__init__(name, age) # call init from the super-class
self._nationality = nationality # extra attribute
= Fox("Kyuubi", 1432, "Japanese")
kyuubi
kyuubi._name 'Kyuubi'
kyuubi._age 1432
kyuubi._nationality 'Japanese'
A unified modelling language diagram is the standard way of illustrating inheritance relationships among classes.
For instance, for our Animal
class we have…
Consider the following code snippet (from the course notes).
class A(object): # 'object' is the universal class
def __init__(self, x):
self.x = x
def f(self):
return self.x
def g(self):
return 2 * self.x
def fg(self):
return self.f() - self.g()
= A(3) a
a.x
3
a.f()
3
a.g()
6
a.fg()
-3
class B(A):
def __init__(self, x): # inherit from A
self.x = x
def f(self): # inherit from A
return self.x
def g(self): # overwrite
return self.x ** 2
def fg(self): # inherit from A
return self.f() - self.g()
class B(A): # inherit from A
def g(self): # overwrite
return self.x ** 2
= B(7) b
b.x
7
b.f()
7
b.g()
49
b.fg()
-42
class C(B):
def __init__(self, x, y): # extend from B from A
super().__init__(x) # Super is B
self.y = y
def fg(self): # extend from B from A
return super().fg() * self.y
class C(B):
def __init__(self, x, y): # extend from B from A
super().__init__(x)
self.y = y
def f(self): # inherit from B from A
return self.x
def g(self): # inherit from B
return self.x ** 2
def fg(self): # extend from B from A
return super().fg() * self.y
= C(3,5) c
c.x
3
c.y
5
c.f()
3
c.g()
9
c.fg()
-30
class D(A): # inherit from A
def f(self): # overwrite
return -2 * self.g()
class D(A): # inherit from A
def __init__(self, x): # inherit from A
self.x = x
def f(self): # overwrite
return -2 * self.g()
def g(self): # inherit from A
return 2 * self.x
def fg(self): # inherit from A
return self.f() - self.g()
= D(3)
d
d.x 3
d.f() -12
d.g() 6
d.fg() -18
A
, B
, C
, D
class example.
Practice Assignment UML
Dotted arrows indicate has-a relationships and solid ones denote is-a relationships.
Exercise 2 Implement the following classes that inherit from Shape
.
class Shape():
pass
class Rectangle(Shape):
pass
class Square(Shape):
pass
Which provides the following interface.
= Rectangle(10, 5)
r = Square(10)
s < s r
False
Classes are further categorized into super-classes which provide abstract-methods. This abstract methods serve as blueprints or outright implementations for the interfaces that all sub-classes must provide.