2.2 Framework Techniques.

Slides:



Advertisements
Similar presentations
Technieken van de Software Architectuur, VUB ‘98-’99, Part 21 Part 2: Component and Framework Reuse Technieken van de Software Architectuur.
Advertisements

©Ian Sommerville 2000 Software Engineering, 6th edition. Chapter 12Slide 1 Software Design l Objectives To explain how a software design may be represented.
ITEC200 – Week03 Inheritance and Class Hierarchies.
1 Software Testing and Quality Assurance Lecture 12 - The Testing Perspective (Chapter 2, A Practical Guide to Testing Object-Oriented Software)
Inheritance and Class Hierarchies Chapter 3. Chapter 3: Inheritance and Class Hierarchies2 Chapter Objectives To understand inheritance and how it facilitates.
Stéphane Ducasse9.1 Inheritance Semantics and Lookup.
Stéphane Ducasse 1 The Taste of Smalltalk.
© 2005 Prentice Hall8-1 Stumpf and Teague Object-Oriented Systems Analysis and Design with UML.
Chapter 10 Class and Method Design
Chapter 10 Classes Continued
S.Ducasse Stéphane Ducasse 1 The Taste of Smalltalk.
REFACTORING Lecture 4. Definition Refactoring is a process of changing the internal structure of the program, not affecting its external behavior and.
An Introduction to Design Patterns. Introduction Promote reuse. Use the experiences of software developers. A shared library/lingo used by developers.
Object-Oriented Software Engineering Practical Software Development using UML and Java Chapter 6: Using Design Patterns 1.
CSCI-383 Object-Oriented Programming & Design Lecture 13.
Refactoring Deciding what to make a superclass or interface is difficult. Some of these refactorings are helpful. Some research items include Inheritance.
S.Ducasse Stéphane Ducasse savoie.fr e/ e/ 1 Inheritance Semantics and Method Lookup.
Refactoring 2. Admin Blackboard Quiz Acknowledgements Material in this presentation was drawn from Martin Fowler, Refactoring: Improving the Design of.
Factory Method Explained. Intent  Define an interface for creating an object, but let subclasses decide which class to instantiate.  Factory Method.
Stéphane Ducasse 1 Elements of Design - Inheritance/Compositio n.
Summing Up Object Oriented Design. Four Major Components: Abstraction modeling real-life entities by essential information only Encapsulation clustering.
Programming with Java © 2002 The McGraw-Hill Companies, Inc. All rights reserved. 1 McGraw-Hill/Irwin Chapter 5 Creating Classes.
Object Oriented Programming
Final Review. From ArrayLists to Arrays The ArrayList : used to organize a list of objects –It is a class in the Java API –the ArrayList class uses an.
Object-Oriented Software Engineering Practical Software Development using UML and Java Chapter 6: Using Design Patterns.
Design Patterns Software Engineering CS 561. Last Time Introduced design patterns Abstraction-Occurrence General Hierarchy Player-Role.
1 Chapter 5:Design Patterns. 2 What are design pattern?  Schematic description of design solution to recurring problems in software design and,  Reusable.
February, 2000Programming Technology Lab, Vrije Universiteit Brussel Component and Framework Reuse Dr. Tom Mens Programming Technology Lab Vrije Universiteit.
Inheritance and Class Hierarchies Chapter 3. Chapter 3: Inheritance and Class Hierarchies2 Chapter Objectives To understand inheritance and how it facilitates.
Inheritance and Class Hierarchies Chapter 3. Chapter Objectives  To understand inheritance and how it facilitates code reuse  To understand how Java.
Chapter 11: Advanced Inheritance Concepts. Objectives Create and use abstract classes Use dynamic method binding Create arrays of subclass objects Use.
PowerPoint Presentation for Dennis, Wixom, & Tegarden Systems Analysis and Design with UML, 5th Edition Copyright © 2015 John Wiley & Sons, Inc. All rights.
Module 9. Dealing with Generalization Course: Refactoring.
Stéphane Ducasse 1 Abstract Classes.
Object Design More Design Patterns Object Constraint Language Object Design Specifying Interfaces Review Exam 2 CEN 4010 Class 18 – 11/03.
Design Patterns Creational Patterns. Abstract the instantiation process Help make the system independent of how its objects are created, composed and.
CSCE 240 – Intro to Software Engineering Lecture 3.
Java Programming: Guided Learning with Early Objects Chapter 9 Inheritance and Polymorphism.
Design Patterns: MORE Examples
Sections Inheritance and Abstract Classes
Abstract Factory Pattern
Let’s Play Objects.
Chapter 5:Design Patterns
MPCS – Advanced java Programming
Inheritance and Polymorphism
2.3 Collaboration Contracts
Chapter 11 Object-Oriented Design
Behavioral Design Patterns
Object-Oriented Programming
About the Presentations
Object Oriented Programming in Java
Abstract Factory Pattern
TIM 58 Chapter 8: Class and Method Design
Intent (Thanks to Jim Fawcett for the slides)
Chapter 6: Using Design Patterns
Object Oriented Programming
Lecture 23 Polymorphism Richard Gesick.
Lecture 22 Inheritance Richard Gesick.
Week 6 Object-Oriented Programming (2): Polymorphism
Advanced Programming Behnam Hatami Fall 2017.
Java – Inheritance.
Object-Oriented Programming: Inheritance and Polymorphism
Reuse Contracts: Managing the Evolution of Reusable Assets
Object Oriented Analysis and Design
Object-Oriented PHP (1)
Final and Abstract Classes
Jim Fawcett CSE687 – Object Oriented Design Spring 2014
Refactoring.
C++ Object Oriented 1.
Presentation transcript:

2.2 Framework Techniques

Overview best practice of inheritance Interlude: Refactoring avoiding copy/paste reuse planning for reuse: self-sends abstract classes and template methods specialisation interfaces Interlude: Refactoring best practice of instantiation factory method abstract factory

Overview best practice of cooperation elimination of class-tests elimination of case-statements double dispatch technique badly placed code

Best Practice of Inheritance avoiding copy/paste reuse planning for reuse: self-sends abstract classes and template methods specialisation interfaces

Reusing the LAN New requirement: “Providing logging (or tracing) facilities: A message is printed, identifying the node and the packet, each time a packet is sent from a node”

Reuse: Copy/Paste Approach LAN System Node accept:thePacket self send:thePacket send:thePacket self nextNode accept:thePacket Logging LAN System Node accept:thePacket self send:thePacket send:thePacket Transcript show: …. self nextNode accept:thePacket copy/paste directly changing code

Copy/Paste Reuse The reused class and the original are not related anymore difficult to maintain: bug fixes and upgrades need to be propagated manually over all duplicates Proliferation of versions code duplication leads to even more code duplication, especially with reuse

Copy/Paste Reuse Avoid copy/paste reuse Avoid direct editing of reusable classes avoid editing of classes in libraries reuse existing classes by using inheritance and method overriding

OOP Solution create subclass LoggingNode of class Node override ‘send’ method

BUT? + Separate discussion (design patterns) How to combine ? Node Node + LoggingNode Printserver Workstation Separate discussion (design patterns) How to combine ? - multiple inheritance - mixin classes

Best Practice of Inheritance avoiding copy/paste reuse planning for reuse: self-sends abstract classes and template methods specialisation interfaces

Recap: self and super refer to the receiver of the currently executing method differ in where method-lookup starts self x super y A x B x y C x y

self in the Node class Node Node name name accept(p:Packet) send(p:Packet) send(p:Packet) SUPER when sending #accept: to an instance of Node LoggingNode send (p : Packet) when sending #accept: to an instance of LoggingNode

Planning for reuse: self-sends can be reused for logging same “external” functionality (send is protected !) cannot be reused for logging

Designing Classes for Reuse Narrow interface principle behavior of a class should be based on a minimal set of methods that can be overridden distinguish “core” behavior/methods from “peripheral” behavior/methods

Best Practice of Inheritance avoiding copy/paste reuse planning for reuse: self-sends abstract classes and template methods specialisation interfaces

Reusing the LAN New requirement: “Extend the LAN system so that documents of different types (Postscript, ASCII) can be printed on a corresponding printer”

Remember ...

Straightforward Approach Printserver subclass: #AsciiPrinter accept: thePacket thePacket addressee = self name and: [ thePacket contents isAscii ] ifTrue: [ self print: thePacket ] ifFalse: [ super accept: thePacket] print: thePacket Transcript show: ‘Packet with contents “, thePacket contents printString Printserver subclass: #PostscriptPrinter accept: thePacket thePacket addressee = self name and: [ thePacket contents isPostscript ] ifTrue: [ self print: thePacket ] ifFalse: [ super accept: thePacket] print: thePacket Transcript show: ‘Packet with contents “, thePacket contents interpretString

Problems may lead to classes that are hard to understand subtle interaction between self and super very deep narrow inheritance hierarchies that are hard to understand Inhibits further reuse and maintenance no abstraction is made of the commonalities between ASCII and Postscript printers

OOP Solution template method Node subclass: #AbstractPrintserver accept: thePacket self isDestinationFor: thePacket ifTrue: [ self print: thePacket ] ifFalse: [ super accept: thePacket] print: thePacket self subclassResponsibility isDestinationFor: thePacket ^thePacket addressee = self name abstract method concrete method

Abstract classes

Reuse Abstract classes AbstractPrintserver subclass: #AsciiPrinter print: thePacket Transcript show: ... isDestinationFor: thePacket ^super isDestinationFor: thePacket and: [ thePacket contentx isAscii ] AbstractPrintserver subclass: #PostscriptPrinter print: thePacket Transcript show: ... isDestinationFor: thePacket ^super isDestinationFor: thePacket and: [ thePacket contentx isPostscript ]

Best Practice of Inheritance avoiding copy/paste reuse planning for reuse: self-sends abstract classes and template methods specialisation interfaces

Which methods to override ? default methods e.g. isDestinationFor: abstract methods e.g. print: extending methods with new behaviour what about overriding template methods ? under what conditions can we override accept ?

Specialisation Interface AbstractPrintServer accept(p:Packet) {*accept, isDestinationFor, print} isDestinationFor(p:Packet) print(p:Packet) client interface: documents the messages that can be sent interface to message passing clients encapsulates the implementation from message passing clients specialisation interface: documents the messages that are sent from an object to itself interface to inheriting clients encapsulates the implementation from inheriting clients

Interpreting the Specialisation Interface (1) Object subclass: #Node Node methodsFor: 'input-output' accept:thePacket self send:thePacket Node methodsFor: 'private' send:thePacket self nextNode accept:thePacket implementation must invoke send Node name accept(p:Packet) {send} send(p:Packet) {nextNode} nextNode Specialisation interfaces “reify” part of the implementation - they link design to implementation Different parts of the implementation can be reified - which parts are interesting to inheritors ? - only essential parts should be documented

Interpreting the Specialisation Interface (2) specialisation clause = UML constraint AbstractPrintServer accept(p:Packet) {*accept, isDestinationFor, print} isDestinationFor(p:Packet) print(p:Packet) super send Specialisation interface = set of all specialisation clauses

Specialisation Interfaces Reify Only Part of the Implementation Object subclass: #Node Node methodsFor: 'input-output' accept:thePacket false ifTrue: [self send:thePacket] implementation must invoke send Node name accept(p:Packet) {send} send(p:Packet) {nextNode} nextNode Different interpretations are possible source level: “self send:...” occurs somewhere in method body run time: “send” must be sent to self in any possible method execution we take the simpler source level interpretation

Using Specialisation Interfaces Terminology for talking about abstract classes and method overriding Designing abstract classes Assess what methods can be overridden

Terminology AbstractPrintServer accept(p:Packet) {*accept, isDestinationFor, print} isDestinationFor(p:Packet) print(p:Packet) A template method is a method that refers to an abstract method in its specialisation interface Core methods are methods with an empty specialisation interface ...

Designing Abstract Classes AbstractPolygon sidesDo(aBlock) circumference {sidesDo} Square PolyLine sidesDo(aBlock) sidesDo(aBlock) circumference {}

Overriding Methods AbstractPolygon UML constraints sidesDo(aBlock) circumference {sidesDo} {Concretisation = sidesDo} {Concretisation = sidesDo} {Coarsening = circumference(-sidesDo)} Square PolyLine sidesDo(aBlock) sidesDo(aBlock) circumference {}

Overriding & introducing methods Node accept(p:Packet) {send} send {Refinement = accept(+isDestinationFor,+print)} {Extension = isDestinationFor, print} AbstractPrintServer accept(p:Packet) {send, isDestinationFor, print} isDestinationFor(p:Packet) print(p:Packet) send

Documenting Inheritance Concretisation: overriding abstract methods with concrete methods Abstraction: overriding concrete methods with abstract methods Refinement: adding message names to the specialisation interface Coarsening: removing message names from the specialisation interface Extension: adding new methods to the client interface Cancellation: removing methods from the client interface 19

Specialisation Interfaces documenting the interface for inheritors basic terminology for inheritance of abstract classes basis for reasoning about inheritance OO is powerful, it has to be used with care.

Interlude: Refactoring Refactoring = reorganization plan for a system to improve reuse A refactoring is behavior preserving A refactoring has a precondition Some refactorings are based on class invariants Refactoring into object-oriented frameworks turning an OO application into a framework

Why Refactoring? Abstract classes and frameworks are generalizations People think concretely, not abstractly Abstractions are found bottom up, by examining concrete examples

Why Refactoring? Generalization proceeds by: finding things that are given different names but are really the same (and thus renaming them) parameterizing to eliminate differences breaking large things into small things so that similar components can be found

Refactoring: Abstraction concrete class A concrete class B abstraction concrete class B concrete class A abstract class X

Refactoring: Abstraction Creating an Abstract Superclass Create a common superclass Make method signatures compatible Add method signatures to the superclass Make method bodies compatible Make instance variables compatible Move instance variables to the superclass Migrate common code to the abstract superclass

Refactoring: Abstraction AbstractPrinter PostscriptPrinter AsciiPrinter AsciiPrinter PostscriptPrinter

Best Practice of Instantiation factory methods abstract factory

Reusing the LAN How to instantiate objects in a reusable, maintainable and adaptable way?

Straightforward Approach Object subclass: LANExample intialize ws1 = Workstation new. ws2 = Workstation new. ps1 = PostscriptPrinter new

Problems names of classes are hardcoded objects are created throughout the whole system what if subclasses are introduced? where in the code are objects created? how to instantiate objects of subclasses without editing existing classes?

OOP Solution abstract the object-creation process do not explicitly create objects inside a method (e.g. do not use ‘new’) provide hooks for a class to create the appropriate objects define methods that do nothing else but instantiating an object = factory methods

Factory methods Object subclass: LANExample intialize ws1 = self newWorkstation. ws2 = self newWorkstation. ps1 = self newPostscriptPrinter newWorkstation ^Workstation new newPostscriptPrinter ^PostscriptPrinter new

Consequences use inheritance and method overriding to instantiate objects of other classes class names are only hard coded in predefined places system is freed of creation code LanExample subclass: OtherLANExample newWorkstation ^LoggingWorkstation new newPostscriptPrinter ^LoggingPostscriptPrinter new

Best Practice of Instantiation factory methods abstract factory

Reusing the LAN How to ensure that a system consistently uses only one family of related objects?

Not So Straightforward Approach use factory methods! Object subclass: LANExample intialize ws1 = self newWorkstation. ws2 = self newWorkstation. ps1 = self newPostscriptPrinter newWorkstation ^Workstation new newPostscriptPrinter ^PostscriptPrinter new LanExample subclass: OtherLANExample newWorkstation ^LoggingWorkstation new newPostscriptPrinter ^LoggingPostscriptPrinter new

Problems what if we forget to override a method? objects will be mixed there is no enforcement factory methods may or may not be overridden factory methods rely on the programmer to not make mistakes

Abstract Factory define a class which is responsible for creating the right objects class consists of nothing but factory methods delegate creation of objects to an instance of this newly defined class

Abstract Factory Object subclass: AbstractLANFactory newWorkstation ^self subclassResponsibility newPostscriptPrinter AbstractLANFactory subclass: #DefaultLANFactory newWorkstation ^Workstation new newPostscriptPrinter ^PostscriptPrinter new AbstractLANFactory subclass: #LoggingLANFactory newWorkstation ^LoggingWorkstation new newPostscriptPrinter ^LoggingPostscriptPrinter new

Consequences abstract the object-creation process provide hooks for a class to create the appropriate objects no risk of mixing two families of objects objects are created in only one place improves maintainability, readability, reusability, ...

But ... how to instantiate Factory objects? use factory method use Singleton design pattern (see later)

Refactorings for factory methods spot creation code inside ordinary methods move this code inside its own (factory) method replace code in original method by invocation of factory method

Refactorings for abstract factory spot factory methods that always get overridden together by subclasses move these methods into a new class replace invocation of factory methods by invocation of appropriate method on factory object

Best Practice of Cooperation elimination of class-tests elimination of case-statements double dispatch technique Law of Demeter badly placed code

Reusing the LAN New requirement: “Providing more than one destination for a packet allows the sender of a packet to print a document either on #printer1 or #printer2, depending on which printer is first encountered in the local area network.” Remember: #printer1 and #printer2 are Smalltalk symbols that are used for identifying nodes in the LAN.

Straightforward Approach Node subclass: #AbstractPrintserver accept: thePacket self isDestinationFor: thePacket ifTrue: [ self print: thePacket ] ifFalse: [ super accept: thePacket] isDestinationFor: thePacket ^thePacket addressee = self name or: [ thePacket addressee = #* ] wildcard symbol

Problems Solution is not very general what about #prin* (any printer name that starts with #prin Solution is brittle towards change based on a convention (e.g. wildcards) adding a new kind of address requires editing the AbstractPrintserver class (and possibly others)

OOP Solution Objectify addresses

OOP Solution Delegate responsibilities Does this work ?

smalltalk recap: polymorphism objects respond to messages different objects can respond differently to the same message KWAAK Speak WOEF Speak

Polymorphic Addresses isDestinationFor: WildcardAddress isDestinationFor: Address AbstractPrintserver accept: Packet print:Packet isDestinationFor:anAddress ^true NodeAddress id:Symbol isDestinationFor: Address = Address isDestinationFor:anAddress ^self = anAddress

Planning for Reuse: Delegation can be reused for different addressing schemes (wildcards, domains, ...) same “external” behavior cannot be reused for different addressing schemes

Delegation of Responsibilities avoids case statements (especially over class types) Example: Address Printserver should not depend on representation of addresses Printserver must delegate address equality checking

Delegation of Responsibilities Names are very important Wrong ASCIIPrinter printAsciiFile PostscriptPrinter printPostsciptFile Right ASCIIPrinter printFile PostscriptPrinter printFile Designing good interfaces is important objects with same interfaces can be substituted

Reusing the LAN New requirement: “Extend the LAN system so that packets can be broadcasted.”

Straightforward Approach

Problems bad coding practice Inhibits further reuse and maintenance class tests should be avoided needs modification of all node classes than can serve as addressee Inhibits further reuse and maintenance what about packets that count the number of hops?

OOP Solution template method abstract method

Class Collaboration Design for a set of classes that collaborate to accomplish a certain task emphasis on the interaction between class instances Possibly consisting of abstract classes template and abstract methods need not reside in the same class Class collaboration can be reused all abstract classes must be filled in with concrete ones first

Reusing Class Collaboration collaboration instance: printing collaboration instance: broadcasting

Reusing the LAN New requirement: “Providing a Document class which represents documents to be printed on a printer.”

Straightforward Approach Object subclass: #Document instanceVariables: ‘contents’ printOn: aPrinter aPrinter print: (self newPacketWithContents: self contents) newPacketWithContents: contents ^Packet new; contents: contents

But ... contents can be ASCII as well as Postscript as well as other types but ... ASCII documents cannot contain figures Postscript and PDF documents can

What is often seen Object subclass: Document instanceVariables: ‘contents’ includeFigure: aFigure self isAsciiDocument ifTrue: [ Transcript show: ‘Error ...’ ] self isPostscriptDocument ifTrue: [ ... ] self isPDFDocument printOn: aPrinter ....

OOP Solution Object subclass: #AbstractDocument instanceVariables: ‘contents’ includeFigure: aFigure self subclassResponsibility AbstractDocument subclass: #AsciiDocument includeFigure: aFigure Transcript show: ‘Error ...’ AbstractDocument subclass: #PostscriptDocument includeFigure: aFigure “code to include the figure”

Refactoring for Specialization Motivation : the design of a framework can be improved by decomposing a large, complex class into several smaller classes the complex class usually embodies both a general abstraction and several different concrete cases that are candidates for specialization

Refactoring: Specialization Specialize a class by adding subclasses corresponding to the conditions in a conditional expression: choose a conditional whose conditions suggest subclasses (this depends on the desired abstraction) for each condition, create a subclass with a class invariant that matches the condition copy the body of the condition to each subclass, and in each class simplify the conditional based on the invariant that is true for the subclass specialize some (or all) expressions that create instances of the superclass

Refactoring: Specialization Disk Management for MSDOS Class Invariant Disk ... copyDisk formatDisk Disk Management for MSDOS+MAC Disk ... copyDisk formatDisk Disk Management for MSDOS+MAC Disk disktype ... copyDisk formatDisk formatDisk self diskType = #MSDOS ifTrue: [ .. code1 ..]. self diskType = #MAC ifTrue: [ .. code2 ..]. MSDOSDisk ... copyDisk formatDisk MACDisk ... copyDisk formatDisk

Best Practice of Cooperation elimination of class-tests elimination of case-statements double dispatch technique badly placed code

Reusing the LAN New requirement: “Make sure documents of a certain type are printed on a corresponding printer.”

Straightforward Approach Object subclass: #AbstractDocument instanceVariableNames: ‘contents’ printOn: aPrinter self subclassResponsibility AbstractDocument subclass: #PostscriptDocument printOn: aPrinter aPrinter class = PostscriptPrinter ifTrue: [ aPrinter print: self newPacketWithContents: self contents ] AbstractDocument subclass: #ASCIIDocument printOn: aPrinter aPrinter class = ASCIIPrinter ifTrue: [ aPrinter print: self newPacketWithContents: self contents ]

Problems class-testing should be avoided what if a subclass of ASCIIPrinter is introduced? what if a printer is introduced that can print both ASCII and Postscript?

Observation the way a document should be printed does not only depend on the type of the document, but also on the type of the printer! delegation by itself cannot solve this problem

Problem late binding polymorphism only takes into account the type of the receiver single dispatch solution to our problem should also take into account the type of the argument double dispatch!

OOP Solution AbstractPrintserver subclass: #PostscriptPrinter printPostsciptDocument: aPSDocument Transcipt show: aPSDocument contents interpret printAsciiDocument: anASCIIDocument Transcipt show: ‘Error’ AsbtractPrintserver subclass: #ASCIIPrinter printPostsciptDocument: aPSDocument Transcipt show: ‘Error’ printAsciiDocument: anASCIIDocument Transcipt show: anASCIIDocument contents print AbstractDocument subclass: #ASCIIDocument printOn: aPrinter aPrinter printASCIIDocument: self AbstractDocument subclass: #PostscriptDocument printOn: aPrinter aPrinter printPostscriptDocument: self

Consequences results in more reusable, adaptable and maintainable code avoids class-testing avoids case-statements

Best Practice of Cooperation elimination of class-tests elimination of case-statements double dispatching badly placed code

Reusing the LAN New requirement: “Return the number of characters after a document is printed.”

Straightforward Approach AddressableNode subclass: #AbstractPrintserver countChars: aDocument ^aDocument contents numberOfChars AbstractPrintserver subclass: #PostscriptPrinter printPostsciptDocument: aPSDocument Transcipt show: aPSDocument contents interpret. ^self countChars: aPSDocument AbstractPrintserver subclass: #ASCIIPrinter printAsciiDocument: anASCIIDocument Transcipt show: anASCIIDocument contents print. ^self countChars: anASCIIDocument

Problems countChars method does not use the receiver no instance variables are used no instance methods are called indication that the method is defined on the wrong class

OOP Solution AbstractDocument subclass: #ASCIIDocument printOn: aPrinter aPrinter printASCIIDocument: self. ^self contents countChars AbstractDocument subclass: #PostscriptDocument printOn: aPrinter aPrinter printPostscriptDocument: self. ^self contents countChars