Class design

Please open notebook rsepython-s4r6.ipynb

The concepts we have introduced are common between different object oriented languages.

Thus, when we design our program using these concepts, we can think at an architectural level, independent of language syntax.

In Python:

class Particle(object):
    def __init__(self, position, velocity):
       self.position=position
       self.velocity=velocity
    def move(self, delta_t):
       self.position+= self.velocity*delta_t

In C++:

class Particle {
    std::vector<double> position;
    std::vector<double> velocity;
    Particle(std::vector<double> position, std::vector<double> velocity);
    void move(double delta_t);
}

In Fortran:

type particle
    real :: position
    real :: velocity
  contains
    procedure :: init
    procedure :: move
end type particle

UML

UML is a conventional diagrammatic notation used to describe “class structures” and other higher level aspects of software design.

Computer scientists get worked up about formal correctness of UML diagrams and learning the conventions precisely.

Working programmers can still benefit from using UML to describe their designs.

YUML

We can see a YUML model for a Particle class with position and velocity data and a move() method using the YUML online UML drawing tool (example).

http://yuml.me/diagram/boring/class/[Particle position;velocity move%28%29]

Here’s how we can use Python code to get an image back from YUML:

import requests
from IPython.display import Image

def yuml(model):
    result = requests.get("http://yuml.me/diagram/boring/class/" + model)
    return Image(result.content)
yuml("[Particle|position;velocity|move()]")

YUML model for a Particle class with `position` and `velocity` data and a `move()` method

The representation of the Particle class defined above in UML is done with a box with three sections. The name of the class goes on the top, then the name of the member variables in the middle, and the name of the methods on the bottom. We will see later why this is useful.

Information Hiding

Sometimes, our design for a program would be broken if users start messing around with variables we don’t want them to change.

Robust class design requires consideration of which subroutines are intended for users to use, and which are internal.

Languages provide features to implement this: access control.

In python, we use leading underscores to control whether member variables and methods can be accessed from outside the class:

class MyClass(object):
    def __init__(self):
        self.__private_data=0
        self._private_data=0
        self.public_data=0

    def __private_method(self): pass

    def _private_method(self): pass

    def public_method(self): pass

    def called_inside(self):
        self.__private_method()
        self._private_method()
        self.__private_data=1
        self._private_data=1


MyClass().called_inside()
MyClass()._private_method() # Works, but forbidden by convention
MyClass().public_method() # OK

print(MyClass()._private_data)

0

print(MyClass().public_data)

0

MyClass().__private_method() # Generates error

—————————————————————————

AttributeError Traceback (most recent call last)

in () </span>

—-> 1 MyClass().__private_method() # Generates error

AttributeError: ‘MyClass’ object has no attribute ‘__private_method’

print(MyClass().__private_data) # Generates error

—————————————————————————

AttributeError Traceback (most recent call last)

in () </span>

—-> 1 print(MyClass().__private_data) # Generates error

AttributeError: ‘MyClass’ object has no attribute ‘__private_data’

Property accessors

Python provides a mechanism to make functions appear to be variables. This can be used if you want to change the way a class is implemented without changing the interface:

class Person(object):
    def __init__(self):
        self.name = "Jameel Holt"

assert(Person().name == "Jameel Holt")

becomes:

class Person(object):
    def __init__(self):
        self._first = "Jameel"
        self._second = "Holt"
        self.name = self._first + " " + self._second

    def get_married(self, to):
        self._second = to._second

assert(Person().name == "Jameel Holt")

Making the same external code work as before.

Note that the code behaves the same way to the outside user. The implementation detail is hidden by private variables. In languages without this feature, such as C++, it is best to always make data private, and always access data through functions:

class Person(object):
    def __init__(self):
        self._first = "Jameel"
        self._second = "Holt"


    @property
    def name(self):
        return self._first + " " + self._second

assert(Person().name == "Jameel Holt")

But in Python this is unnecessary because the @property capability.

Another way could be to create a member variable name which holds the full name. However, this could lead to inconsistent data. If we create a get_married function, then the name of the person won’t change!

class Person(object):
    def __init__(self, first, second):
        self._first = first
        self._second = second
        self.name = f"{self._first} {self._second}"

    def get_married(self, to):
        self._second = to._second

graham = Person("Graham", "Chapman")
david = Person("David", "Sherlock")
assert(graham.name == "Graham Chapman")
graham.get_married(david)
assert(graham.name == "Graham Sherlock")

—————————————————————————

AssertionError Traceback (most recent call last)

in () </span>

12 assert(graham.name == “Graham Chapman”)

13 graham.get_married(david)

—> 14 assert(graham.name == “Graham Sherlock”)

AssertionError:

This type of situation could makes that the object data structure gets inconsistent with itself. Making variables being out of sync with other variables. Each piece of information should only be stored in once place! In this case, name should be calculated each time it’s required as previously shown. In database design, this is called Normalisation.

UML for private/public

We prepend a +/- on public/private member variables and methods:

yuml("[Particle|+public;-private|+publicmethod();-privatemethod]")

YUML model - public and private

Class Members

Class, or static members, belong to the class as a whole, and are shared between instances.

class Counted(object):
    number_created=0

    def __init__(self):
        Counted.number_created+=1

    @classmethod
    def howMany(cls):
        return cls.number_created

Counted.howMany() # 0
x=Counted()
Counted.howMany() # 1
z=[Counted() for x in range(5)]
Counted.howMany() # 6

6

The data is shared among all the objects instantiated from that class. Note that in __init__ we are not using self.number_created but the name of the class. The howMany function is not a method of a particular object. It’s called on the class, not on the object. This is possible by using the @classmethod decorator.

Next: Reading - Inheritance and Polymorphism