Week 1 - Design Principles, Creational Patterns: Singleton and Factory Patterns

Lecture recording (Tuesday 3 September, 2024) here.

Lab recording (Thursday 5 September, 2024) here.

Introduction

To start the course we will look at software design principles in general. These principles will be adhered to during our study of standard design patterns and design patterns for machine learning. Students are encouraged to take the tutorial on UML. We will look at two design patterns that we are already familiar with - the singleton design pattern and the factory design pattern. The singleton pattern ensures that a class has only one instance and provides a global point of access to it. The factory pattern provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.

Videos

Design PrinciplesSOLID Design Principles with Java Examples
10 Design Principles For Software Engineers
The Singleton PatternSingleton Pattern - Design Patterns
The Singleton Pattern Explained and Implemented in Java
The Factory PatternFactory Design Pattern
The Factory Method Pattern Explained and Implemented in Java

Assignment(s)

Assignment 1 - The Retail Store

Design Principles

Software design principles represent a set of guidelines that helps us to avoid having a bad design. there are 3 important characteristics of a bad design that should be avoided:

Software design should be based on SOLID Design Principles, such as:

See also 10 Design Principles in Software Engineering:

Common themes in design patterns

Design patterns are used by experts to make their designs more flexible and reusable, so in studying them you tend to see:

The Three Categories of Design Patterns

Design patterns can be categorized into three main types: creational patterns, structural patterns, and behavioral patterns.
Creational patterns focus on object creation mechanisms, providing flexible ways to create objects while hiding the complexities involved. They help ensure that objects are created in a manner that is suitable for the given situation.
Structural patterns deal with object composition and relationships between objects, focusing on class and object composition to form larger structures. They help define how objects and classes can be combined to form flexible and efficient structures.
Behavioral Patterns focus on communication between objects and the distribution of responsibilities, describing patterns of communication between objects to achieve specific behaviors. They help define how objects interact and fulfill their roles in a flexible and maintainable way.

The Singleton Design Pattern

The Rationale

The singleton pattern is a design pattern that is used to ensure that a class has only one instance, and that this instance can be easily accessed throughout an application. The rationale for using the singleton pattern is to provide a single point of access to a shared resource, such as a database connection, a file system, or a network socket.

The UML

Here is the UML diagram for the singleton pattern:
The Singleton Pattern
Here are the components of the singleton design pattern:

  1. instance: The Singleton class has a private static variable instance which points to the single instance of the class.
  2. getInstance: This method creates or returns the single instance of the Singleton class.
  3. Note that the constructor is private, to ensure no other objects of this class can be created.

Code Example - Do Something

The following code attempts to create or access one instance of the singleton class:
C++: Singleton.cpp.
C#: Program.cs.
Java: Factory.java.
Python: Python.py.

Common Usage

The following are some common usages of the singleton pattern:

  1. Database connections: In many applications, it's necessary to have a single connection to the database to ensure consistency and prevent issues that can occur from having multiple connections.
  2. Caching: When you need to cache data to improve performance, you can use a singleton class to manage the cache and ensure that it's accessed in a thread-safe manner.
  3. Logging: Logging is an essential aspect of any application. By using a Singleton class to manage the logging, you can ensure that all log messages are written to the same log file.
  4. Configuration management: In many applications, there are configuration settings that need to be managed centrally. A Singleton class can be used to manage these settings and ensure that they are accessed consistently throughout the application.
  5. Thread pool management: When working with multi-threaded applications, a Singleton class can be used to manage thread pools and ensure that threads are used efficiently and consistently across the application.
  6. GUI components: When working with graphical user interfaces, it's often necessary to have a single instance of a component, such as a toolbar or menu bar, to ensure consistency across the application.

Code Problem - The Database

We wish to write code that access a database. There can only be one database. The code must either create the database, or use it if it has already been created. The following code allows only one instance of a database:
Database.h,
Database.cpp and
DatabaseMain.cpp.

The Factory Design Pattern

The Rationale

The Factory pattern is a creational design pattern that provides an interface for creating objects in a super class, but allows subclasses to alter the type of objects that will be created. The rationale for using the Factory pattern is to separate the creation of objects from their use in the code, which can simplify the code and make it more maintainable.

The UML

Here is the UML diagram for the factory pattern:
The Factory Pattern
Here are the components of the Factory design pattern shown in the above UML diagram:

  1. Shape: An abstract class that defines the concrete shapes.
  2. Circle, Square, Rectangle: Concrete shapes.
  3. ShapeFactory: Creates one of the concrete shapes.

Code Example - Factory Pattern

The following is an example of a factory pattern to create one of three concrete objects:
C++: Factory.cpp.
C#: Program.cs.
Java: Factory.java.
Python: Python.py.

Common Usage

The following are some common usages of the factory pattern:

  1. Object creation: One of the most common use cases of the Factory pattern is to encapsulate object creation logic in a separate class. This allows for better modularization of code, reduces coupling between components, and makes it easier to change object creation logic without affecting other parts of the code.
  2. Plug-in architectures: In applications with plug-in architectures, the Factory pattern is often used to create instances of plug-ins at runtime. This allows the application to be easily extended with new functionality without modifying the core codebase.
  3. Configuration management: The Factory pattern can be used to create objects based on configuration data. For example, a configuration file may specify which concrete implementation of a class to use, and the Factory pattern can create an instance of that implementation.
  4. Testing: The Factory pattern can be used in testing to create mock objects or test doubles that simulate the behavior of a real object. This allows for better unit testing and easier isolation of components during testing.

Code Problem - Toolbox

We wish to build a toolbox of many types of tools. The types of tools will be entered by the user, and are not known at compile time. The following code fills a toolbox with screwdrivers and wrenches with the factory pattern:
ToolIf.h, the tool abstract class,
Hammer.h, the hammer concrete class,
Screwdriver.h, the screwdriver concrete class,
Toolbox.h, the toolbox,
ToolIf.cpp, the toolbox factory function.
ToolMain.cpp, the main function.

Coding Tutorials

The following is a tutorial showing sample code written in C++ from SEP101 and SEP200 and their equivalent code in Java and C#.

SEP101 Week 1 - Hello World

The following is a simple hello world program.
C++: Hello.cpp
C#: Hello.cs
Java: Hello.java

SEP101 Week 2 - Student Scholarship

The following program demonstrates if-else and switch, input/output, and simple mathematical operations.
C++: StudentScholarship.cpp
C#: StudentScholarship.cs
Java: StudentScholarship.java

SEP101 Week 3 - Average Mark

The following program demonstrates loops.
C++: AverageMark.cpp
C#: AverageMark.cs
Java: AverageMark.java

SEP101 Week 5 - Pointers

The following program demonstrates pass by value and pass by address. Note that the Java program does not handle pointers and does not work properly.
C++: Swap.cpp
C#: Swap.cs
Java: Swap.java

SEP101 Week 7 - Structures and Functions

The following program demonstrates structures (or classes) and functions.
C++: StudentMarks.cpp
C#: StudentMarks.cs
Java: StudentMarks.java

SEP101 Week 10 - Structures and Functions, Multiple Files

The following program demonstrates how C# and Java handle multiple file includes (they don't).
C++: Transaction.h, Transaction.cpp, TransactionMain.cpp.
C#: Transaction.cs, TransactionMain.cs
Java: Transaction.java TransactionMain.java

SEP200 Week 4 - Calculate Area

The following program demonstrates inheritance.
C++: CalculateArea.cpp
C#: CalculateArea.cs
Java: StudentScholarship.java

SEP200 Week 5 - Math Library

The following program demonstrates polymorphism.
C++: MathLib.cpp
C#: MathLib.cs
Java: MathLib.java

SEP200 Week 9 - Employee Information

The following program demostrates some of the container classes seen in C++'s' STL, in multiple files.
C++: EmployeeInfoSTL.h, EmployeeInfoSTL.cpp, EmployeeInfoSTLMain.cpp
C#: EmployeeInfoSTL.cs, EmployeeInfoSTLMain.cs
Java: EmployeeInfoSTL.java, EmployeeInfoSTLMain.java