+ -
Notes for current slide
Notes for next slide

5CCYB041

OBJECT-ORIENTED PROGRAMMING

Week 7, session 1

OOP design and principles

1

Exercises

We have now covered all the material necessary to finish the robot arm project

You can find the most up to date version in the project's solution/ folder

Modify your code to implement the remaining steps:

  1. load the parameter file (including all relevant error checking)
  2. set the various angles to their corresponding values in the parameter file for each time frame
  3. iterate over the time frames, compute the trajectory, and display on the terminal
  4. compute the speed of the tip and display this as a function of time
  5. compute the acceleration of the tip and display this alongside the speed
  6. compute the maximum speed and acceleration and check whether they are within the specified safety margins

Refer to the online solution for the final design

2

OOP core principles

3

OOP core principles

Object-Oriented Programming is based on 4 core principles:

  • abstraction
  • encapsulation
  • inheritance
  • polymorphism
4

OOP core principles

Object-Oriented Programming is based on 4 core principles:

  • abstraction
  • encapsulation
  • inheritance
  • polymorphism

We have already been using all 4 of these principles throughout the course

⇒ it's time to formalise what they mean

4

Abstraction

5

Abstraction

Abstraction aims to provide a simplified interface to otherwise complex systems.

  • this allows us to focus on how to use these abstractions to solve problems
  • without worrying about the details of how these abstractions are implemented
6

Abstraction

Abstraction aims to provide a simplified interface to otherwise complex systems.

  • this allows us to focus on how to use these abstractions to solve problems
  • without worrying about the details of how these abstractions are implemented

We use abstractions all the time:

  • cars provides a uniform and familiar interface to the driver
    • regardless of the type of engine, gearbox, etc.
6

Abstraction

Abstraction aims to provide a simplified interface to otherwise complex systems.

  • this allows us to focus on how to use these abstractions to solve problems
  • without worrying about the details of how these abstractions are implemented

We use abstractions all the time:

  • cars provides a uniform and familiar interface to the driver
    • regardless of the type of engine, gearbox, etc.
  • TVs can be operated by remote control
    • without the need to understand whether this is a plasma or LCD screen, whether the signal comes over the air, via cable, or the internet, etc.
6

Abstraction

Abstraction aims to provide a simplified interface to otherwise complex systems.

  • this allows us to focus on how to use these abstractions to solve problems
  • without worrying about the details of how these abstractions are implemented

We use abstractions all the time:

  • cars provides a uniform and familiar interface to the driver
    • regardless of the type of engine, gearbox, etc.
  • TVs can be operated by remote control
    • without the need to understand whether this is a plasma or LCD screen, whether the signal comes over the air, via cable, or the internet, etc.
  • a coffee machine can be operated by providing water and coffee beans, then pushing the right button
    • without needing to worry about the temperature and pressure of the water, the coarseness of the grind, etc.
6

Abstraction

Abstraction aims to provide a simplified interface to otherwise complex systems.

  • this allows us to focus on how to use these abstractions to solve problems
  • without worrying about the details of how these abstractions are implemented

We use abstractions all the time:

  • cars provides a uniform and familiar interface to the driver
    • regardless of the type of engine, gearbox, etc.
  • TVs can be operated by remote control
    • without the need to understand whether this is a plasma or LCD screen, whether the signal comes over the air, via cable, or the internet, etc.
  • a coffee machine can be operated by providing water and coffee beans, then pushing the right button
    • without needing to worry about the temperature and pressure of the water, the coarseness of the grind, etc.

These are all examples of abstraction

6

Abstraction in OOP

Abstraction can be achieved in C++ by providing intuitive interfaces to potentially complex objects

The most common manifestation is the use of classes with methods

7

Abstraction in OOP

Abstraction can be achieved in C++ by providing intuitive interfaces to potentially complex objects

The most common manifestation is the use of classes with methods

Example, the Image class that we set up for the fMRI project:

  • this provides an intuitive way to access individual pixels by providing their coordinates
  • even though the pixels are stored in a one-dimensional std::vector internally
7

Abstraction in OOP

Abstraction can be achieved in C++ by providing intuitive interfaces to potentially complex objects

The most common manifestation is the use of classes with methods

Example, the Image class that we set up for the fMRI project:

  • this provides an intuitive way to access individual pixels by providing their coordinates
  • even though the pixels are stored in a one-dimensional std::vector internally

The various segments in the robot arm projects also provide a level of abstraction

  • the tip_position() method provides the position without the need to worry about how it was computed
7

Abstraction in OOP

Abstraction can be achieved in C++ by providing intuitive interfaces to potentially complex objects

The most common manifestation is the use of classes with methods

Example, the Image class that we set up for the fMRI project:

  • this provides an intuitive way to access individual pixels by providing their coordinates
  • even though the pixels are stored in a one-dimensional std::vector internally

The various segments in the robot arm projects also provide a level of abstraction

  • the tip_position() method provides the position without the need to worry about how it was computed

But functions can also provide abstractions:

  • we don't need to worry about how to load a PGM file if we know how to use the load_pgm() function
7

Encapsulation

8

Encapsulation

Encapsulation aims to maintain the integrity of a system, by protecting its internal components and preventing direct manipulation other than through the interface provided

  • it is closely related to abstraction, but more focussed on maintaining integrity
  • this allows us to use complex objects without worrying about causing accidental damage
9

Encapsulation

Encapsulation aims to maintain the integrity of a system, by protecting its internal components and preventing direct manipulation other than through the interface provided

  • it is closely related to abstraction, but more focussed on maintaining integrity
  • this allows us to use complex objects without worrying about causing accidental damage

Again, there are plenty of everyday examples of encapsulation:

  • many electrical devices use non-standard, sometimes hidden screws to prevent users from trying to 'fix' their device
  • modern smartphones are locked down to prevent malicious or accidental damage to the operating system
  • trying to open up your laptop will most likely void your warranty...
9

Encapsulation

Encapsulation aims to maintain the integrity of a system, by protecting its internal components and preventing direct manipulation other than through the interface provided

  • it is closely related to abstraction, but more focussed on maintaining integrity
  • this allows us to use complex objects without worrying about causing accidental damage

Again, there are plenty of everyday examples of encapsulation:

  • many electrical devices use non-standard, sometimes hidden screws to prevent users from trying to 'fix' their device
  • modern smartphones are locked down to prevent malicious or accidental damage to the operating system
  • trying to open up your laptop will most likely void your warranty...

All of these are examples of encapsulation:

  • they aim to ensure the integrity of an object or system
9

Encapsulation in OOP

Encapsulation can be achieved in C++ by protecting the internal state of objects

The most common way is to use classes, storing the state of the object as private or protected attributes, along with suitably designed public methods to interact with these attributes safely

  • these methods need to provide both the abstraction and ensure the integrity of the data in the class
10

Encapsulation in OOP

Encapsulation can be achieved in C++ by protecting the internal state of objects

The most common way is to use classes, storing the state of the object as private or protected attributes, along with suitably designed public methods to interact with these attributes safely

  • these methods need to provide both the abstraction and ensure the integrity of the data in the class

For example, consider our Image class:

  • its state consists of the image dimensions (integers) and the vector of intensities
  • what if we wanted to resize an existing image?
  • could we just set the image dimensions to the desired size?
10

Encapsulation in OOP

Encapsulation can be achieved in C++ by protecting the internal state of objects

The most common way is to use classes, storing the state of the object as private or protected attributes, along with suitably designed public methods to interact with these attributes safely

  • these methods need to provide both the abstraction and ensure the integrity of the data in the class

For example, consider our Image class:

  • its state consists of the image dimensions (integers) and the vector of intensities
  • what if we wanted to resize an existing image?
  • could we just set the image dimensions to the desired size?

⇒ no, since changing the image dimensions changes the number of pixels

  • the vector of intensities would need to be resized to match!
  • what if we wanted to preserve any intensities already present in the image?
10

Encapsulation in OOP

Encapsulation can be achieved in C++ by protecting the internal state of objects

The most common way is to use classes, storing the state of the object as private or protected attributes, along with suitably designed public methods to interact with these attributes safely

  • these methods need to provide both the abstraction and ensure the integrity of the data in the class

For example, consider our Image class:

  • its state consists of the image dimensions (integers) and the vector of intensities
  • what if we wanted to resize an existing image?
  • could we just set the image dimensions to the desired size?

⇒ no, since changing the image dimensions changes the number of pixels

  • the vector of intensities would need to be resized to match!
  • what if we wanted to preserve any intensities already present in the image?

The current implementation protects these attributes, ensuring they cannot be modified other than through the methods provided.

10

Inheritance

11

Inheritance

Inheritance allows one class to derive from and extent another class, allowing strongly related objects to share common attributes and methods

  • it allows for code re-use, simplifying the implementation and maintenance of shared functionality
  • it is used to implement multiple sub-types that share a common set of features
12

Inheritance

Inheritance allows one class to derive from and extent another class, allowing strongly related objects to share common attributes and methods

  • it allows for code re-use, simplifying the implementation and maintenance of shared functionality
  • it is used to implement multiple sub-types that share a common set of features

Examples of inheritance include:

  • cars that share a common chassis and engine, but can be customised with different bodywork, furnishings, and extras
    • e.g. Volkswagen Golf, Audi A3, and Skoda Octavia
12

Inheritance

Inheritance allows one class to derive from and extent another class, allowing strongly related objects to share common attributes and methods

  • it allows for code re-use, simplifying the implementation and maintenance of shared functionality
  • it is used to implement multiple sub-types that share a common set of features

Examples of inheritance include:

  • cars that share a common chassis and engine, but can be customised with different bodywork, furnishings, and extras
    • e.g. Volkswagen Golf, Audi A3, and Skoda Octavia
  • laptops are often designed as a series, with a range of models sharing common attributes (chassis, screen, battery, etc), but different CPUs, memory, storage, etc.
12

Inheritance

Inheritance allows one class to derive from and extent another class, allowing strongly related objects to share common attributes and methods

  • it allows for code re-use, simplifying the implementation and maintenance of shared functionality
  • it is used to implement multiple sub-types that share a common set of features

Examples of inheritance include:

  • cars that share a common chassis and engine, but can be customised with different bodywork, furnishings, and extras
    • e.g. Volkswagen Golf, Audi A3, and Skoda Octavia
  • laptops are often designed as a series, with a range of models sharing common attributes (chassis, screen, battery, etc), but different CPUs, memory, storage, etc.

These are examples of making use of a common base, which can then be extended in different ways for specific products

  • this means developpers can optimise a single shared base, and spend more time on the features specific to each product
12

Inheritance in OOP

Inheritance in achieved in OOP by allowing a base class to be inherited by one or more derived classes

  • The data members of the base class are also present in any derived class

    • The derived class is a realisation of the base class
  • The methods of the base class are also available to the derived class and external code

    • depending on the level of access provided and mode of inheritance
  • Derived classes can extend the base class by adding more data members and providing additional functionality

  • Derived class can also provide their own implementation of some of the methods of the base class

13

Inheritance in OOP

Inheritance in achieved in OOP by allowing a base class to be inherited by one or more derived classes

  • The data members of the base class are also present in any derived class

    • The derived class is a realisation of the base class
  • The methods of the base class are also available to the derived class and external code

    • depending on the level of access provided and mode of inheritance
  • Derived classes can extend the base class by adding more data members and providing additional functionality

  • Derived class can also provide their own implementation of some of the methods of the base class

We have seen this in action in our robot arm project

13

Polymorphism

14

Polymorphism

The word comes from Greek, and means 'many forms'

Polymorphism refers to the ability to define different objects that provide the same interface

  • it allows us to focus on using the object, rather than worrying about how this is implemented
15

Polymorphism

The word comes from Greek, and means 'many forms'

Polymorphism refers to the ability to define different objects that provide the same interface

  • it allows us to focus on using the object, rather than worrying about how this is implemented

There are many day-to-day examples of polymorphism:

  • cars all have a steering wheel and pedals
    • even though what these do may be implemented completely differently for different types of car
15

Polymorphism

The word comes from Greek, and means 'many forms'

Polymorphism refers to the ability to define different objects that provide the same interface

  • it allows us to focus on using the object, rather than worrying about how this is implemented

There are many day-to-day examples of polymorphism:

  • cars all have a steering wheel and pedals
    • even though what these do may be implemented completely differently for different types of car
  • bank accounts all have one or more account holders, balance, history of previous transactions, etc.
    • but come in different variants: current, savings, investment, mortgage, ...
15

Polymorphism

The word comes from Greek, and means 'many forms'

Polymorphism refers to the ability to define different objects that provide the same interface

  • it allows us to focus on using the object, rather than worrying about how this is implemented

There are many day-to-day examples of polymorphism:

  • cars all have a steering wheel and pedals
    • even though what these do may be implemented completely differently for different types of car
  • bank accounts all have one or more account holders, balance, history of previous transactions, etc.
    • but come in different variants: current, savings, investment, mortgage, ...
  • many numerical problems can be represented as a cost function, which computes a cost for a given set of input parameters
    • solving these problems can be done by optimisation: finding the set of parameters that minimises the output cost function value
    • ... even though the internal calculations of the cost function may be completely different, and will not be known to the optimisation algorithm
15

Polymorphism

The word comes from Greek, and means 'many forms'

Polymorphism refers to the ability to define different objects that provide the same interface

  • it allows us to focus on using the object, rather than worrying about how this is implemented

There are many day-to-day examples of polymorphism:

  • cars all have a steering wheel and pedals
    • even though what these do may be implemented completely differently for different types of car
  • bank accounts all have one or more account holders, balance, history of previous transactions, etc.
    • but come in different variants: current, savings, investment, mortgage, ...
  • many numerical problems can be represented as a cost function, which computes a cost for a given set of input parameters
    • solving these problems can be done by optimisation: finding the set of parameters that minimises the output cost function value
    • ... even though the internal calculations of the cost function may be completely different, and will not be known to the optimisation algorithm

These all involve objects with a known, common interface, but different implementations

15

Polymorphism in OOP

C++ supports different types of polymorphism:

Compile-time or static polymorphism

In this form, a common interface is defined in the (human-readable) C++ code, and the compiler deduces which implementation to use when generating the binary code

16

Polymorphism in OOP

C++ supports different types of polymorphism:

Compile-time or static polymorphism

In this form, a common interface is defined in the (human-readable) C++ code, and the compiler deduces which implementation to use when generating the binary code

Run-time or dynamic polymorphism

In this form, a common interface is defined in the C++ code, and also in the generated binary code. The compiler implements the necessary machinery to ensure that the program can work with any object that provides the expected interface, but cannot make assumptions regarding which specific subtype might be provided at any point in time while running.

The decision as to which specific implementation is to be used is therefore made while the program is running: dynamically, at run-time.

16

Compile-time polymorphism

C++ provides two main ways to achieve compile-time polymorphism:

Function overloading

We can provide multiple functions with the same name, but different arguments

void display (int x) { std::cout << x << "\n"; }
void display (double d) { std::cout << d << "\n"; }
void display (const std::string& s) { std::cout << s << "\n"; }
...
double val = 10.2;
display (val); // <= the compiler can deduce which version to invoke here
...
std::string mesg = "not good!";
display (mesg); // <= the compiler can deduce which different version to use here
17

Compile-time polymorphism

C++ provides two main ways to achieve compile-time polymorphism:

Template functions

We can specify a function as taking one or more generic argument types

  • When encountered in the code, the compiler can then substitute the type provided to generate the code for the function
template <typename X>
void display (const X& x) { std::cout << x << "\n"; }
...
double val = 10.2;
display (val); // <= the compiler can substitute 'double' instead of 'X' here
...
std::string mesg = "not good!";
display (mesg); // <= the compiler can substitute 'std::string' instead of 'X' here
18

Compile-time polymorphism

C++ provides two main ways to achieve compile-time polymorphism:

Template classes

We can also create template classes in this way:

template <typename X>
class Complex {
public:
void display () const { std::cout << "(" << real << "," << imag << ")\n";
...
private:
X real, imag;
};
Complex<int> ci { 3, 2 };
Complex<double> cd { 1.0, 0.5 };
19

Run-time polymorphism

In C++, runtime polymorphism is typically achieved using inheritance and virtual functions.

20

Run-time polymorphism

In C++, runtime polymorphism is typically achieved using inheritance and virtual functions.

We define a base class to specify the expected interface

  • we specify which methods (behaviours) can be overridden using the virtual keyword
20

Run-time polymorphism

In C++, runtime polymorphism is typically achieved using inheritance and virtual functions.

We define a base class to specify the expected interface

  • we specify which methods (behaviours) can be overridden using the virtual keyword

We can then define derived classes that inherit from the base class

  • they will provide the interface specified in the base class
  • but can also extend it with additional methods & behaviours specific to their role
  • they can also override any methods marked virtual and provide their own implementation
20

Run-time polymorphism

In C++, runtime polymorphism is typically achieved using inheritance and virtual functions.

We define a base class to specify the expected interface

  • we specify which methods (behaviours) can be overridden using the virtual keyword

We can then define derived classes that inherit from the base class

  • they will provide the interface specified in the base class
  • but can also extend it with additional methods & behaviours specific to their role
  • they can also override any methods marked virtual and provide their own implementation

Which implementation to invoke will be determined at runtime, at the point of use, based on the specific type of the object being handled at that time

  • The compiler knows all derived classes provide the interface specified in the base class, so can generate code to process any object of type base, and that code can then also be used with any derived types
20

Run-time polymorphism

In C++, runtime polymorphism is typically achieved using inheritance and virtual functions.

We define a base class to specify the expected interface

  • we specify which methods (behaviours) can be overridden using the virtual keyword

We can then define derived classes that inherit from the base class

  • they will provide the interface specified in the base class
  • but can also extend it with additional methods & behaviours specific to their role
  • they can also override any methods marked virtual and provide their own implementation

Which implementation to invoke will be determined at runtime, at the point of use, based on the specific type of the object being handled at that time

  • The compiler knows all derived classes provide the interface specified in the base class, so can generate code to process any object of type base, and that code can then also be used with any derived types
  • For this to work, we have to use references, pointers or iterators that are declared to refer to objects of base class types, but actually refer to instances of derived type
20

Run-time polymorphism

For example, let's design a class to represent a generic numerical problem

  • i.e. any problem that can be expressed as a function of a parameter vector, and for which a cost can be computed
  • the solution is the parameter vector that minimises the cost
class ProblemBase {
public:
virtual int size () const = 0;
virtual double eval (const std::vector<double>& x) const = 0;
};
21

Run-time polymorphism

For example, let's design a class to represent a generic numerical problem

  • i.e. any problem that can be expressed as a function of a parameter vector, and for which a cost can be computed
  • the solution is the parameter vector that minimises the cost
class ProblemBase {
public:
virtual int size () const = 0;
virtual double eval (const std::vector<double>& x) const = 0;
};


We can then write a function to minimise any problem defined in this way:

std::vector<double> minimise (const ProblemBase& problem);

which could use some suitable optimisation algorithm to identify and return the optimal parameter vector.

21

Run-time polymorphism

One such problem might be to fit an exponential: S=x0exp(x1t) to a set of measurements of acquired at various times.

The task is to identify the parameters of the curve that minimise the sum of squared differences between the fitted curve and the measurements.

22

Run-time polymorphism

We can then represent our exponential fitting problem by deriving from ProblemBase:

class Exponential : public ProblemBase {
public:
Exponential (const std::vector<double>& measurements,
const std::vector<double>& timepoints) :
m (measurements), t (timepoints) { }
int size () const override { return 2; }
double eval (const std::vector<double>& x) const override {
double cost = 0.0;
for (int n = 0; n < m.size(); n++) {
double diff = x[0] * std::exp (x[1]*t[n]) - m[n];
cost += diff*diff;
}
return cost;
}
private:
std::vector<double> m, t;
};
23

Run-time polymorphism

We can then represent our exponential fitting problem by deriving from ProblemBase:

class Exponential : public ProblemBase {
public:
Exponential (const std::vector<double>& measurements,
const std::vector<double>& timepoints) :
m (measurements), t (timepoints) { }
int size () const override { return 2; }
double eval (const std::vector<double>& x) const override {
double cost = 0.0;
for (int n = 0; n < m.size(); n++) {
double diff = x[0] * std::exp (x[1]*t[n]) - m[n];
cost += diff*diff;
}
return cost;
}
private:
std::vector<double> m, t;
};

The problem is characterised by 2 parameters: the amplitude and decay rate S=x0exp(x1t)

24

Run-time polymorphism

We can then represent our exponential fitting problem by deriving from ProblemBase:

class Exponential : public ProblemBase {
public:
Exponential (const std::vector<double>& measurements,
const std::vector<double>& timepoints) :
m (measurements), t (timepoints) { }
int size () const override { return 2; }
double eval (const std::vector<double>& x) const override {
double cost = 0.0;
for (int n = 0; n < m.size(); n++) {
double diff = x[0] * std::exp (x[1]*t[n]) - m[n];
cost += diff*diff;
}
return cost;
}
private:
std::vector<double> m, t;
};

The eval() call evaluates the goodness of fit: ϕ=n(x0exp(x1tn)mn)2 In other words, the sum of squares difference between measurements mn at specified times tn and corresponding predictions from the exponential model parameterised by amplitude x0 and exponent x1

25

Run-time polymorphism

We can then represent our exponential fitting problem by deriving from ProblemBase:

class Exponential : public ProblemBase {
public:
Exponential (const std::vector<double>& measurements,
const std::vector<double>& timepoints) :
m (measurements), t (timepoints) { }
int size () const override { return 2; }
double eval (const std::vector<double>& x) const override {
double cost = 0.0;
for (int n = 0; n < m.size(); n++) {
double diff = x[0] * std::exp (x[1]*t[n]) - m[n];
cost += diff*diff;
}
return cost;
}
double eval (const std::vector<double>& x) const override {
private:
std::vector<double> m, t;
};

We can provide the measurements and their corresponding time points in the constructor

26

Run-time polymorphism

Similarly, we could use this approach to solve any other problem of interest that can be expressed as a cost that depends on a vector of parameters.

  • the placement of sensors to optimise sensitivity to a signal of interest
  • optimising the parameters of a machine learning algorithm
  • finding the transformation that best aligns two images
  • ...
27

Run-time polymorphism

Similarly, we could use this approach to solve any other problem of interest that can be expressed as a cost that depends on a vector of parameters.

  • the placement of sensors to optimise sensitivity to a signal of interest
  • optimising the parameters of a machine learning algorithm
  • finding the transformation that best aligns two images
  • ...

Runtime polymorphism is useful when the system needs to interact with a generic type of object, but the specific type cannot be known at runtime, or may change at runtime.

  • the base class can be used to specify the interface
  • derived classes can provide specific implementations suitable for each sub-type
27

Relationships between classes

28

Relationships between classes

A fundamental aspect of OOP design is to:

  • break up a problem into distinct objects
  • identify the relationships between these objects
29

Relationships between classes

A fundamental aspect of OOP design is to:

  • break up a problem into distinct objects
  • identify the relationships between these objects

When designing an OOP solution, the first challenge is to identify which aspects of the problem should be represented as distinct classes

29

Relationships between classes

A fundamental aspect of OOP design is to:

  • break up a problem into distinct objects
  • identify the relationships between these objects

When designing an OOP solution, the first challenge is to identify which aspects of the problem should be represented as distinct classes

The next challenge is to specify how these classes and objects relate to each other. There are different types of relationships in OOP, including notably:

  • dependency
  • association
    • aggregation
    • composition
  • inheritance
29

Dependency

Dependency is the most 'loose' form of relationship

Objects may 'know' about each other, but are otherwise independent.

  • it may be used to represent "uses-a" relationships

30

Dependency

Dependency is the most 'loose' form of relationship

Objects may 'know' about each other, but are otherwise independent.

  • it may be used to represent "uses-a" relationships

For example, a class to load and store a matrix of data would depend on the std::string and the std::ifstream classes

  • std::string is used to provide the filename
  • std::ifstream might be used internally in the class' load() method
30

Dependency

Dependency is the most 'loose' form of relationship

Objects may 'know' about each other, but are otherwise independent.

  • it may be used to represent "uses-a" relationships

For example, a class to load and store a matrix of data would depend on the std::string and the std::ifstream classes

  • std::string is used to provide the filename
  • std::ifstream might be used internally in the class' load() method

More generally, classes A & B can be said to be in a dependency relationship if class A is only used in class B's method(s) (whether as arguments or local variables), but not in a more persistent manner.

30

Association: aggregation

Aggregation implies a weak association between two classes

This applies in situations where one class holds a reference to another, but does not otherwise 'own' it

  • it is said to represent "has-a" relationships

31

Association: aggregation

Aggregation implies a weak association between two classes

This applies in situations where one class holds a reference to another, but does not otherwise 'own' it

  • it is said to represent "has-a" relationships

For example, a student has-a university, and vice-versa, but neither 'own' each other

  • this can be considered from the point of view of lifetime: closing down the university will not automatically terminate the student (or vice-versa)
  • it is also possible for the student to drop out of university, or change to a different university
31

Association: aggregation

More examples of aggregation:

  • Company has-a Employee (in fact, has-many Employees)
    • Employees exist even if Company closes
    • An Employee can be employed in multiple Companys
32

Association: aggregation

More examples of aggregation:

  • Company has-a Employee (in fact, has-many Employees)
    • Employees exist even if Company closes
    • An Employee can be employed in multiple Companys
  • Airplane has-a Passenger (again, has-many Passengers)
    • Decommissioning Airplane does not imply Passengers need to be terminated
    • Here, Passenger can only be on one Airplane at a time
32

Association: aggregation

More examples of aggregation:

  • Company has-a Employee (in fact, has-many Employees)
    • Employees exist even if Company closes
    • An Employee can be employed in multiple Companys
  • Airplane has-a Passenger (again, has-many Passengers)
    • Decommissioning Airplane does not imply Passengers need to be terminated
    • Here, Passenger can only be on one Airplane at a time
  • Computer has-a Keyboard
    • Computer can exist without a keyboard
    • Keyboard can be removed, replaced, etc.
32

Association: aggregation

More examples of aggregation:

  • Company has-a Employee (in fact, has-many Employees)
    • Employees exist even if Company closes
    • An Employee can be employed in multiple Companys
  • Airplane has-a Passenger (again, has-many Passengers)
    • Decommissioning Airplane does not imply Passengers need to be terminated
    • Here, Passenger can only be on one Airplane at a time
  • Computer has-a Keyboard
    • Computer can exist without a keyboard
    • Keyboard can be removed, replaced, etc.


Aggregation can be unidirectional or bidirectional

  • a Company has-a Employee, but it's also possible that an Employee has-a Company
  • a Computer has-a Keyboard, but there is no need to say that the Keyboard has-a Computer...
32

Association: composition

Composition is implies a stronger association between two classes

This applies in situations where one class is made up of other objects, including the other class under consideration

  • it can be said to represent "is-made-of" relationships

33

Association: composition

Composition is implies a stronger association between two classes

This applies in situations where one class is made up of other objects, including the other class under consideration

  • it can be said to represent "is-made-of" relationships

For example, a car is-made-of a chassis, engine, 4 wheels, etc.

  • composition implies ownership: one object contains and manages the other
  • it also implies the lifetime of the contained object is tied to that of the container object
33

Association: composition

Composition is implies a stronger association between two classes

This applies in situations where one class is made up of other objects, including the other class under consideration

  • it can be said to represent "is-made-of" relationships

For example, a car is-made-of a chassis, engine, 4 wheels, etc.

  • composition implies ownership: one object contains and manages the other
  • it also implies the lifetime of the contained object is tied to that of the container object

Composition can only be unidirectional

  • the container object is responsible for the contained object
  • the contained object will (typically) have no knowledge of or reference to its container
33

Association: composition

More examples of composition:

  • a Building is-made-of (multiple) Rooms
    • Room cannot exist outside of a Building
34

Association: composition

More examples of composition:

  • a Building is-made-of (multiple) Rooms
    • Room cannot exist outside of a Building
  • a Book is-made-of (many) Pages
    • Here, relationship is one of lifetime: if Book is destroyed, so are its Pages
34

Association: composition

More examples of composition:

  • a Building is-made-of (multiple) Rooms
    • Room cannot exist outside of a Building
  • a Book is-made-of (many) Pages
    • Here, relationship is one of lifetime: if Book is destroyed, so are its Pages
  • Car is-made-of Motor (amongst other parts!)
    • this is a more contentious example, depending on context
    • Motor can be taken out of Car, replaced, etc
    • But Car is not complete without Motor, and Motor is (usually!) destroyed if Car is destroyed
    • ⇒ this reflects the intention of the designer: a Car shall be made-of a Motor
34

Inheritance

Inheritance (also known as generalisation) is the strongest form of relationship

This applies in situations where one class is a specialisation of another, more general class

  • it can be said to represent "is-a" relationships

35

Inheritance

Example of inheritance include:

  • a computer screen is-a general type of display, and the computer will need to know how to interact with it in a generic way
    • but actual computer screens may use different technologies (LCD, LED, plasma, CRT, ...)
    • or use different connectors (VGA, DisplayPort, HDMI, Thunderbolt, ...)
    • and provide different native resolutions and refresh rates, etc.
36

Inheritance

Example of inheritance include:

  • a computer screen is-a general type of display, and the computer will need to know how to interact with it in a generic way
    • but actual computer screens may use different technologies (LCD, LED, plasma, CRT, ...)
    • or use different connectors (VGA, DisplayPort, HDMI, Thunderbolt, ...)
    • and provide different native resolutions and refresh rates, etc.
  • Dog is-a Animal
    • So is Cat, Mouse, Pangolin, ...
36

Inheritance

Example of inheritance include:

  • a computer screen is-a general type of display, and the computer will need to know how to interact with it in a generic way
    • but actual computer screens may use different technologies (LCD, LED, plasma, CRT, ...)
    • or use different connectors (VGA, DisplayPort, HDMI, Thunderbolt, ...)
    • and provide different native resolutions and refresh rates, etc.
  • Dog is-a Animal
    • So is Cat, Mouse, Pangolin, ...
  • 4WheelDrive is-a Car
    • So is FrontWheelDrive, RearWheelDrive, Formula1, ...
36

Inheritance

Example of inheritance include:

  • a computer screen is-a general type of display, and the computer will need to know how to interact with it in a generic way
    • but actual computer screens may use different technologies (LCD, LED, plasma, CRT, ...)
    • or use different connectors (VGA, DisplayPort, HDMI, Thunderbolt, ...)
    • and provide different native resolutions and refresh rates, etc.
  • Dog is-a Animal
    • So is Cat, Mouse, Pangolin, ...
  • 4WheelDrive is-a Car
    • So is FrontWheelDrive, RearWheelDrive, Formula1, ...
  • BluetoothMouse is-a Mouse
    • So is USBMouse, PS/2Mouse, TouchPad, TouchScreen, ...
36

Inheritance

Inheritance implies that derived classes are full instances of the class they inherit from

  • it is a stronger relationship than ownership
  • the lifetime of the base object is obviously tied to that of the derived object
    • the 'base' object is a part of the derived object
  • inheritance can only be unidirectional
37

Inheritance

Inheritance implies that derived classes are full instances of the class they inherit from

  • it is a stronger relationship than ownership
  • the lifetime of the base object is obviously tied to that of the derived object
    • the 'base' object is a part of the derived object
  • inheritance can only be unidirectional

Derived classes extend the functionality of the base class

37

Inheritance

Inheritance implies that derived classes are full instances of the class they inherit from

  • it is a stronger relationship than ownership
  • the lifetime of the base object is obviously tied to that of the derived object
    • the 'base' object is a part of the derived object
  • inheritance can only be unidirectional

Derived classes extend the functionality of the base class

The Base class will have no 'knowledge' of or dependence on the derived classes

37

Inheritance

Inheritance implies that derived classes are full instances of the class they inherit from

  • it is a stronger relationship than ownership
  • the lifetime of the base object is obviously tied to that of the derived object
    • the 'base' object is a part of the derived object
  • inheritance can only be unidirectional

Derived classes extend the functionality of the base class

The Base class will have no 'knowledge' of or dependence on the derived classes

Objects of a derived type can be used anywhere the Base type is expected

37

Inheritance

Inheritance implies that derived classes are full instances of the class they inherit from

  • it is a stronger relationship than ownership
  • the lifetime of the base object is obviously tied to that of the derived object
    • the 'base' object is a part of the derived object
  • inheritance can only be unidirectional

Derived classes extend the functionality of the base class

The Base class will have no 'knowledge' of or dependence on the derived classes

Objects of a derived type can be used anywhere the Base type is expected

... but objects of the Base type cannot be used where a specific derived type is expected

37

OOP design

38

OOP design

Object-Oriented Design (OOD) is a method of designing software by conceptualizing it as a group of interacting objects, each representing an instance of a class.

39

OOP design

Object-Oriented Design (OOD) is a method of designing software by conceptualizing it as a group of interacting objects, each representing an instance of a class.

These objects encapsulate both data (attributes) and behavior (methods).

39

OOP design

Object-Oriented Design (OOD) is a method of designing software by conceptualizing it as a group of interacting objects, each representing an instance of a class.

These objects encapsulate both data (attributes) and behavior (methods).

The aim of Object-Oriented Design is to create a system with the following properties:

  • Modularity: Breaks down complex problems into manageable pieces.
  • Reusability: Promotes reuse of existing components.
  • Scalability: Makes it easier to extend and scale software.
  • Maintainability: Enhances code readability and maintainability.
39

OOP design

Object-Oriented Design (OOD) is a method of designing software by conceptualizing it as a group of interacting objects, each representing an instance of a class.

These objects encapsulate both data (attributes) and behavior (methods).

The aim of Object-Oriented Design is to create a system with the following properties:

  • Modularity: Breaks down complex problems into manageable pieces.
  • Reusability: Promotes reuse of existing components.
  • Scalability: Makes it easier to extend and scale software.
  • Maintainability: Enhances code readability and maintainability.


The OOP design process involves:

  • Understanding the project brief and figuring out all the requirements
  • mapping out classes that best represent the different components of the problem
  • working out the relationships between these different classes
  • writing out code that matches the design
39

The OOP design process

When writing out an OOP design, we need to state:

  • what is the purpose or responsibility of each class?
40

The OOP design process

When writing out an OOP design, we need to state:

  • what is the purpose or responsibility of each class?
  • what data will each class need to encapsulate?
40

The OOP design process

When writing out an OOP design, we need to state:

  • what is the purpose or responsibility of each class?
  • what data will each class need to encapsulate?
  • what methods or behaviours will each class provide?
    • in other words, what interface or abstraction will it provide?
40

The OOP design process

When writing out an OOP design, we need to state:

  • what is the purpose or responsibility of each class?
  • what data will each class need to encapsulate?
  • what methods or behaviours will each class provide?
    • in other words, what interface or abstraction will it provide?
  • which aspects of this interface need to be polymorphic?
    • in other words, which methods need to be virtual?
    • Should any of them be pure virtual? If so, this class should be labelled as abstract
40

The OOP design process

When writing out an OOP design, we need to state:

  • what is the purpose or responsibility of each class?
  • what data will each class need to encapsulate?
  • what methods or behaviours will each class provide?
    • in other words, what interface or abstraction will it provide?
  • which aspects of this interface need to be polymorphic?
    • in other words, which methods need to be virtual?
    • Should any of them be pure virtual? If so, this class should be labelled as abstract
  • what are the relationships between the different classes?
40

The OOP design process

When writing out an OOP design, we need to state:

  • what is the purpose or responsibility of each class?
  • what data will each class need to encapsulate?
  • what methods or behaviours will each class provide?
    • in other words, what interface or abstraction will it provide?
  • which aspects of this interface need to be polymorphic?
    • in other words, which methods need to be virtual?
    • Should any of them be pure virtual? If so, this class should be labelled as abstract
  • what are the relationships between the different classes?


⇒ Let's look at a few illustrative examples

40

Number representations

We are designing a system to handle mathematics on numbers provided in different forms: real, rational, and complex

  • real numbers can be simply represented as regular floating-point values
  • rational numbers are represented as the ratio of two integer numbers
  • complex numbers are represented as the combination of real and imaginary components, both represented as real numbers

We need to support display and basic operations on these numbers

  • for now, let's focus on just one basic operation: negation

We also need to provide:

  • a way to simplify a rational number
  • a way to get the complex conjugate of a complex number
41

Number representations

Our design might involves:

  • an abstract base class for numbers, specifying the interface that all numbers are expecting to provide. This interface involves (at least) these pure virtual methods:
    • a display() method
    • a negate() method
    • ...
42

Number representations

Our design might involves:

  • an abstract base class for numbers, specifying the interface that all numbers are expecting to provide. This interface involves (at least) these pure virtual methods:
    • a display() method
    • a negate() method
    • ...
  • a set of 3 classes that derive from that base class, and override the implementations for the virtual methods
    • a Real class
    • a Rational class, with an additional simplify() method
    • a Complex class, with an additional conjugate() method
42

Number representations

Our design might involves:

  • an abstract base class for numbers, specifying the interface that all numbers are expecting to provide. This interface involves (at least) these pure virtual methods:
    • a display() method
    • a negate() method
    • ...
  • a set of 3 classes that derive from that base class, and override the implementations for the virtual methods
    • a Real class
    • a Rational class, with an additional simplify() method
    • a Complex class, with an additional conjugate() method
  • the Complex class may also be composed of two Real numbers
42

Number representations

Our design might involves:

  • an abstract base class for numbers, specifying the interface that all numbers are expecting to provide. This interface involves (at least) these pure virtual methods:
    • a display() method
    • a negate() method
    • ...
  • a set of 3 classes that derive from that base class, and override the implementations for the virtual methods
    • a Real class
    • a Rational class, with an additional simplify() method
    • a Complex class, with an additional conjugate() method
  • the Complex class may also be composed of two Real numbers
  • All of these classes will also need to provide getter (accessor) & setter (mutator) methods to get & set the values
42

Number representations

Here what this might look like in C++ (ignoring constructors and getter/setter methods)

class NumberBase {
public:
virtual void display () = 0;
virtual void negate () = 0;
virtual ~NumberBase() { }
};
class Rational : public NumberBase {
public:
void display () override;
void negate () override;
void simplify ();
private:
int m_numerator, m_denominator;
};
class Real : public NumberBase {
public:
void display () override;
void negate () override;
private:
double m_value;
};
class Complex : public NumberBase {
public:
void display () override;
void negate () override;
void conjugate ();
private:
Real m_real, m_imag;
};
43

Medical catheters

A medical catheter manufacturer wishes to store information about all of the catheter types it produces. The following statements summarise the characteristics of catheters:

  • All catheters should have a stiffness (nm-1), a cost (in pounds/pence) and a diameter. Diameters are represented in the French scale, in which 1Fr = 0.33mm. All catheter diameters will be an integer between 3Fr and 34Fr

  • All catheters produced by the manufacturer are either ablation catheters or balloon catheters. Ablation catheters have a power in Watts. Balloon catheters have a balloon size in mm

  • The company manufactures both MR-compatible and non-MR-compatible ablation catheters. MR-compatible ablation catheters consist of a number of segments, although the precise number of segments varies

In the software application, the classes for all types of catheter should be able to display their parameters to the screen

44

Medical catheters

This project brief calls for:

An abstract base class to represent a generic Catheter, with:

  • data members:
    • stiffness (a floating-point value)
    • cost (a floating-point value, or an integer if expressed in pence)
    • diameter (an integer value)
  • a pure virtual method to display the parameters
45

Medical catheters

This project brief calls for:

An abstract base class to represent a generic Catheter, with:

  • data members:
    • stiffness (a floating-point value)
    • cost (a floating-point value, or an integer if expressed in pence)
    • diameter (an integer value)
  • a pure virtual method to display the parameters

A BallonCatheter class derived from Catheter, with:

  • additional data members: ballon size (a floating-point value)
  • methods / behaviours: a concrete implementation of the display method
45

Medical catheters

This project also calls for:

Another abstract class derived from Catheter, to represent an AblationCatheter, with:

  • additional data members: power (a floating-point value)
  • this will also be an abstract class since we need to know which sub-type to implement
    • we will not define the display method in this class, so it will remain pure virtual
46

Medical catheters

This project also calls for:

Another abstract class derived from Catheter, to represent an AblationCatheter, with:

  • additional data members: power (a floating-point value)
  • this will also be an abstract class since we need to know which sub-type to implement
    • we will not define the display method in this class, so it will remain pure virtual

A NonMRCompatibleAblationCatheter class:

  • methods / behaviours: a concrete implementation of the display method
46

Medical catheters

This project also calls for:

Another abstract class derived from Catheter, to represent an AblationCatheter, with:

  • additional data members: power (a floating-point value)
  • this will also be an abstract class since we need to know which sub-type to implement
    • we will not define the display method in this class, so it will remain pure virtual

A NonMRCompatibleAblationCatheter class:

  • methods / behaviours: a concrete implementation of the display method

A MRCompatibleAblationCatheter class:

  • additional data members: number of segments (an integer value)
  • methods / behaviours: a concrete implementation of the display method
46

What this might look like in C++ (ignoring constructors and getter/setter methods):

class Catheter {
public:
virtual void display () const = 0;
virtual ~Catheter () { }
protected:
double m_stiffness;
double m_cost;
int m_diameter;
};
class BallonCatheter : public Catheter {
public:
void display () const override;
private:
double m_balloon_size;
};
class AblationCatheter :
public Catheter {
public:
virtual ~AblationCatheter () { }
protected:
double m_power;
};
class MRCompatibleAblationCatheter :
public AblationCatheter {
public:
void display () const override;
protected:
int m_num_segments;
};
class NonMRCompatibleAblationCatheter :
public AblationCatheter {
public:
void display () const override;
};
47

Numerical optimisation

We are developing software to perform numerical optimisation of any well-behaved continuous function or problem. Problems to be solved will all compute a cost as a function of the parameter vector of a size dependent on the problem. The software will provide implementations of multiple optimisation algorithms, allowing the user to choose the most appropriate approach for their particular problem.

A problem will need to report its number of parameters, and provide a method to compute the cost for a given parameter vector of the stated size.

Optimisation algorithms will all provide methods to:

  • initialise the optimisation with an initial guess for the solution
  • iterate
  • check for convergence.
  • get the solution vector

To start with, the software will provide implementations for 3 derivative-free optimisation algorithms: the Nelder-Mead method; Particle Swarm Optimisation; and the Genetic algorithm

48

Numerical optimisation

This project calls for:

  • an abstract base class to represent a numerical problem: ProblemBase
  • an abstract base class to represent an optimisation algorithm: OptimiserBase
49

Numerical optimisation

This project calls for:

  • an abstract base class to represent a numerical problem: ProblemBase
  • an abstract base class to represent an optimisation algorithm: OptimiserBase

The OptimiserBase class will be inherited by (at least) 3 derived classes:

  • NelderMeadOptimiser
  • ParticleSwarmOptimiser
  • GeneticOptimiser
49

Numerical optimisation

This project calls for:

  • an abstract base class to represent a numerical problem: ProblemBase
  • an abstract base class to represent an optimisation algorithm: OptimiserBase

The OptimiserBase class will be inherited by (at least) 3 derived classes:

  • NelderMeadOptimiser
  • ParticleSwarmOptimiser
  • GeneticOptimiser

The ProblemBase class will be inherited by any problem that software users wish to optimise

  • the software we are developing does not need to provide an implementation
  • but it is nonetheless a good idea to provide a DemoProblem to illustrate the use of the software!
49

Numerical optimisation

The ProblemBase class:

  • purpose: specifies the interface for the types of problems to be optimised
  • no attributes / data members
  • behaviours / methods:
    • a pure virtual size() method to return its number of parameters
    • a pure virtual compute() method to calculate the cost, given a parameter vector as its argument
50

Numerical optimisation

The ProblemBase class:

  • purpose: specifies the interface for the types of problems to be optimised
  • no attributes / data members
  • behaviours / methods:
    • a pure virtual size() method to return its number of parameters
    • a pure virtual compute() method to calculate the cost, given a parameter vector as its argument

The OptimiserBase class:

  • purpose: specifies the interface for the available optimisation algorithms
  • attributes / data members:
    • a reference to the problem to be solved
    • the current best parameter vector
  • behaviours / methods:
    • a pure virtual init() method to initialise the algorithm, providing a reference to the problem to be solved
    • a pure virtual iterate() method to perform the next iteration of the algorithm and update the current best estimate of the parameter vector
    • a pure virtual converged() method to check for convergence
    • a get_solution() method to retrieve the current best estimate
50

Numerical optimisation

The relationship between ProblemBase and OptimiserBase is one of aggregation:

  • OptimiserBase 'has-a' ProblemBase
51

Numerical optimisation

The relationship between ProblemBase and OptimiserBase is one of aggregation:

  • OptimiserBase 'has-a' ProblemBase

The relationship between OptimiserBase and the 3 specific implementations (NelderMeadOptimiser, ParticleSwarmOptimiser, GeneticOptimiser) is one of inheritance:

  • e.g. NelderMeadOptimiser 'is-a' OptimiserBase
51

Numerical optimisation

The relationship between ProblemBase and OptimiserBase is one of aggregation:

  • OptimiserBase 'has-a' ProblemBase

The relationship between OptimiserBase and the 3 specific implementations (NelderMeadOptimiser, ParticleSwarmOptimiser, GeneticOptimiser) is one of inheritance:

  • e.g. NelderMeadOptimiser 'is-a' OptimiserBase

Likewise, the relationship between ProblemBase and any specific problem (notably DemoProblem) is also one of inheritance:

  • DemoProblem 'is-a' ProblemBase
51

Numerical optimisation

The NelderMeadOptimiser class:

  • purpose: provide a concrete realisation of a ProblemBase that implements the Nelder-Mead method
  • attributes / data members:
    • a set of N+1 parameter vectors to represent the internal state of the algorithm
    • a set of reflection, expansion, contraction and shrink coefficients
  • behaviours / methods:
    • an override init() method
    • an override iterate() method
    • an override converged() method
52

Numerical optimisation

The NelderMeadOptimiser class:

  • purpose: provide a concrete realisation of a ProblemBase that implements the Nelder-Mead method
  • attributes / data members:
    • a set of N+1 parameter vectors to represent the internal state of the algorithm
    • a set of reflection, expansion, contraction and shrink coefficients
  • behaviours / methods:
    • an override init() method
    • an override iterate() method
    • an override converged() method

The other optimisers will loop very similar, though the attributes will necessarily differ according to the needs of the specific optimisation algorithm

52

Numerical optimisation

The DemoProblem class:

  • purpose: provide a concrete realisation of ProblemBase in the expected format to demonstrate the use of the software
  • attributes / data members: none required
  • behaviours / methods:
    • an override size() method
    • an override compute() method
53

Numerical optimisation

The DemoProblem class:

  • purpose: provide a concrete realisation of ProblemBase in the expected format to demonstrate the use of the software
  • attributes / data members: none required
  • behaviours / methods:
    • an override size() method
    • an override compute() method

Other problems can be set up by copying DemoProblem and implementing the specific calculations required for their problem

53

Numerical optimisation

class ProblemBase {
public:
virtual int size () const = 0;
virtual double compute (const std::vector<double>& x) const = 0;
};
class OptimiserBase {
public:
OptimiserBase (const ProblemBase& problem) :
m_problem (problem) { }
virtual void init (const std::vector<double>& initial_guess) = 0;
virtual void iterate () = 0;
virtual bool converged () = 0;
const std::vector<double>& get_solution () const { return m_estimate; }
protected:
ProblemBase& m_problem;
std::vector<double> m_estimate;
};
54
class DemoProblem : public ProblemBase {
public:
int size () override { return 5; } // for example...
double compute (const std::vector<double>& x) const override;
};


class NelderMeadOptimiser : public OptimiserBase {
public:
NelderMeadOptimiser (const ProblemBase& problem) :
OptimiserBase (problem) { }
void init (const std::vector<double>& initial_guess) override;
void iterate () override;
bool converged () override;
private:
std::vector<std::vector<double>> m_simplex;
double m_reflection, m_expansion, m_contraction, m_shrink;
};

and similarly for the other optimisers

55

Design principles

56

Design principles

It is difficult to provide general advice about how to engineer an OOP design for a given problem

  • Problems vary enormously in their requirements and structure, and solutions have to match the problem!
57

Design principles

It is difficult to provide general advice about how to engineer an OOP design for a given problem

  • Problems vary enormously in their requirements and structure, and solutions have to match the problem!

Nonetheless, there are some broad principles that can be used to design robust, maintainable, and scalable software.

57

Design principles

It is difficult to provide general advice about how to engineer an OOP design for a given problem

  • Problems vary enormously in their requirements and structure, and solutions have to match the problem!

Nonetheless, there are some broad principles that can be used to design robust, maintainable, and scalable software.

The best known such principles are known as the SOLID principles

  • but there are many others!
57

Design principles

It is difficult to provide general advice about how to engineer an OOP design for a given problem

  • Problems vary enormously in their requirements and structure, and solutions have to match the problem!

Nonetheless, there are some broad principles that can be used to design robust, maintainable, and scalable software.

The best known such principles are known as the SOLID principles

  • but there are many others!

We'll go over a few such principles over The next few slides to give you an idea of what to have in mind when faced with an Object-Oriented Design problem

57

The SOLID principles

The SOLID principles are five widely-used guidelines

The five principles spell out the acronym SOLID:

  1. Single Responsibility Principle
  2. Open/Closed Principle
  3. Liskov’s Substitution Principle
  4. Interface Segregation Principle
  5. Dependency Inversion Principle
58

The SOLID principles

1. Single Responsibility Principle

A class should have one and only one reason to change, meaning that a class should have only one job.

If you find that your class is responsible for distinct aspects of your code, then you should consider splitting the functionality across multiple classes.

59

The SOLID principles

1. Single Responsibility Principle

A class should have one and only one reason to change, meaning that a class should have only one job.

If you find that your class is responsible for distinct aspects of your code, then you should consider splitting the functionality across multiple classes.

For example, if you have a class to manage patient records, but it also has functionality to manage patient appointments, you should consider having separate classes to manage each aspects separately

  • you could then also have a class that provides an interface into both – but its single job would be to provide that interface
59

The SOLID principles

2. Open/Closed Principle

Objects or entities should be open for extension but closed for modification

The idea here is that we should create a design that allows for the addition of functionality, without requiring modification of any code that might be used elsewhere.

60

The SOLID principles

2. Open/Closed Principle

Objects or entities should be open for extension but closed for modification

The idea here is that we should create a design that allows for the addition of functionality, without requiring modification of any code that might be used elsewhere.

For example, imagine our robot arm was written assuming a fixed configuration, with the calculation of the tip position all done as a single function

  • if we wanted to insert another segment, or another type of segment, that would require modification of that one function – which risks introducing errors

A better approach is to use inheritance:

  • we can trivially extend the system with additional types of segments by adding more derived segment classes – without modifying any of the existing code!
  • we can easily define new configurations by using the existing segments and connecting them differently
60

The SOLID principles

3. Liskov Substitution Principle

Subtypes must be substitutable for their base types without altering the correctness of the program.

Essentially, this means that derived classes should not behave in unexpected ways

  • with respect to their base class
61

The SOLID principles

3. Liskov Substitution Principle

Subtypes must be substitutable for their base types without altering the correctness of the program.

Essentially, this means that derived classes should not behave in unexpected ways

  • with respect to their base class

For example, imagine we have a base class representing a general 3D shape, with a virtual method get_volume() to obtain its volume

  • the get_volume() method of any derived type should always return the correct volume
  • the get_volume() method of any derived type should always succeed (for a properly initialised shape)
61

The SOLID principles

3. Liskov Substitution Principle

Subtypes must be substitutable for their base types without altering the correctness of the program.

Essentially, this means that derived classes should not behave in unexpected ways

  • with respect to their base class

For example, imagine we have a base class representing a general 3D shape, with a virtual method get_volume() to obtain its volume

  • the get_volume() method of any derived type should always return the correct volume
  • the get_volume() method of any derived type should always succeed (for a properly initialised shape)

More explicitly, if the base class provides certain functionality that is expected to work in a certain way, it should work the same way for all derived classes as well.

61

The SOLID principles

4. Interface Segregation Principle

Clients should not be forced to depend on interfaces they do not use

This means that interfaces should be kept minimal and specific to a single task. If a class provides functionality for many loosely related tasks, you should consider splitting this off into distinct interfaces.

The main advantage of this principle is to avoid bloated interfaces that become difficult to maintain.

62

The SOLID principles

4. Interface Segregation Principle

Clients should not be forced to depend on interfaces they do not use

This means that interfaces should be kept minimal and specific to a single task. If a class provides functionality for many loosely related tasks, you should consider splitting this off into distinct interfaces.

The main advantage of this principle is to avoid bloated interfaces that become difficult to maintain.

A good example is the addition of functionality such as stapling and faxing to Xerox printers (this is actually where this principle originates!).

  • over time, all this functionality was added to the main interface
  • this eventually made almost impossible to make modifications as it would take too long to make even simple changes!
62

The SOLID principles

5. Dependency Inversion Principle

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.

The idea behind this principle is to avoid introducing dependencies between specific sub-systems that would be hard to untangle later.

63

The SOLID principles

5. Dependency Inversion Principle

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.

The idea behind this principle is to avoid introducing dependencies between specific sub-systems that would be hard to untangle later.

For example, a patient record system might need to interface with a database where the records are stored. If we write the code for the database we currently use, it may be very difficult to modify this code to swap to a different (presumably better) database at a later stage.

  • we may need to make many changes in many places deep in the code
63

The SOLID principles

5. Dependency Inversion Principle

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.

The idea behind this principle is to avoid introducing dependencies between specific sub-systems that would be hard to untangle later.

For example, a patient record system might need to interface with a database where the records are stored. If we write the code for the database we currently use, it may be very difficult to modify this code to swap to a different (presumably better) database at a later stage.

  • we may need to make many changes in many places deep in the code

A better approach is to introduce another level of abstraction to represent a database and all the generic interactions required, so that switching to a different database can be done by modifying that interface, without affecting any other parts of the code.

63

Favor Composition over Inheritance

While inheritance is a powerful tool, it has its downsides, introducing very tight coupling between the different components, and making it harder to modify.

64

Favor Composition over Inheritance

While inheritance is a powerful tool, it has its downsides, introducing very tight coupling between the different components, and making it harder to modify.

For example, if we had designed a base class for a car that assumed manual transmission, introducing automatic transmission into the class hierarchy would require extensive changes in the base and derived classes.

64

Favor Composition over Inheritance

While inheritance is a powerful tool, it has its downsides, introducing very tight coupling between the different components, and making it harder to modify.

For example, if we had designed a base class for a car that assumed manual transmission, introducing automatic transmission into the class hierarchy would require extensive changes in the base and derived classes.

Rather than trying to identify common features between components and forming a hierarchy, it is often easier to compose complex components out of simpler ones.

64

Favor Composition over Inheritance

While inheritance is a powerful tool, it has its downsides, introducing very tight coupling between the different components, and making it harder to modify.

For example, if we had designed a base class for a car that assumed manual transmission, introducing automatic transmission into the class hierarchy would require extensive changes in the base and derived classes.

Rather than trying to identify common features between components and forming a hierarchy, it is often easier to compose complex components out of simpler ones.

Inheritance is best reserved for cases that truly require runtime polymorphism.

64

Don't repeat yourself

One of the aims of Object-Oriented design to maximise code re-use: don't repeat yourself!

If you have the same block of code in more than two places, consider making it a separate method.

Similarly, if you use a hard-coded value more than once, consider defining them as a constant at global scope.

65

Don't repeat yourself

One of the aims of Object-Oriented design to maximise code re-use: don't repeat yourself!

If you have the same block of code in more than two places, consider making it a separate method.

Similarly, if you use a hard-coded value more than once, consider defining them as a constant at global scope.

The benefit of this principle is reduced maintenance:

  • if we need to fix or update this particular functionality, we only need to do it in one place
65

You ain't gonna need it!

This principle suggests that you should only implement the features you need – not the features you think you might need in the future

66

You ain't gonna need it!

This principle suggests that you should only implement the features you need – not the features you think you might need in the future

The point is to avoid unnecessary code bloat, maintenance burden, source of bugs – and wasting your own time!

66

You ain't gonna need it!

This principle suggests that you should only implement the features you need – not the features you think you might need in the future

The point is to avoid unnecessary code bloat, maintenance burden, source of bugs – and wasting your own time!

This is closely related to the widely used Keep It Simple Stupid (KISS)

66

Keep It Simple, Stupid (KISS)

The KISS principle suggests that designs (in software and more broadly!) should aim for simplicity

Simple solutions are often better in practice

  • they are easier to understand and reason about
  • they are easier to implement and get right
  • they are easier to maintain and use
  • they are often more efficient in terms of resource use
67

Keep It Simple, Stupid (KISS)

The KISS principle suggests that designs (in software and more broadly!) should aim for simplicity

Simple solutions are often better in practice

  • they are easier to understand and reason about
  • they are easier to implement and get right
  • they are easier to maintain and use
  • they are often more efficient in terms of resource use

... and simple designs are often more elegant!

67

Design principles: summary

There are many design principles that can help to guide the design process

Some of these can at times be contradictory

  • for example, the dependency inversion principle advocates for the use of abstract interfaces to avoid unnecessary coupling
  • but the 'You ain't gonna need it' principle suggests not implementing unnecessary features unless you need them now
68

Design principles: summary

There are many design principles that can help to guide the design process

Some of these can at times be contradictory

  • for example, the dependency inversion principle advocates for the use of abstract interfaces to avoid unnecessary coupling
  • but the 'You ain't gonna need it' principle suggests not implementing unnecessary features unless you need them now

Remember these are only guidelines

  • they are not hard-and-fast rules!

Ultimately, the best design is one that is sufficiently complex to perform the task at hand, while remaining as simple as it can be.

  • think about how you would interact with your code a year from now:
    • will you be able to understand it?
    • would you be able to modify it easily to extend its functionality or fix bugs?
    • what if someone else needs to maintain your code in the future?
68

Exercise

Propose an Object-Oriented Design for the following project:

A medical device manufacturer specialises in designing and producing stent grafts, which are tubular devices typically composed of a special fabric supported by a rigid structure (the stent). They are commonly employed in endovascular surgery to repair aneurysms, which are balloon-like bulges in arteries (e.g. the aorta) caused by weakness in the arterial wall. The manufacturer produces two different types of stent graft: bifurcated and complex (see images below). In a bifurcated stent graft the tubular structure splits into two, and each branch is typically placed in a separate branch of the artery undergoing repair. In a complex stent grant, the tubular structure contains a number of holes which should be located over the smaller arteries that branch off of the main artery. Complex stent grafts come in two types: branched (in which the holes have tubes attached to them which fit into the smaller arteries) or fenestrated (in which they are just holes).

The extra information in the following slides may be useful.

69

The rigid structure of all stent grafts is made of either nitinol or stainless steel. Some stent grafts have a fabric cover whilst others are uncovered. All stent grafts should have a diameter and a length, both stored in millimetres. For a bifurcated stent graft it is also necessary to store the diameter of each of the bifurcations. For a complex stent graft it is necessary to store the number of holes it has. For each hole the following extra information is required: the angular position (an integer between 1 and 12 which represents the position of the hole on a 'clock face' viewed from above the stent graft; and the height of the hole in millimetres.

There will not normally be more than 20 holes in a stent graft.

You are tasked with creating an object-oriented design to represent this problem domain. In your final design, the classes for each type of stent graft should be able to display their information to the screen.

Stent grafts: (left) bifurcated, and (right) complex.

70

Exercises

We have now covered all the material necessary to finish the robot arm project

You can find the most up to date version in the project's solution/ folder

Modify your code to implement the remaining steps:

  1. load the parameter file (including all relevant error checking)
  2. set the various angles to their corresponding values in the parameter file for each time frame
  3. iterate over the time frames, compute the trajectory, and display on the terminal
  4. compute the speed of the tip and display this as a function of time
  5. compute the acceleration of the tip and display this alongside the speed
  6. compute the maximum speed and acceleration and check whether they are within the specified safety margins

Refer to the online solution for the final design

2
Paused

Help

Keyboard shortcuts

, , Pg Up, k Go to previous slide
, , Pg Dn, Space, j Go to next slide
Home Go to first slide
End Go to last slide
Number + Return Go to specific slide
b / m / f Toggle blackout / mirrored / fullscreen mode
c Clone slideshow
p Toggle presenter mode
s Start & Stop the presentation timer
t Reset the presentation timer
?, h Toggle this help
Esc Back to slideshow