Post on 08-Sep-2015
description
transcript
1
Object-Oriented Design
Principles
2
What goes wrong with software?
You start with a clear picture in your mind what the
software should be. The clarity of the design helps you to
get to the first release.
Then something goes wrong, the software starts to rot:
changes and additions are harder and harder to make,
you are forced to let go of the original design, eventually even the
simplest changes terrify you because of rippling unexpected effects,
and you must redesign the whole software.
You started with good intentions, so what went wrong ?
3
Bad signs, symptoms of rotting design
Rigidity (S cng nhc)
code difficult to change (no continuity)
changes propagate via dependencies
Fragility (S yu t)
even small changes can cause cascading effects
code breaks in unexpected places having no conceptual
relationship with the area that was changed (no protection)
Immobility (S khng linh ng)
code is so tangled that it's impossible to reuse anything
no composability
4
Bad signs, symptoms of rotting design
Viscosity
design preserving ways of working are harder than hacks
developement environment is slow and inefficient
Needless complexity (S phc tp khng cn thit)
design contains elements that are not currently useful
results from too much anticipating future needs
Opacity (S ti ngha)
the tendency of a module to become more difficult to
understand
a constant effort is needed to keep code readable
5
Causes of rotting design
Inevitably changing requirements
"All systems change during their life-cycles. This must be
born in mind when developing systems expected to last
longer than the first version."
Ivar Jacobson, OOSE 1992
Poor dependency management
Dependencies can be controlled
Tools:
OO-design principles, desing patterns
Guidelines: coupling, cohesion
6
OO Principles
To create reusable, extensible and maintainable OO systems
Underlie the patterns, help you to cope changes.
Design Principles from Design Pattern.
Encapsulate what varies
Favor Composition Over Inheritance
Program To An Interface, Not An Implementation
Principles of class design.
(SRP) The Single Responsibility Principle
(OCP) The Open Closed Principle
(LSP) The Liskov Substitution Principle
(DIP) The Dependency Inversion Principle
(ISP) The Interface Segregation Principle
7
Design Principles from Design Pattern
Principle 1
Encapsulate what varies
8
Encapsulate what varies
Separate what changes from what stays the same
Identify the aspects of application that vary and
separate them from what stay the same
Take the parts that vary and ancapsulate them, so
that later you can alter or extend without affecting
those that don't vary
9
Design Principles from Design Pattern
Principle 2
Favor Composition Over
Inheritance
10
Composition
"HAS-A" relationship
Method of reuse in which new functionality is
obtained by creating an object composed of other
objects
The new functionality is obtained by delegating
functionality to one of the objects being composed
Composition can be: "By reference" or "By
value"
11
Advantages/Disadvantages Of Composition
Advantages:
Contained objects are accessed by the containing class
solely through their interfaces
"Black-box" reuse, since internal details of contained
objects are not visible - Good encapsulation
Can be defined dynamically at run-time through objects
acquiring references to other objects of the same type
Disadvantages:
Resulting systems tend to have more objects
Interfaces must be carefully defined in order to use many
different objects as composition blocks
12
Inheritance
"IS-A" relationship
Method of reuse in which new functionality is
obtained by extending the implementation of an
existing object
The generalization class (the superclass) explicitly
captures the common attributes and methods
The specialization class (the subclass) extends the
implementation with additional attributes and
methods
13
Advantages/Disadvantages Of Inheritance
Advantages:
New implementation is easy, since most of it is inherited
Easy to modify or extend the implementation being reused
Disadvantages:
Breaks encapsulation, since it exposes a subclass to implementation details of its superclass
"White-box" reuse, since internal details of superclasses are often visible to subclasses
Subclasses may have to be changed if the implementation of the superclass changes
Implementations inherited from superclasses can not be changed at run- time
14
Inheritance vs Composition Example public class InstrumentedHashSet extends HashSet {
// The number of attempted element insertions
private int addCount = 0;
public InstrumentedHashSet(Collection c) { super(c); }
public InstrumentedHashSet(int initCap, float loadFactor){
super(initCap, loadFactor);
}
// overide method
public boolean add(Object o) {
addCount++;
return super.add(o);
}
// overide method
public boolean addAll(Collection c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
15
Inheritance vs Composition Example
Looks good, right. Lets test it! public static void main(String[] args) {
InstrumentedHashSet s = new InstrumentedHashSet();
s.addAll(Arrays.asList(new String[]
{"Snap","Crackle","Pop"}));
System.out.println(s.getAddCount());
}
We get a result of 6, not the expected 3. Why?
Because the internal implementation of addAll() in the HashSet superclass itself invokes the add() method.
So first we add 3 to addCount in InstrumentedHashSets addAll(). Then we invoke HashSets addAll(). For each element, this addAll() invokes the add() method, which as overridden by InstrumentedHashSet adds one for each element.
The result: each element is double counted.
16
Inheritance vs Composition Example
This is "fragile base class" problem:
Implementation details of our superclass affected the operation of our subclass. There are several ways to fix this.
Fix problem: use composition.
Lets write an InstrumentedSet class that is composed of a Set object. Our InstrumentedSet class will duplicate the Set interface, but all Set operations will actually be forwarded to the contained Set object.
InstrumentedSet is known as a wrapper class, since it wraps an instance of a Set object
This is an example of delegation through composition!
17
Inheritance vs Composition Example public class InstrumentedSet implements Set {
private final Set s;
private int addCount = 0;
public InstrumentedSet(Set s) {this.s = s;}
public boolean add(Object o) {
addCount++;
return s.add(o);
}
public boolean addAll(Collection c) {
addCount += c.size();
return s.addAll(c);
}
public int getAddCount() {return addCount;}
// Forwarding rest methods of the Set interface methods public void clear() { s.clear(); }
public boolean contains(Object o) { return s.contains(o); }
public boolean isEmpty() { return s.isEmpty(); }
...
}
Go To
Decorator
Pattern!
18
Inheritance/Composition Example
Agent- password- authorizationLevel
Passenger- frequendFlyerID- reservation
Person- name- address
Lm th no biu din i tng va l Passenger va
l Agent?
19
Inheritance/Composition Example
Passenger- frequendFlyerID- reservation
Person- name- address
Agent- password- authorizationLevel
Composition can resolve!
20
Inheritance/Composition Summary
Both composition and inheritance are important methods of reuse
Inheritance was overused in the early days of OO development
Over time we've learned that designs can be made more reusable and simpler by favoring composition
Of course, the available set of composable classes can be enlarged using inheritance
So composition and inheritance work together
But our fundamental principle is:
Favor Composition Over Inheritance
21
Design Principles from Design Pattern
Principle 3
Program To An Interface, Not
An Implementation
22
Interface
An interface is the set of methods one object knows it can
invoke on another object
An object can have many interfaces.
an interface is a subset of all the methods that an object implements.
A type is a specific interface of an object
Different objects can have the same type and the same object can have
many different types
An object is known by other objects only through its
interface
Interfaces are the key to pluggability!
23
Implementation vs Interface Inheritance
Implementation Inheritance (Class Inheritance)
an object's implementation is defined in terms of another's objects implementation
a mechanism for extending an application's functionality by reusing functionality in parent classes.
It lets you define a new kind of object rapidly in terms of an old one.
Interface Inheritance (Subtyping)
describes when one object can be used in place of another object
define families of objects with identical interfaces - usually by inheriting from an abstract class.
polymorphism depends on it.
24
Program to an interface, not an
implementation
Don't declare variables to be instances of particular concrete classes. Instead, commit only to an interface defined by an abstract class
Advantages:
Clients are unaware of the specific class of the object they use
One object can be easily replaced by another
Loosens coupling
Increases likelihood of reuse
Improves opportunities for composition since contained objects can be of any class that implements a specific interface
Disadvantages:
Modest increase in design complexity
25
Program to Interface Example
Program to implement:
Car c = new Car(); c.left();
Program to interface:
IManeuverable c = new Car(); c.left();
Hoc
public void travel(IManeuverable vehicle) { vehicle.setSpeed(35.0); vehicle.forward(); vehicle.left(); vehicle.climb(); }
Maneuver the vehicle without being concerned about what the actual class is (car, boat, submarine)
Car Submarine Boat
IManeuverable
+ left() + right() + forward() + reverse() + climb() + dive() + setSpeed() + getSpeed()
26
Principles of class design
Principle 1
Single Responsibility
Principle:
A class should have only one
reason to change
27
Single Responsibility Principle
Also known as cohesion
Rational behind the principle:
When the requirements change, that change will be manifest through a change in responsibility among the classes.
Requirement changes often are restricted to a few cohesive responsibilities
To avoid coupling responsibilities, a class should have only one responsibility, one reason to change
Violation of SRP causes dependencies between modules that are hard to anticipate (fragility)
28
What is a "responsibility"?
A responsibility is thought of being a larger set of
functionalities that serves one purpose.
Seeing the responsibilities of a class may be hard
You really can not understand the responsibilities of a class
without understanding the needs of the clients of that class.
Should look at responsibilities as "reasons to change",
i.e. how are possible changes in the needs of the clients reflected to
a single module or class.
A responsibility is an axis of change if that change
will really occur.
29
Example design that violates SRP
The Rectangle class has two responsibilities.
One is to provide a mathematical model of the geometry of a rectangle - area() method.
The other is to render the rectangle on a graphical user interface - draw() method.
30
SRP Example (con't)
A better design is to separate the two responsibilities into
two completely different classes
31
Separating the responsibility Example
Separating the responsibilities of modem into two
interfaces decouples the two responsibilities: Data Chanel
and Connection
32
Principles of class design
Principle 2
The Open-Closed Principle:
Software Entities Should Be Open
For Extension, Yet Closed For
Modification
33
The Open-Closed Principle
Attempt to design modules that never need to be changed
To extend the behavior of the system, we add new code. We do not modify old code.
Modules that conform to the OCP meet two criteria:
Open For Extension: The behavior of the module can be extended to meet new requirements
Closed For Modification: the source code of the module is not allowed to change
How can we do this?
Abstraction, Polymorphism, Inheritance, Interfaces
34
The Open-Closed Principle
It is not possible to have all the modules of a software system satisfy the OCP, but we should attempt to minimize the number of modules that do not satisfy it
The Open-Closed Principle is really the heart of OO design
The central guidance to cope with inevitable change, to create designs that are stable and well behaved in spite of change
Conformance to this principle yields the greatest level of reusability and maintainability
35
Open-Closed Principle Example
Consider the following method of some class:
public double totalPrice(Part[] parts) {
double total = 0.0;
for (int i=0; i
36
Open-Closed Principle Example (con't)
public double totalPrice(Part[] parts) {
double total = 0.0;
for (int i=0; i
37
Open-Closed Principle Example (con't)
Does this conform to the OCP? No way!
Every time the Accounting Department comes out
with a new pricing policy, we have to modify the
totalPrice() method! It is not Closed For
Modification. Obviously, policy changes such as
that mean that we have to modify code
somewhere, so what could we do?
To use our first version of totalPrice(), we
could incorporate pricing policy in the
getPrice() method of a Part
38
Open-Closed Principle Example (con't)
Here are example Part and ConcretePart classes:
// Class Part is the superclass for all parts.
public class Part {
private double price;
public Part(double price) (this.price = price;}
public void setPrice(double price) {this.price = price;}
public double getPrice() {return price;}
}
// Class ConcretePart implements a part for sale.
// Pricing policy explicit here!
public class ConcretePart extends Part {
public double getPrice() {
// return (1.45 * price); //Premium
return (0.90 * price); //Labor Day Sale
}
}
39
Open-Closed Principle Example (con't)
But now we must modify each subclass of Part whenever the pricing policy changes!
A better idea is to have a PricePolicy class which can be used to provide different pricing policies:
// The Part class now has a contained PricePolicy object.
public class Part {
private double price;
private PricePolicy pricePolicy;
public void setPricePolicy(PricePolicy pricePolicy) {
this.pricePolicy = pricePolicy;
}
public void setPrice(double price) {this.price = price;}
public double getPrice() {
return pricePolicy.getPrice(price);
}
}
40
Open-Closed Principle Example (con't) /**
* Class PricePolicy implements a given price policy.
*/
public class PricePolicy {
private double factor;
public PricePolicy (double factor) {
this.factor = factor;
}
public double getPrice(double price) {
return price * factor;
}
}
With this solution we can dynamically set pricing
policies at run time by changing the PricePolicy
object that an existing Part object refers to
41
Principles of class design
Principle 3
The Liskov Substitution Principle:
Functions That Use References To Base
(Super) Classes Must Be Able To Use
Objects Of Derived (Sub) Classes
Without Knowing It
42
The Liskov Substitution Principle
The Liskov Substitution Principle (LSP) seems obvious
given all we know about polymorphism
For example:
public void drawShape(Shape s) {
// Code here.
}
The drawShape method should work with any subclass of
the Shape superclass (or, if Shape is a Java interface, it
should work with any class that implements the Shape
interface)
But we must be careful when we implement subclasses to
insure that we do not unintentionally violate the LSP.
43
The Liskov Substitution Principle
If a function does not satisfy the LSP, then it
probably makes explicit reference to some or all of
the subclasses of its superclass.
Such a function also violates the Open-Closed
Principle, since it may have to be modified
whenever a new subclass is created.
44
LSP Example
Consider the following Rectangle class:
// A very nice Rectangle class.
public class Rectangle {
private double width;
private double height;
public Rectangle(double w, double h) {
width = w;
height = h;
}
public double getWidth() {return width;}
public double getHeight() {return height;}
public void setWidth(double w) {width = w;}
public void setHeight(double h) {height = h;}
public double area() {return (width * height);
}
45
LSP Example (con't)
Now, had about a Square class? Clearly, a square is a
rectangle, so the Square class should be derived from the
Rectangle class, right? Let's see!
Observations:
A square does not need both a width and a height as attributes, but it
will inherit them from Rectangle anyway. So, each Square object
wastes a little memory, but this is not a major concern.
The inherited and setHeight() methods are not really appropriate
for a Square, since the width and height of a square are identical. So
we'll need to override setWidth() and setHeight(). Having to override these simple methods is a clue that this might not be an
appropriate use of inheritance!
46
LSP Example (con't)
Here's the Square class:
// A Square class.
public class Square extends Rectangle {
public Square(double s) {super(s, s);}
public void setWidth(double w) {
super.setWidth(w);
super.setHeight(w);
}
public void setHeight(double h) {
super.setHeight(h);
super.setWidth(h);
}
}
47
LSP Example (con't)
Everything looks good. But check this out!
public class TestRectangle {
// Define a method that takes a Rectangle reference
public static void testLSP(Rectangle r) {
r.setWidth(4.0);
r.setHeight(5.0);
System.out.println("Width is 4.0 and "
+ "Height is 5.0 so Area is " + r.area());
if (r.area() == 20.0)
System.out.println("Looking good!\n");
else
System.out.println("Huh?? What kind of "
+ " rectangle is this??\n");
}
48
LSP Example (con't)
public static void main(String args[]) {
//Create a Rectangle and a Square
Rectangle r = new Rectangle(1.0, 1.0);
Square s = new Square(1.0);
// Now call the method above. According to the
// LSP, it should work for either Rectangles or
// Squares. Does it??
testLSP(r);
testLSP(s);
}
}
49
LSP Example (con't)
Test program output:
Width is 4.0 and Height is 5.0, so Area
is 20.0
Looking good!
Width is 4.0 and Height is 5.0, so Area
is 25.0
Huh?? What kind of rectangle is this??
Looks like we violated the LSP!
50
LSP Example (con't)
What's the problem here? The programmer of the testLSP() method made the reasonable assumption that changing the width of a Rectangle leaves its height unchanged.
Passing a Square object to such a method results in problems, exposing a violation of the LSP
The Square and Rectangle classes look self consistent and valid. Yet a programmer, making reasonable assumptions about the base class, can write a method that causes the design model to break down
Solutions can not be viewed in isolation, they must also be viewed in terms of reasonable assumptions that might be made by users of the design
51
LSP Example (con't)
A mathematical square might be a rectangle, but a
Square object is not a Rectangle object,
because the behavior of a Square object is not
consistent with the behavior of a Rectangle
object!
Behaviorally, a Square is not a Rectangle! A
Square object is not polymorphic with a
Rectangle object.
52
Liskov Substitution Principle
The Liskov Substitution Principle (LSP) makes it clear that the ISA relationship is all about behavior
In order for the LSP to hold (and with it the Open-Closed Principle) all subclasses must conform to the behavior that clients expect of the base classes they use
A subtype must have no more constraints than its base type, since the subtype must be usable anywhere the base type is usable
If the subtype has more constraints than the base type, there would be uses that would be valid for the base type, but that would violate one of the extra constraints of the subtype and thus violate the LSP!
The guarantee of the LSP is that a subclass can always be used wherever its base class is used!
53
Principles of class design
Principle 4
The 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
54
Dependency Inversion Principle
In other words: modules with detailed implementations are not depended upon, but depend themselves upon abstractions
LSP provides guidance for the use of public inheritance, violating LSP forces the client to violate OCP.
The structure that results from rigorous use of LSP and OCP is a principle in its own right, the DIP.
OCP states the goal; LSP enables it; DIP shows the mechanism to achieve the goal
55
Why call it "inversion" ?
Traditional software development tends to creatre structures where high-level modules depend on low-level modules, the policy (or business logic) gets to depend on details
procedure call hierarchies is an ultimate example
changes to low-level implementation modules propagate to high-level modules, which is unacceptable
reuse of high-level modules becomes very difficult
The dependency structure of a well designed OO software is inverted no propagation of change, easier reuse
High-level modules contain the important business model of the application, the policy
This high-level should be independent of details, they simply should not depend on details in any way.
The high-level policy setting modules should be the focus of reuse
greatest benefits are achievable here
This thinking is in the heart of framework design
56
Application of DIP: layering
57
Who owns the interface?
The common way is to have a module implementing a set of services and declaring a public interface for any client needing these services
services are seen as an OO library, and utility libraries own their interfaces
DIP often brings about an inversion of interface ownership in addition to inversion of dependencies
clients own the abstract interfaces, implementations implement what clients need
it is this inversion of ownership that protects the high-level layer from changes from lower layers
reusability of higher level layers is improved
frameworks operate in this manner
58
Finding the underlying abstraction
You should abstract out the high-level policy (hp ng), but how to find it?
You need experience, some guidelines
the main task of the system
the things that do not vary when details change
the metaphor of the system
a system inside the system
Example: button object and a lamp object
a Button senses its state in the environment and if it is "pressed", it turns the lamp object on or off, depending on whether the user has activated or deactivated the button
a naive design that violates the DIP the high level logic of Button depends on details of lamp
59
Finding the underlying abstraction
The underlying abstraction here is to detect an on/off gesture from the user and to rely that gesture to target object
users gesture can be anything, a physical button, a motion detector...
target object can be anything, a lamp in a room, an alarm device...
these details do not affect the abstraction
In a DIP conforming design
Button depends on something that provides abstract methods to turn anything on or off
Lamp is not any more depended upon, but depends on the abstract interface too
60
Finding the underlying abstraction
Notice that Lamp does not depend
on Button, even though it depends
on
ButtonServer.
Change the name of the interface to
SwitchableDevice to see that there
is no dependency on Button.
who owns the interface
SwitchableDevice? No-one, put it in an
own package (Java) or namespace
(C++) and it decouples the classes that
sense the environment from classes that
affect the external environment.
61
Conclusion
Traditional procedural programming creates a dependency structure in which policy depends on volatile detail
OO programming inverts this dependency structure
details and policies depend on abstraction
service interfaces are often owned by their clients
hallmark of good OO design however, it is possible to do procedural design with an OO language
DIP is crucial for the creation of reusable software frameworks
The three founding principles are closely related
Violating either LSP or DIP invariably results in violating OCP
LSP violations are latent violations of OCP
It is important to keep in mind these principles to get most out of OO development...
62
Interface Segregation Principle
Many client-specific interfaces are better than one general purpose interface
No fat interfaces. No non-cohesive interfaces.
Consequence:
impact of changes to one interface arent as big if interface is smaller
probability of a change reduces
clients do not depend on other clients needs
No interface pollution
Related to SRP
Clients should not be forced to depend
upon methods that they do not use.
63
Interface Segregation Principle
Separating the responsibilities
No separating
the responsibilities