You are on page 1of 33

Principles

A Starting Point
 As a general rule, aiming for systems
whose components exhibit high cohesion
and low coupling is a very good thing, as it
results in systems that readily reusable,
testable, maintainable, modifiable etc.
 This applies regardless of what we are
designing e.g. electronic devices, cars,
houses or (especially) software.
 Of course, there will be times when the rule
needs to be broken, as when performance
requirements can not be achieved any
other way.
Coupling and Cohesion
Lethbridge and Laganiere (2004) provide the following
definitions:

Coupling:
“A measure of the extent to which interdependencies
exist between software modules. An important design
principle is to reduce coupling”
Cohesion:
“A measure of the extent to which related aspects of a
system are kept together in the same module, and
related aspects are kept out”

The goal is to achieve designs with high cohesion and low


coupling
Coupling and Cohesion
Coupling and cohesion as defined are
implied to be measurable but no indication
is provided as to
1. what “measures” should be used and
2. how these measures might be minimized
(coupling) and maximized (cohesion)
Some Simple Measures
 Coupling:
○ The number of dependencies that a class has

 Cohesion:
○ The number of responsibilities that a class has

 Used wisely, these work well in practice.


 Note that measure determination and
evaluation has been, and continues to
be, an active research area, so things
may change.
Q: What constitutes wise usage?
A: Recognising that
 As components get smaller (package ->
method) responsibilities become more
focused. E.g. what are the responsibilities for
java.util, java.util.ArrayList and
java.util.ArrayList.add()?
 Responsibilities may not be immediately
obvious. E.g. user class creation is a
responsibility that is separate from the
responsibility that uses the class instance, as
in connection creation in the PersonQueries
class of Case Study 1.
 In business, being dependent on a single supplier is
generally something to be avoided. Likewise in
software – having a dependency on an interface
(potentially multiple suppliers) is generally better
than having a dependency on a concrete class (a
single supplier). E.g. in Case Study 1, the
PersonQueries interface (and hence clients of that
interface) are dependent on the interface
java.util.List. In the PersonQueries class, query
results are stored in an ArrayList (which implements
List), but returned as a List.
 Having a dependency on an interface rather than on
a concrete class doesn’t reduce the dependency
count, but rather, it weakens the dependency.
Measure optimization
Q: How can we maximize cohesion and
minimise coupling?
A: Apply design principles
Design Principles
One commonly used set of principles are
the SOLID principles:
Single Responsibility Principle
Open Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
Single Responsibility Principle
Definition:
“Classes should change only for a single
reason”
“ A class should ideally have a single responsibility”
Application:
If a class has more than one reason to change,
then the responsibilities of that class that are the
cause of the change should be separated into
multiple classes. Alternatively, if a class has
multiple responsibilities, separate those
responsibilities into separate classes.
Consequence:
Highly cohesive classes
An Example
Consider the PersonQueries class from
Case Study 1.

Q: Does it violate SRP?


A: Yes. The class is responsible for
1. Opening and closing the connection and
2. Executing queries

What are the design alternatives?


Alternatives
Alternative 1
 One class to manage the connection
 One class to execute queries
Alternative 2
 One class to manage the connection
 One class for queries that return result sets
 One class for queries that return integers
Alternative 3
 One class to manage the connection
 One class for each query
 Q: Which is best?
 A: Not 2, but probably 1 rather than 3,
although feel free to convince me otherwise.
My rationale is that
1. If you are going to model query execution as
multiple responsibilities rather than a single
responsibility, there is nothing actually gained
unless every query is a separate class
2. while having one class for each query is perhaps
“better” in terms of SRP, its implementation is more
complicated because you want the prepared
statements to be created once only and to persist
for as long as the connection is open
Object Creation
As you might now expect, object creation
is a frequent cause of SRP violation.

Q: If you no longer create user objects in


the class (library objects are fine) how are
they accessed?
A: They are created in a separate class
and are injected into the classes that use
them via the class constructor or an
explicit method.
Note that
 Since the object is being passed in as a
parameter, if the using class can store the
object reference as an interface, if an
appropriate interface has been
implemented.
 Main() is a good place for object creation
and dependency injection. That, plus starting
the execution then becomes the single
responsibility for the class, which I call the
application class.
 In addition to helping achieve SRP, the application
class idiom also provides for a separation of the start
up process, when the application objects are
constructed and the dependencies are “wired”
together, from the run time logic that takes over after
start up. Having the start up logic in one place is a
very good thing.
 The application class idiom is widely used, but has
not, to my knowledge, achieved pattern status.
Some authors refer to a more a general form of it as
a guideline called “separate use from construction”.
Refer to the resources section of the course website
for a link to one such discussion.
Open Closed Principle
Definition:
“Classes should be open for extension, but closed
for modification”
Application:
Couple a concrete class (which is closed for
modification) to an abstract class or an interface.
The abstract class/interface can then be extended
through subclassing and the abstract/interface
methods of the extended class invoked from the
concrete class using polymorphism. Inject the
dependency via the class constructor or an explicit
method.
Consequence:
Looser coupling; concrete class is unmodified
An Example: Knoernschild, p. 321
// open for extension class ChequeAccount extends AccountType {
abstract class AccountType { private int balance;
public abstract void deposit( int d );
ChequeAccount() {
}
balance = 0;
}
class SavingsAccount extends AccountType { @Override
public void deposit( int d ) {
private int balance;
balance += d;
SavingsAccount() { System.err.println("Cheque account
balance = 0; balance now $"+balance);
}
}
}

@Override
public void deposit( int d ) {
balance += d;
System.err.println("Savings account
balance now $"+balance);
}
}
// closed for modification public class OCP {
class Account { public static void main(String[] args) {
private AccountType _a; AccountType savingsAccount =
new SavingsAccount();
public Account(AccountType a) { Account account =
_a = a; new Account(savingsAccount);
} account.deposit( 1000 );
public void deposit(int d) {
_a.deposit( d ); }
} }
}
Note that
 In the preceding example, we could have
used an interface instead of an abstract
class
 OCP also underpins a number of common
design patterns, such as the State and
Strategy patterns
Dependency Inversion Principle
Definition:
“Depend on abstractions. Do not depend on
concretions” or in the Java world, “Design to
interfaces”
Application:
Couple at the abstract level (ie to an abstract
class or interface, rather than to a concrete
class). Inject the dependency via the class
constructor or an explicit method.
Consequence:
Looser coupling
An Example
In the OCP example, Account is
dependent on the abstract class
AccountType and not on the concrete
classes, ChequeAccount and
SavingsAccount. Not only does this
weaken the dependency, it also facilitates
OCP.
Liskov Substitution Principle
Definition:
“Subclasses should be substitutable for their base classes”. More
specifically substitutability means that a caller that communicates with an
abstraction, i.e. a base class or an interface, should not be aware of and
should not be concerned with the different concrete types of those
abstractions. The client should be able to call BaseClass.DoSomething()
and get a perfectly usable answer regardless of what the concrete class
is in place of BaseClass
Application:
For LSP to work the derived class must also “behave well”, meaning it
must not
1. remove any base class behaviour
2. violate base class invariants i.e. the rules and constraints of a class,
in order to preserve its integrity.
Consequence:
In effect, LSP refines the IS-A relationship with ‘IS-SUBSTITUTABLE-FOR’,
meaning that an object is substitutable with another object in all situations without
running into exceptions and unexpected behaviour.

http://dotnetcodr.com/2013/08/19/solid-design-principles-in-net-the-liskov-substitution-
principle/
An Example: Bennett et al., p.409
ChequeAccount
Account
Disinheritance of
debit() means that accountName
balance accountName
the left-hand hierarchy
balance
is not Liskov compliant Restructuring
credit() to
debit() satisfy LSP credit()

MortgageAccount
MortgageAccount ChequeAccount
interestRate
interestRate
calculateInterest() debit()
- debit() calculateInterest()

24
© 2010 Bennett, McRobb and Farmer
Note that in Java, inherited methods
cannot be disinherited, so either debit()
1. has to be removed from ChequeAccount or
2. is overridden in MortgageAccount with a
method that does nothing / throws a
NotImplementedException
Interface Segregation Principle
Definition:
“Many specific interfaces are better than a
single, general interface”
Application:
Apply SRP to interface design
Consequence:
highly cohesive interfaces
An Example: Knoernschild, p. 328

Consider a requirement for an public interface RowSetManager {


interface that would allow public void next();
public void previous();
uniform access to a variety of
public void retrieve();
different data sources public void insertRow();
(relational database, flat file, public void updateRows();
…). Ignoring return types and public void deleteRow();
parameters, the following is a public void setProperty();
reasonable proposal: public void getProperty();
}
 But, what if
 Data was read only?
 The application only needed to iterate
through the retrieved data set?
 The data was cached in memory?
…
 Perhaps
public interface DataReader { public interface DataInserter {
public void retrieve(); public void insertRow();
} }

public interface RowCursor { public interface DataDeleter {


public void next(); public void deleteRow();
public void previous(); }
public void setProperty();
public void getProperty();
}
public interface DataManager extends
DataInserter, DataDeleter {
public void updateRows();
}
Other Principles
 Composite Reuse Principle
 Law of Demeter/Principle of Least
Knowledge
 Don’t Repeat Yourself (DRY)
 ...
Composite Reuse Principle
Definition:
“Favour polymorphic composition of objects
over inheritance”
Application:
Work through the example in Knoernschild,
p.329 - 334 if you are keen.
Consequence:
Avoids the problems associated with using
inheritance as the primary reuse
mechanism.
Law of Demeter/Principle of Least Knowledge

Definition:
“Don’t talk to strangers”
http://en.wikipedia.org/wiki/Law_of_Demeter
Application:
A method m of an object O may only invoke the methods
of the following kinds of objects:
1. O’s
2. M’s parameters
3. Any objects instantiated within M
4. O’s direct component objects
5. Any global variables accessible by O
Consequence:
Objects are less dependent on the internal structure of
other objects, which is a good thing.
Don’t Repeat Yourself
Definition:
“Every piece of knowledge must have a single,
unambiguous, authoritative representation within a
system”
http://en.wikipedia.org/wiki/Don%27t_repeat_yourself
Application:
Self evident?
Consequence:
Self evident?

You might also like