week 03
COMP 2511
Object Oriented Design &
Programming
1© Aarthi Natarajan, 2018
• Earlier,
– we looked at different types of relationships between objects
• Inheritance
• Association (and types of association)
• This week,
we will learn about
– How to code for different types of association
– Abstract classes, interfaces and polymorphism
– How to model inheritance in the right way
– And we start exploring a series of design principles for building
good software
© Aarthi Natarajan, 2018 2
Coding for association,
aggregation, composition
© Aarthi Natarajan, 2018 3
• A semantically weak relationship between otherwise unrelated
objects
• Association represents a “uses” relationship between two or
more objects in which the objects have their own life-times and
there is no “ownership”
Association
© Aarthi Natarajan, 2018 4
Aggregation
• A specialised form of association between two or more objects in
which objects have their own life-cycle but there is ownership as
well
Composition
• A specialised form of aggregation in which if the parent object is
destroyed, the child objects ceases to exist
• Often referred to as a “Death Relationship”
Aggregation, Composition
© Aarthi Natarajan, 2018 5
Abstract classes, Interfaces and
Polymorphism
© Aarthi Natarajan, 2018 6
Abstract Classes
• A class which cannot be instantiated, i.e. cannot create object instances
of an abstract class
• Serves as a convenient place-holder for factoring out common behaviour
in sub-classes
• An abstract class may define abstractmethods (undefined in the abstract
class and defined in the sub-class
• A class with one or more abstract methods must be declared as abstract
Shape
-color: String
-name: String
+getColor() : String
+getArea() : double
+getPerimeter(): double
Circle
-radius: double
Rectangle
-width: double
-height: double
+getArea() : double
+getPerimeter(): double +getArea() : double
+getPerimeter(): double
Abstract class shown in italics
© Aarthi Natarajan, 2018 7
Interfaces
• An interface type defines collection of abstract methods, i.e.,
defining methods without a body (Java 8 can have default methods )
– Specifies “what” needs to be done, not “how” to do it
• A class implements an interface, by providing an implementation for
all the methods in the interface
• Classes can implement multiple interfaces, but extend only one class
Shape
-color: String
-name: String
+setColor(): void
+getColor() : String
+getArea() : double
+getPerimeter(): double
Circle
-radius: double
+getArea() : double
+getPerimeter(): double
GraphicObject
+drawShape(Shape) : void
© Aarthi Natarajan, 2018 8
Revising Polymorphism
• We have seen polymorphism in Java means:
– A variable of a particular reference type can change behaviour
depending upon the object instance it is pointing to
– Actual method invoked depends upon object at run-time and done
by dynamic-binding
– Polymorphism works with interfaces as well…
GraphicObject g1 = new Rectangle();
GraphicObject g2 = new Circle();
where Rectangle and Circle implement the interface
GraphicObject
polymorphism guarantees that the right drawshape()method is
applied to ensure correct results
© Aarthi Natarajan, 2018 9
Designing Good Software
© Aarthi Natarajan, 2018 10
The One Constant in software
analysis and design
• What is the one thing you can always count on in
writing software? – Change
© Aarthi Natarajan, 2018 11
Is all about:
• Making sure your software does what the customer wants it to do –
use-case diagram, feature list, prioritise them
• Applying OO design principles to:
– To ensure the system is flexible and extensible to accommodate
changes in requirements
– To strive for a maintainable, reusable, extensible design
Building Good Software
© Aarthi Natarajan, 2018 12
• A change in requirements some-times reveals
problems with your system that you did not even know
that they existed
• Remember, change is constant and your system should
continually improve when you add these
changes…..else software rots
© Aarthi Natarajan, 2018 13
We write bad code
Why do write bad code ?
• Is it because do not know how to write better code?
• Requirements change in ways that original design did not
anticipate
• But changes are not the issue –
• changes requires refactoring and refactoring requires time
and we say we do not have the time
• Business pressure – changes need to be made quickly –
“quick and dirty solutions”
• changes may be made by developers not familiar with the
original design philosophy
Bad code, in fact slows us down
Why does Software Rot?
© Aarthi Natarajan, 2018 14
Design Smells
When software rots
it smells…
A design smell
• is a symptom of poor design
• often caused by violation of key design
principles
• has structures in software that suggest
refactoring
© Aarthi Natarajan, 2018 15
Design Smells (1)
Rigidity
• Tendency of the software being too difficult to change even in
simple ways
• A single change causes a cascade of changes to other dependent
modules
Fragility
• Tendency of the software to break in many places when a single
change is made
Rigidity and fragility complement each other – aim towards
minimal impact, when a new feature or change is needed
© Aarthi Natarajan, 2018 16
Design Smells (2)
Immobility
• Design is hard to reuse
• Design has parts that could be useful to other systems, but
the effort needed and risk in disentangling the system is too
high
Viscosity
• Software viscosity – changes are easier to implement
through ‘hacks’ over ‘design preserving methods’
• Environment viscosity – development environment is slow
and in-efficient
Opacity
• Tendency of a module to be difficult to understand
• Code must be written in a clear and expressive manner
© Aarthi Natarajan, 2018 17
Design Smells (3)
Needless complexity
• Contains constructs that are
not currently useful
• Developers ahead of
requirements
Needless repetition
• Design contains repeated
structures that could
potentially be unified under a
single abstraction
• Bugs found in repeated units
have to be fixed in every
repetition
© Solid Principles and Design Patterns – Ganesh Samarthyam© Aarthi Natarajan, 2018 18
Characteristics of Good Design
So, we know when our design smells…
But how do we measure if a software is well-designed?
The design quality of software is characterised by
• Coupling
• Cohesion
Good software aims for building a system with loose coupling
and high cohesion among its components so that software
entities are:
• Extensible
• Reusable
• Maintainable
• Understandable
• Testable
© Aarthi Natarajan, 2018 19
Coupling
– Is defined as the degree of interdependence between
components or classes
– High coupling occurs when one component A depends on the
internal workings of another component B and is affected by
internal changes to component B
– High coupling leads to a complex system, with difficulties in
maintenance and extension…eventual software rot
– Aim for loosely coupled classes – allows components to be used
and modified independently of each other
– But “zero-coupled” classes are not usable – striking a balance is
an art!
© Aarthi Natarajan, 2018 20
Cohesion
– The degree to which all elements of a component or class or module
work together as a functional unit
– Highly cohesive modules are:
– much easier to maintain and less frequently changed and have
higher probability of reusability
– Think about
– How well the lines of code in a method or function work together
to create a sense of purpose?
– How well do the methods and properties of a class work together
to define a class and its purpose?
– How well do the classes fit together to create modules?
• Again, just like zero-coupling, do not put all the responsibility into a
single class to avoid low cohesion!
© Aarthi Natarajan, 2018 21
And, applying design principles is the key to creating high-quality
software
“Design principles are key notions considered
fundamental to many different software design
approaches and concepts.”
– SWEBOK v3 (2014)
“The critical design tool for software development
is a mind well educated in design principles”
– Craig Larman
© Aarthi Natarajan, 2018 22
What is a “design principle”?
A basic tool or technique that can be applied to designing
or writing code to make software more maintainable,
flexible and extensible
© Aarthi Natarajan, 2018 23
Several Design Principles…One Goal
Good Software
Design
Highly cohesive,
Loosely coupled
systems
Separation of Concerns (SOC)
(Djikstra, 1974)
SOLID
Principles
(next slide)
Design Patterns (GOF)
₋ Program to an interface, not to an
implementation
₋ Object composition over class inheritance
Pragmatic Programming
• DRY
(Don’t Repeat Yourself)
• KISS
(keep it simple, stupid!)
Less Fragile Systems –
( Maintainable,
Reusable
Extensible Code )
© Aarthi Natarajan, 2018 24
SOLID
• Single responsibility principle: A class should only have a
single responsibility.
• Open–closed principle: Software entities should be open for
extension, but closed for modification.
• Liskov substitution principle: Objects in a program should be
replaceable with instances of their subtypes without altering
the correctness of that program.
• Interface segregation principle: Many client-specific
interfaces are better than one general-purpose interface.
• Dependency inversion principle: One should “depend upon
abstractions, [not] concretions.”
© Aarthi Natarajan, 2018 25
When to use design principles
• Design principles help eliminate design smells
• But, don’t apply principles when there no design smells
• Unconditionally conforming to a principle (just because
it is a principle is a mistake)
• Over-conformance leads to the design smell – needless
complexity
© Aarthi Natarajan, 2018 26
Design Principle #1
The Principle of Least Knowledge or Law of Demeter
© Aarthi Natarajan, 2018 27
• Classes should know about and interact with as few classes as
possible
• Reduce the interaction between objects to just a few close
“friends”
• These friends are “immediate friends” or “local objects”
• Helps us to design “loosely coupled” systems so that changes
to one part of the system does not cascade to other parts of
the system
• The principle limits interaction through a set of rules
Design Principle #1
The Principle of Least Knowledge (Law of Demeter) – Talk
only to your friends
© Aarthi Natarajan, 2018 28
A method in an object should only invoke methods of:
• The object itself
• The object passed in as a parameter to the method
• Objects instantiated within the method
• Any component objects
• And not those of objects returned by a method
Don’t dig deep inside your friends for friends of friends of friends
and get in deep conversations with them — don’t do
– e.g. o.get(name).get(thing).remove(node)
The Principle of Least Knowledge
(Law of Demeter)
© Aarthi Natarajan, 2018 29
A method M in an object O can call on any other method
within O itself
• This rule makes logical sense, a method encapsulated within a
class can call any other method that is also encapsulated within
the same class
public class M {
public void methodM() {
this.methodN();
}
public void methodN() {
// do something
}
}
• Here methodM() calls methodN() as both are methods of
the same class
Principle of Least Knowledge, Rule 1:
© Aarthi Natarajan, 2018 30
A method M in an object O can call on any methods of
parameters passed to the method M
• The parameter is local to the method, hence it can be called as a
friend
public class O {
public void M(Friend f) {
// Invoking a method on a parameter passed to the method is
// legal
f.N();
}
public class Friend {
public void N() {
// do something
}
}
Principle of Least Knowledge, Rule 2:
© Aarthi Natarajan, 2018 31
A method M can call a method N of another object, if that
object is instantiated within the method M
• The object instantiated is considered “local” just as the object
passed in as a parameter
public class O {
public void M() {
Friend f = new Friend();
// Invoking a method on an object created within the
// method is legal
f.N();
}
public class Friend {
public void N() {
// do something
}
}
Principle of Least Knowledge, Rule 3:
© Aarthi Natarajan, 2018 32
Any method M in an object O can call on any methods of any
type of object that is a direct component of O
• This means a method of a class can call methods of classes of its
instance variables
public class O {
public Friend instanceVar = new Friend();
public void M4() {
// Any method can access the methods of the friend class
F through the instance variable “instanceVar”
instanceVar.N();
}
public class Friend {
public void N() {
// do something
}
}
Principle of Least Knowledge, Rule 4:
© Aarthi Natarajan, 2018 33
Well-designed Inheritance
© Aarthi Natarajan, 2018 34
Design Principle #2
LSP (Liskov Substitution Principle)
LSP is about well-designed inheritance
Barbara Liskov (1988) wrote:
If for each object o1 of type S there is an object o2 of type T such
that for all programs P defined in terms of T, the behavior of P is
unchanged when o1 is substituted for o2 then S is a subtype of T.
Bob wrote:
subtypes must be substitutable for their base types
Lecture demo: Square vs Rectangle
What is the problem with Square-Rectangle IS A relationship?
© Aarthi Natarajan, 2018 35
Another LSP Example: A board game
LSP reveals hidden problems with the above inheritance structure
Board3D
© Aarthi Natarajan, 2018 36
What are the issues?
LSP reveals hidden problems with the above inheritance structure
Board3D
Board3D
© Aarthi Natarajan, 2018 37
What are the issues?
LSP states that subtypes must be substitutable for their base
types
Board board = new Board3D()
But, when you start to use the instance of Board3D like a Board,
things go wrong
Artillery unit = board.getUnits(8,4)
Inheritance and LSP indicate that any method on Board should be
able to use on a Board3D, and that Board3D can stand in for
Board without any problems, so the above example clearly
violates LSP
Board here is actually
an instance of the sub-
type Board3D
But, what does this
method for a 3D board?
© Aarthi Natarajan, 2018 38
Solve the problem without inheritance
So what options are there besides inheritance?
• Delegation – delegate the functionality to another class
• Composition – reuse behaviour using one or more classes with
composition
Design Principle: Favour composition over inheritance
If you favour delegation, composition over inheritance, your
software will be more flexible, easier to maintain, extend
© Aarthi Natarajan, 2018 39
• The argument list should be exactly the same as that of the
overridden method
• The access level cannot be more restrictive than the overridden
method’s access level.
E.g., if the super class method is declared public then the
overriding method in the sub class cannot be either private or
protected.
• A method declared final cannot be overridden.
• Constructors cannot be overridden.
Rules for Method Overriding
© Aarthi Natarajan, 2018 40
Can static methods be over-ridden?
Static methods can be defined in the sub-class with the same
signature
– This is not overriding, as there is no run-time polymorphism
– The method in the derived class hides the method in the base
class
Lecture demo…
© Aarthi Natarajan, 2018 41
Covariance of return types in the overridden method
• The return type in the overridden method should be the same or
a sub-type of the return type defined in the super-class
• This means that return types in the overridden method may be
narrower than the parent return types
e.g., Assume Cat is a sub class of Animal
Rules for Method Overriding
© Aarthi Natarajan, 2018 42
What about Contra-variance of method arguments in the
overridden method
Can arguments to methods in sub-class be wider than the
arguments passed in the parent’s method ?
Rules for Method Overriding
© Aarthi Natarajan, 2018 43
Refactoring
The process of restructuring (changing the internal
structure of software) software to make it easier to
understand and cheaper to modify without changing its
external, observable behaviour
© Aarthi Natarajan, 2018 44
• Refactoring improves design of software
• Refactoring Makes Software Easier to Understand
• Refactoring Helps You Find Bugs
• Refactoring Helps You Program Faster
• Refactoring helps you to conform to design principles and avoid
design smells
Why should you refactor?
© Aarthi Natarajan, 2018 45
Tip: When you find you have to add a feature to a program, and the
program’s code is not structured in a convenient way to add the
feature, first refactor the program to make it easy to add the feature,
then add the feature
Refactor when:
– You add a function (swap hats between adding a function and
refactoring)
– Refactor When You Need to Fix a Bug
– Refactor As You Do a Code Review
When should you refactor?
© Aarthi Natarajan, 2018 46
• Duplicated Code
– Same code structure in more than one place or
– Same expression in two sibling classes
• Long Method
• Large Class (when a class is trying to do too much, it often shows
up as too many instance variables)
• Long Parameter List
• Divergent Change ( when one class is commonly changed in
different ways for different reasons )
• Shotgun Surgery ( The opposite of divergent change, when you
have to make a lot of little changes to a lot of different classes
Common Bad Code Smells
© Aarthi Natarajan, 2018 47
What is wrong with the design?
Is it wrong to write a quick and dirty solution OR is it an aesthetic
judgment (dislike of ugly code ) …
• Overly long statement()method , poorly designed that does
far too much, tasks that should be done by other classes (Code
Smell: Long Method)
• What if customer wanted to generate a statement in HTML? –
Impossible to reuse any of the behaviour of the current statement
method for an HTML statement. (Code Smell: Duplicated code)
• What about changes?
– What happens when “charging rules” change?
– what if the user wanted to change the way the movie was
classified
• The code is a maintenance night-mare (Design smell: Rigidity)
The Video Rental Example
© Aarthi Natarajan, 2018 48
Apply a series of fundamental refactoring techniques:
Technique #1: Extract Method
• Find a logical clump of code and use Extract Method.
Which is the obvious place? the switch statement
• Scan the fragment for any variables that are local in scope to the
method we are looking at
(Rental r and thisAmount)
• Identify the changing and non-changing local variables
• Non-changing variable can be passed as a parameter
• Any variable that is modified needs more care, if there is only one,
you could simply do a return
Improving the design
© Aarthi Natarajan, 2018 49
Technique #2: Rename variable
• Is renaming worth the effort? Absolutely
• Good code should communicate what it is doing clearly, and
variable names are a key to clear code. Never be afraid to change
the names of things to improve clarity.
Tip
Any fool can write code that a computer can understand. Good
programmers write code that humans can understand.
Improving the design
© Aarthi Natarajan, 2018 50
#3: Move method
• Re-examine method calculateRental() in class Customer
• Method uses the Rental object and not the Customer object
• Method is on the wrong object
Tip
Generally, a method should be on the object whose data it uses
Improving the design
© Aarthi Natarajan, 2018 51
What OO principles do Extract Method and Move Method use?
They make code reusable through Encapsulation and Delegation
But, isn’t encapsulation about keeping your data private?
The basic idea about encapsulation is to protect information in one
part of your application from other parts of the application, so
– You can protect data
– You can protect behaviour – when you break the behaviour out
from a class, you can change the behaviour without the class
having to change
And what is delegation?
– The act of one object forwarding an operation to another object
to be performed on behalf of the first object
Improving the design
© Aarthi Natarajan, 2018 52
#4: Replace Temp With Query
• A technique to remove unnecessary local and temporary variables
• Temporary variables are particularly insidious in long methods
and you can loose track of what they are needed for
• Sometimes, there is a performance price to pay
Improving the design
© Aarthi Natarajan, 2018 53
#5: Replacing conditional logic with Polymorphism
• The switch statement – an obvious problem, with two issues
• class Rental Is tightly coupled with class Movie – a switch
statement based on the data of another object – not a good
design
• There are several types of movies with its own type of charge,
hmm… sounds like inheritance
Improving the design
© Aarthi Natarajan, 2018 54
• A base class Movie class with method getPrice() and sub-
classes NewRelease, ChildrenMovie and Regular
• This allows us to replace switch statement with polymorphism
• Sadly, it has one flaw…a movie can change its classification during
its life-time
Improving the design
© Aarthi Natarajan, 2018 55
• Composition – reuse behaviour using one or more classes
with composition
• Delegation: delegate the functionality to another class
…this is the second time, this week we have said, we need
something more than inheritance
So, next …
• Design Principle: Favour composition over inheritance
• More refactoring techniques to solve our “switch” problem
– Replace type code with Strategy/State Pattern
– Move Method
– Replace conditional code with polymorphism
So, what options are there besides inheritance ?
© Aarthi Natarajan, 2018 56