An example problem about software architecture: what is the best
An example problem about the interaction between classes: how to lower the
UI
and Logic
classes ?
Software Design Pattern : An elegant reusable solution to a commonly recurring problem within a given context in software design.
Creating more than one instance of a certain class is undesirable due to some reason
Normally, any client can instantiate a class by calling the constructor. This means we can end up with multiple instances of the class in concern.
Make the constructor of the singleton class private
. Provide a public
method to access the singleton instance.
As shown, the solution makes the constructor private (note the “-“ visibility marker for the constructor), which prevents instantiation from outside the class. The single instance of the singleton class is maintained by a private class-level
variable. Access to this object is provided by a public class-level operation getInstance()
. In the skeleton code above, getInstance()
instantiates a single copy of the singleton class when it is executed for
the first time. Subsequent calls to this operation return the single instance of the class.
public class ClassicSingleton {
private static ClassicSingleton instance = null;
private ClassicSingleton() {
// Exists only to defeat instantiation.
}
public static ClassicSingleton getInstance() {
if(instance == null) {
instance = new ClassicSingleton();
}
return instance;
}
}
class GlobalClass
{
int m_value;
static GlobalClass *s_instance;
GlobalClass(int v = 0)
{
m_value = v;
}
public:
int get_value()
{
return m_value;
}
void set_value(int v)
{
m_value = v;
}
static GlobalClass *instance()
{
if (!s_instance)
s_instance = new GlobalClass;
return s_instance;
}
};
Which of the following is an ideal situation for using Singleton pattern?
Database is a shared resources here, and by using singleton pattern, we have better control over the database operations.
Client component/method/object: The component/method/object that is interacting with a given code.
❓ Definition of a design pattern
❓ Attributes of a design pattern
❓ Definition of a design pattern
❓ Attributes of a design pattern
❓ Definition of a design pattern
❓ Attributes of a design pattern
Software Architecture: The high level structures of a software system, the discipline of creating such structures, and the documentation of these structures. These structures are needed to reason about the software system. Each structure comprises software elements, relations among them, and properties of both elements and relations. The architecture of a software system is a metaphor, analogous to the architecture of a building.
Coupling: The degree of interdependence between software modules; a measure of how closely connected two routines or modules are; the strength of the relationships between module.
The authors of the DesignPatternsBook came to be known as the "Gang of Four." The name of the book ("Design Patterns: Elements of Reusable Object-Oriented Software") is too long for e-mail, so "book by the gang of four" became a shorthand name for it. After all, it isn't the ONLY book on patterns. That got shortened to "GOF book", which is pretty cryptic the first time you hear it.
Creating more than one instance of a certain class is undesirable due to some reason
Normally, any client can instantiate a class by calling the constructor. This means we can end up with multiple instances of the class in concern.
Make the constructor of the singleton class private
. Provide a public
method to access the singleton instance.
As shown, the solution makes the constructor private (note the “-“ visibility marker for the constructor), which prevents instantiation from outside the class. The single instance of the singleton class is maintained by a private
class-level variable. Access to this object is provided by a public class-level operation getInstance()
. In the skeleton code above, getInstance()
instantiates a single copy of the singleton class when
it is executed for the first time. Subsequent calls to this operation return the single instance of the class.
public class ClassicSingleton {
private static ClassicSingleton instance = null;
private ClassicSingleton() {
// Exists only to defeat instantiation.
}
public static ClassicSingleton getInstance() {
if(instance == null) {
instance = new ClassicSingleton();
}
return instance;
}
}
class GlobalClass
{
int m_value;
static GlobalClass *s_instance;
GlobalClass(int v = 0)
{
m_value = v;
}
public:
int get_value()
{
return m_value;
}
void set_value(int v)
{
m_value = v;
}
static GlobalClass *instance()
{
if (!s_instance)
s_instance = new GlobalClass;
return s_instance;
}
};
Which of the following is an ideal situation for using Singleton pattern?
Database is a shared resources here, and by using singleton pattern, we have better control over the database operations.
Client component/method/object: The component/method/object that is interacting with a given code.
The common format to describe a pattern consists of the following components:
Creating more than one instance of a certain class is undesirable due to some reason
Normally, any client can instantiate a class by calling the constructor. This means we can end up with multiple instances of the class in concern.
Make the constructor of the singleton class private
. Provide a public
method to access the singleton instance.
As shown, the solution makes the constructor private (note the “-“ visibility marker for the constructor), which prevents instantiation from outside the class. The single instance of the singleton class is maintained by a private class-level
variable. Access to this object is provided by a public class-level operation getInstance()
. In the skeleton code above, getInstance()
instantiates a single copy of the singleton class when it is executed for
the first time. Subsequent calls to this operation return the single instance of the class.
public class ClassicSingleton {
private static ClassicSingleton instance = null;
private ClassicSingleton() {
// Exists only to defeat instantiation.
}
public static ClassicSingleton getInstance() {
if(instance == null) {
instance = new ClassicSingleton();
}
return instance;
}
}
class GlobalClass
{
int m_value;
static GlobalClass *s_instance;
GlobalClass(int v = 0)
{
m_value = v;
}
public:
int get_value()
{
return m_value;
}
void set_value(int v)
{
m_value = v;
}
static GlobalClass *instance()
{
if (!s_instance)
s_instance = new GlobalClass;
return s_instance;
}
};
Which of the following is an ideal situation for using Singleton pattern?
Database is a shared resources here, and by using singleton pattern, we have better control over the database operations.
Client component/method/object: The component/method/object that is interacting with a given code.
Components need to access functionality deep inside other components. For example, the UI
component of a Library system might want to access functionality of the Book class contained inside the Logic
component.
Access to the component should be allowed without exposing its internal details. For example, the UI
component should access the functionality of the Logic
component without knowing that it contained a Book
class within it.
Include a
LibraryLogic
class acts as the Façade class.
The following ShapeMaker
is a Façade class. The caller class who uses ShapeMaker
could use it to draw different shape, but the implementation detail and the actual Shape
class is invisible to the caller
class.
public class ShapeMaker {
private Shape circle;
private Shape rectangle;
private Shape square;
public ShapeMaker() {
circle = new Circle();
rectangle = new Rectangle();
square = new Square();
}
public void drawCircle(){
circle.draw();
}
public void drawRectangle(){
rectangle.draw();
}
public void drawSquare(){
square.draw();
}
}
A system is required to execute a number of commands, each doing a different task. For example, a system might have to support Sort
, List
, Reset commands.
It is preferable that some part of the code execute these commands without having to know each command type. For example, there can be a CommandQueue
object that is responsible for queuing commands and executing them without knowledge
of what each command does.
In the example solution below, the CommandCreator creates List
, Sort
, and Reset
Command objects and adds them to the CommandQueue
object. The CommandQueue
object treats them all
as Command
objects and performs the execute/undo
operation on each of them without knowledge of the specific Command type. When executed, each Command
object will access the DataStore
object
to carry out its task. The Command
class can also be an abstract class or an interface.
The general form of the solution is as follows.
The <<Client>>
creates a <<ConcreteCommand>>
object, and passes it to the <<Invoker>>
. The <<Invoker>>
object treats all commands as a general
<<Command>>
type.
<<Invoker>>
issues a request by calling execute()
on the command. If a command is undoable, <<ConcreteCommand>>
will store the state for undoing the command prior to invoking execute().
In addition, the <<ConcreteCommand>>
object may have to be linked to any <<Receiver>>
of the command before it is passed to the <<Invoker>>
. Note that an application
of the command pattern does not have to follow the structure given above. The essential element is to have a general <<Command>>
object that can be passed around, stored, executed, etc.
Create a command interface.
public interface Command {
void execute();
}
Some concrete commands class.
public class ConcreteCommand implements Command {
public void execute() {
// Do something
}
}
public class OtherConcreteCommand implements Command {
public void execute() {
// Do other thing
}
}
And the invoker class.
public class Invoker {
List<Command> cmdList = new ArrayList<Command>();
public void addCmd(Command cmd) {
cmdList.add(cmd);
}
public void executeAll() {
for (Command cmd : cmdList) {
cmd.execute();
}
cmdList.clear();
}
}
And use the Invoker to add and execute commands.
public static void main(String[] args) {
Command cmd1 = new ConcreteCommand();
Command cmd2 = new OtherConcreteCommand();
Invoker invoker = new Invoker();
invoker.addCommand(cmd1);
invoker.addCommand(cmd2);
invoker.executeAll();
}
Study the Memento pattern yourself, and compare it with Command pattern.
Command and Memento act as magic tokens to be passed around and invoked at a later time. In Command, the token represents a request; in Memento, it represents the internal state of an object at a particular time. Polymorphism is important to Command, but not to Memento because its interface is so narrow that a memento can only be passed as a value.
Command can use Memento to maintain the state required for an undo operation.
Most applications support storage/retrieval of information, displaying of information to the user (often via multiple UIs having different formats), and changing stored information based on external inputs.
To reduce coupling resulting from the interlinked nature of the features described above.
To decouple data, presentation, and control logic of an application by separating them into three different components: Model, View and Controller.
The relationship between the components can be observed in the diagram below. Typically, the UI is the combination of view and controller.
Given below is a concrete example of MVC applied to a student management system. In this scenario, the user is retrieving data of one student.
In the diagram above, when the user clicks on a button using the UI, the ‘click’ event is caught and handled by the UiController.
Note that in a simple UI where there’s only one view, Controller and View can be combined as one class.
There are many variations of the MVC model used in different domains. For example, the one used in a desktop GUI could be different from the one used in a Web application.
Study the "Front Controller" and "Page Controller" patterns yourself, and compare their similarity and difference.
Here is another scenario from the same student management system where the user is adding a new student to the system.
Now, assume the system has two additional views used in parallel by different users:
StudentListUi
: that accesses a list of students andStudentStatsUi
: that generates statistics of current students.When a student is added to the database using NewStudentUi shown above, both StudentListUi and StudentStatsUi should get updated automatically, as shown below.
However, the StudentList
object has no knowledge about StudentListUi
and StudentStatsUi
(note the direction of the navigability) and has no way to inform those objects. This is an example of the type of problem
addressed by the Observer pattern.
An object (possibly, more than one) is interested to get notified when a change happens to another object. That is, some objects want to ‘observe’ another object.
A bidirectional link between the two objects is not desirable. However the two entities need to communicate with each other. That is, the ‘observed’ object does not want to be coupled to objects that are ‘observing’ it.
The Observer pattern shows us how an object can communicate with other objects while avoiding a direct coupling with them.
The solution is to force the communication through an interface known to both parties. A concrete example is given below.
Here is the Observer pattern applied to the student management system.
During initialization of the system,
1.First, create the relevant objects.
StudentList studentList = new StudentList();
StudentListUi listUi = new StudentListUi();
StudentStatusUi statusUi = new StudentStatsUi();
2.Next, the two UIs indicate to the StudentList
that they are interested in being updated whenever StudentList changes. This is also known as ‘subscribing for updates’.
studentList.addUi(listUi);
studentList.addUi(statusUi);
Within the addUi operation of StudentList
, all Observer objects subscribers are added to an internal data structure called observerList
.
//StudentList class
public void addUi(Observer o) {
observerList.add(o);
}
As such, whenever the data in StudentList
changes (e.g. when a new student is added to the StudentList
), all interested observers are updated by calling the notifyUIs
operation.
//StudentList class
public void notifyUIs() {
for(Observer o: observerList) //for each observer in the list
o.update();
}
UIs can then pull data from the StudentList whenever the update operation is called.
//StudentListUI class
public void update() {
//refresh UI by pulling data from StudentList
}
Note that StudentList
is unaware of the exact nature of the two UIs but still manages to communicate with them via an intermediary.
Here is the generic description of the observer pattern:
<<Observer>>
is an interface: any class that implements it can observe an <<Observable>>
. Any number of <<Observer>>
objects can observe (i.e. listen to changes of) the
<<Observable>>
object.<<Observable>>
maintains a list of <<Observer>>
objects. addObserver(Observer)
operation adds a new <<Observer>>
to the list of <<Observer>>
s.<<Observable>
, the notifyObservers()
operation is called that will call the update()
operation of all <<Observer>>
s in the list.In a GUI application, how is the Controller notified when the “save” button is clicked? UI frameworks such as JavaFX has inbuilt support for the Observer pattern.
There is a group of similar entities that appears to be ‘occurrences’ (or ‘copies’) of the same thing, sharing lots of common information, but also differing in significant ways.
For example, in a library, there can be multiple copies of same book title. Each copy shares common information such as book title
, author
,
ISBN
purchase date
and barcode number
(assumed to be unique for each copy of the book). Other examples include episodes
of the same TV series and stock items of the same product model (e.g. TV sets of the same model).
Representing the objects mentioned previously as a single class would be problematic (refer to anti-pattern description below). A better way to represent such instances is required, which should avoid duplicating the common information. Without duplicated information, inconsistency is avoided should these common information be changed.
Take for example the problem of representing books in a library. Assume that there could be multiple copies of the same title, bearing the same ISBN number, but different serial numbers. The above diagram shows an inferior or incorrect design for this problem. It requires common information to be duplicated by all instances. This will not only waste storage space, but also creates a consistency problem. Suppose that after creating several copies of the same title, the librarian realized that the author name was wrongly spelt. To correct this mistake, the system needs to go through every copy of the same title to make the correction. Also, if a new copy of the title is added later on, the librarian has to make sure that all information entered is the same as the existing copies to avoid inconsistency.
The design above segregates the common and unique information into a class hierarchy. Each book title is represented by a separate class with common data (i.e. Name
, Author
,
ISBN
The solution is to let a book copy be represented by two objects instead of one, as given below.
In this solution, the common and unique information are separated into two classes to avoid duplication. Given below is another example that contrasts the two situations before and after applying the pattern.
The general idea can be found in the following class diagram:
The <<Abstraction>>
class should hold all common information, and the unique information should be kept by the <<Occurrence>>
class. Note that ‘Abstraction’ and ‘Occurrence’ are not class names,
but roles played by each class. Think of this diagram as a meta-model (i.e. a ‘model of a model’) of the BookTitle-BookCopy
class diagram given above.
class MetaBook {
String name;
String author;
String isbn;
List<BookCopy> copies;
}
class BookCopy {
MetaBook meta;
String serial;
}
class MetaBook {
public:
string name;
string author;
string isbn;
vector<BookCopy> copies;
};
class BookCopy {
public:
string serial;
MetaBook meta;
}
What is the resulted Java class for the following relationship model using abstraction occurrence pattern?
Refer to the code example.
class TVSeries {
String seriesName;
String producer;
List<Episode> episodes;
}
class Episode {
TVSeries series;
int number;
String title;
String storySynopsis;
}
Next, let us look at a case study that shows how design patterns are used in the design of a class structure for a Stock Inventory System (SIS) for a shop. The shop sells appliances, and accessories for the appliances. SIS simply stores information about each item in the store.
Use cases: create a new item, view information about an item, modify information about an item, view all available accessories for a given appliance, list all items in the store.
SIS can be accessed using multiple terminals. Shop assistants use their own terminals to access SIS, while the shop manager’s terminal continuously displays a list of all items in store. In the future, it is expected that suppliers of items use their own applications to connect to SIS to get real-time information about current stock status. User authentication is not required for the current version, but may be required in the future.
A step by step explanation of the design is given below. Note that this is one out of many possible designs. Design patterns are also applied where appropriate.
Here is a step-through of the design:
A StockItem
can be an Appliance
or an Accessory
.
To track that each Accessory is associated with the correct Appliance
, consider the following alternative class structures.
The third one seems more appropriate (the second one is suitable if accessories can have accessories). Next, consider between keeping a list of Appliances
, and a list of StockItems
. Which is more appropriate?
The latter seems more suitable because it can handle both appliances and accessories the same way. Next, an abstraction occurrence pattern is applied to keep track of StockItems
.
Note the inclusion of navigabilities. Here’s a sample object diagram based on the class model created thus far.
Next, apply the façade pattern to shield the SIS internals from the UI.
As UI consists of multiple views, the MVC pattern is applied here.
Some views need to be updated when the data change; apply the Observer pattern here.
In addition, the Singleton pattern can be applied to the façade class.
The notion of capturing design ideas as "patterns" is usually attributed to Christopher Alexander. He is a building architect noted for his theories about design. His book Timeless way of building talks about “design patterns” for constructing buildings.
Here is a sample pattern from that book:
When a room has a window with a view, the window becomes a focal point: people are attracted to the window and want to look through it. The furniture in the room creates a second focal point: everyone is attracted toward whatever point the furniture aims them at (usually the center of the room or a TV). This makes people feel uncomfortable. They want to look out the window, and toward the other focus at the same time. If you rearrange the furniture, so that its focal point becomes the window, then everyone will suddenly notice that the room is much more “comfortable”
Apparently, patterns and anti-patterns are found in the field of building architecture. This is because they are general concepts applicable to any domain, not just software design. In software engineering, there are many general types of patterns: Analysis patterns, Design patterns, Testing patterns, Architectural patterns, Project management patterns, and so on.
In fact, the abstraction occurrence pattern is more of an analysis pattern than a design pattern, while MVC is more of an architectural pattern.
New patterns can be created too. If a common problem needs to be solved frequently that leads to a non-obvious and better solution, it can be formulated as a pattern so that it can be reused by others. However, don’t reinvent the wheel; the pattern might already exist.
The more patterns one acquires, the more ‘experienced’ he/she is. Exposing oneself to a multitude of patterns (at least the context and problem) is a must. Some patterns are domain- specific (e.g. patterns for distributed applications), some are created in-house (e.g. patterns the company/project) and some can be self-created (e.g. from past experience). However, most are common, and well known. As an example, GoF book contains 23 design patterns:
When using patterns, be careful not to overuse them. Do not throw patterns at a problem at every opportunity. Patterns come with overhead such as adding more classes or increasing the levels of abstraction. Use them only when they are needed. Before applying a pattern, make sure that: