3 oo principles

63
1 Object-Oriented Design Principles

Upload: bang-trinh

Post on 08-Sep-2015

238 views

Category:

Documents


3 download

DESCRIPTION

3-OO-Principles

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