CSc 668/868: Object Oriented Programming © 2008 by Dr. Barry Levine 1 TOPICS .................................................................................................................................................................. 8 Note on Group Work ...................................................................................................................................... 11 CSC 868 Oral Presentations ......................................................................................................................... 12 AN INTRODUCTION TO OBJECT-ORIENTED ANALYSIS AND DESIGN................................................... 14 MACRO LEVEL PROCESS ..................................................................................................................................... 21 OBJECT-ORIENTED ANALYSIS ............................................................................................................................. 22 OBJECT-ORIENTED DESIGN ................................................................................................................................. 22 IMPLEMENT A STORE/POST (POINT OF SALE TERMINAL) SYSTEM ............................................................ 23 POST .................................................................................................................................................................. 24 DATABASES ......................................................................................................................................................... 25 OBJECT-ORIENTED ANALYSIS ............................................................................................................................. 26 CONCEPTUAL MODEL FOR POST (OBJECTS FOUND IN THE REAL WORLD – NOUNS DRAWN FROM THE REQUIREMENTS/SPECIFICATION) .......................................................................................................................... 28 OBJECT-ORIENTED DESIGN ................................................................................................................................. 30 HIGH-LEVEL STAGES IN THE STORE/POST SOFTWARE SYSTEM: ....................................................................... 31 POST ASSIGNMENTS........................................................................................................................................... 32 SAMPLE FILE I/O API .......................................................................................................................................... 34 USE CASE VIEW .................................................................................................................................................. 38 LOGICAL VIEW .................................................................................................................................................... 38 RELATIONSHIPS ................................................................................................................................................... 38 COMPONENT VIEW .............................................................................................................................................. 39 SOME NOTES ON JAVA ...................................................................................................................................... 40 THIS IN JAVA........................................................................................................................................................ 40 this Used in Method Calls.............................................................................................................................. 41 ENUMERATED TYPES AND TYPE SAFETY ............................................................................................................ 42 ARCHITECTURAL DESIGN (APPENDIX 2) ..................................................................................................... 43 ARCHITECTURAL PATTERNS ................................................................................................................................ 43 Model-View-Control ...................................................................................................................................... 43 Client-Server .................................................................................................................................................. 43 Reflection ....................................................................................................................................................... 44 Layers ............................................................................................................................................................ 44 PACKAGE STRUCTURES ....................................................................................................................................... 44 JAVA'S REMOTE METHOD INVOCATION - RMI........................................................................................... 46 DISTRIBUTED GARBAGE COLLECTION ................................................................................................................. 49 OBJECT REGISTRATION ....................................................................................................................................... 49 RMI EXAMPLE - ACCOUNTS USED BY ATM'S ..................................................................................................... 49 Steps to run this example (JDK 1.5 on Windows XP): ................................................................................ 53 RMI EXAMPLE - REMOTE METHODS RETURNING REMOTE OBJECTS .................................................................... 55 Steps to run this example (JDK 1.5 on Windows XP): ................................................................................ 59 RMI CLIENT-SERVER IMPLEMENTATION OF THE POST PROBLEM ..................................................... 62 MAIN OBJECTS SHIPPED FROM SERVER (STORE) TO CLIENT (POST) .................................................................. 62 MAIN OBJECTS SHIPPED FROM CLIENT TO SERVER ............................................................................................. 62 OBJECTS ON CLIENT (POST) ............................................................................................................................... 63 OBJECTS ON SERVER (STORE) ............................................................................................................................. 63 COMMENTS ......................................................................................................................................................... 63 OBJECT ORIENTED PROGRAMMING PRINCIPLES .................................................................................... 64 DOCUMENTATION................................................................................................................................................ 64 2 MESSAGE PASSING VERSUS FUNCTION CALLS ...................................................................................................... 64 QUALITY OF AN INTERFACE ................................................................................................................................. 65 INHERITANCE (TYPE/SUBTYPE RELATIONSHIP)..................................................................................................... 67 SUBCLASSING HEURISTICS ................................................................................................................................... 67 SOFTWARE REUSE ............................................................................................................................................... 70 SUBCLASS/SUBTYPES .......................................................................................................................................... 71 POLYMORPHIC VARIABLES .................................................................................................................................. 72 REVERSE POLYMORPHISM ................................................................................................................................... 72 REPLACEMENT VS REFINEMENT .......................................................................................................................... 74 IMPLEMENTATION ISSUES .................................................................................................................................... 76 COVARIANCE AND CONTRAVARIANCE (OVERRIDING METHODS) ......................................................................... 78 COVARIANT RETURN TYPES ................................................................................................................................ 78 MULTIPLE INHERITANCE ..................................................................................................................................... 79 POLYMORPHISM (DEALS WITH TYPES) ................................................................................................................. 80 FORMS OF POLYMORPHISM ................................................................................................................................. 80 VISIBILITY AND DEPENDENCE ............................................................................................................................. 82 COUPLING AND COHESION................................................................................................................................... 82 LAW OF DEMETER ........................................................................................................................................... 85 OVERRIDING VERSUS SHADOWING ...................................................................................................................... 87 OVERRIDING: ACCESSIBILITY AND EXCEPTIONS .................................................................................................. 88 A NOTE ON PROTECTED VARIABLES AND METHODS ........................................................................................... 90 FRAMEWORKS AND PATTERNS ............................................................................................................................ 91 ANOTHER LOOK AT CLASSES .............................................................................................................................. 92 OOP AND SOFTWARE ENGINEERING ............................................................................................................ 93 ABSTRACTION ................................................................................................................................................ 93 INFORMATION HIDING ................................................................................................................................. 93 ABSTRACTION AND INFORMATION HIDING ........................................................................................... 94 LOCALIZATION ............................................................................................................................................... 94 SOFTWARE ENGINEERING CONCEPTS......................................................................................................... 95 SURFACE AREA............................................................................................................................................... 95 FACTORS INFLUENCING SURFACE AREA................................................................................................. 95 HARDWARE IC'S: ............................................................................................................................................. 95 SOFTWARE IC'S: .............................................................................................................................................. 95 SOFTWARE QUALITY .................................................................................................................................... 96 MODULES ......................................................................................................................................................... 97 TOP-DOWN VS OBJECT-ORIENTED DESIGN ............................................................................................. 98 UNIT TESTING ..................................................................................................................................................... 99 SQUEAK/SMALLTALK - 80 ............................................................................................................................... 101 IDENTIFIERS: ..................................................................................................................................................... 102 MESSAGE EXPRESSIONGS .................................................................................................................................. 102 SELF................................................................................................................................................................. 103 CLASS VARIABLES............................................................................................................................................. 104 GENERATORS .................................................................................................................................................... 105 CLASSES AND METACLASSES ................................................................................................................... 107 REFLECTION AND PERSISTENCE (APPENDIX 5) ...................................................................................... 116 REFLECTION ...................................................................................................................................................... 116 REFLECTIVE SYSTEMS ....................................................................................................................................... 116 STRATEGIES....................................................................................................................................................... 119 Static Structure ............................................................................................................................................ 120 RUNTIME CLASSES ............................................................................................................................................ 121 3 Runtime Type Information in Java .............................................................................................................. 122 DYNAMIC INSTANTIATION ................................................................................................................................. 123 Dynamic Instantiation in Java ..................................................................................................................... 124 Dynamic Instantiation in C++ (The Prototype Pattern) ............................................................................. 124 PERSISTENCE ..................................................................................................................................................... 126 Databases .................................................................................................................................................... 126 DATA STREAMS ................................................................................................................................................. 129 Output Streams ............................................................................................................................................ 129 Input Streams ............................................................................................................................................... 130 OBJECT STREAMS .............................................................................................................................................. 130 DATABASES ....................................................................................................................................................... 136 Connection ................................................................................................................................................... 137 Statements .................................................................................................................................................... 137 Result Set ..................................................................................................................................................... 138 PROGRAMMING AND DESIGN PRINCIPLES ............................................................................................... 140 CHALLENGES IN MODERN PROGRAMMING ......................................................................................................... 140 Coupling and variabilities ........................................................................................................................... 140 WHAT IS A GOOD DESIGN? ................................................................................................................................. 140 DOCUMENTING POLICY...................................................................................................................................... 140 OBJECT ORIENTED ANALYSIS AND DESIGN ....................................................................................................... 141 EVENT NOTIFICATION (APPENDIX 3) ......................................................................................................... 144 OVERVIEW ........................................................................................................................................................ 144 DESIGN PATTERNS ............................................................................................................................................ 144 THE PUBLISHER-SUBSCRIBER PATTERN ............................................................................................................ 146 Dynamic Structure ....................................................................................................................................... 147 The Publisher-Subscriber Pattern in Java................................................................................................... 148 Example: Monitoring Devices (continued) .................................................................................................. 148 JAVA FOUNDATION CLASSES (JFC) ................................................................................................................... 151 CONSTRAINT NETWORKS .................................................................................................................................. 153 PROGRAMMING NOTE ....................................................................................................................................... 157 SOME IMPORTANT DESIGN PATTERNS ...................................................................................................... 160 MODEL-VIEW-CONTROLLER ............................................................................................................................. 160 ADAPTOR PATTERN ........................................................................................................................................... 160 CHAIN OF RESPONSIBILITY PATTERN ................................................................................................................. 160 DECORATOR PATTERN ...................................................................................................................................... 160 OBSERVER PATTERN ......................................................................................................................................... 160 PROXY PATTERN ............................................................................................................................................... 161 STRATEGY PATTERN ......................................................................................................................................... 161 STREAMS PATTERN (PIPES AND FILTERS, DATAFLOW) ...................................................................................... 161 REFERENCES ..................................................................................................................................................... 161 REFACTORING TO PATTERNS ....................................................................................................................... 162 1. COMPOSE METHOD REFACTORING ................................................................................................................ 162 Example ....................................................................................................................................................... 162 Benefits and Liabilities ............................................................................................................................... 164 2. REPLACE CONDITIONAL LOGIC WITH STRATEGY ........................................................................................... 165 Example ....................................................................................................................................................... 165 Benefits and Liabilities ................................................................................................................................ 168 3. REPLACE CONDITIONAL DISPATCHER WITH COMMAND ................................................................................. 169 Example ....................................................................................................................................................... 169 Benefits and Liabilities ................................................................................................................................ 172 4. REPLACE TYPE CODE WITH CLASS ................................................................................................................ 173 4 Example ....................................................................................................................................................... 173 Benefits and Liabilities ................................................................................................................................ 174 FRAMEWORKS. TOOLKITS, AND POLYMORPHISM (APPENDIX 4)....................................................... 175 OVERVIEW ........................................................................................................................................................ 175 FRAMEWORKS ................................................................................................................................................... 175 Example: Application Frameworks ............................................................................................................. 176 Example: Client-Server Frameworks .......................................................................................................... 176 OBJECT-ORIENTED CONCEPTS .......................................................................................................................... 177 Polymorphism .............................................................................................................................................. 177 Abstraction .................................................................................................................................................. 178 WORKING WITH UNKNOWN CLASSES ................................................................................................................ 181 MFW: A Framework for Music Applications .............................................................................................. 182 Heterogeneous Collections .......................................................................................................................... 182 Virtual Factory Methods.............................................................................................................................. 183 FACTORIES IN JAVA ........................................................................................................................................... 185 Inner Classes ............................................................................................................................................... 185 Local Classes ............................................................................................................................................... 186 Anonymous Classes ..................................................................................................................................... 186 Closures, Functors, and Thunks ................................................................................................................ 187 POWER TYPES AS FACTORIES ............................................................................................................................ 189 TOOLKITS .......................................................................................................................................................... 191 Generic Methods ......................................................................................................................................... 193 Singletons .................................................................................................................................................... 194 EXAMPLE: A SIMPLE APPLICATION FRAMEWORK .............................................................................................. 195 Calculator .................................................................................................................................................... 195 THE CONSOLE FRAMEWORK ............................................................................................................................. 196 EXAMPLE: PIPELINES ......................................................................................................................................... 199 PIPES: A Pipeline Toolkit .......................................................................................................................... 202 PROGRAMMING NOTES ...................................................................................................................................... 207 Stereotypes ................................................................................................................................................... 207 The Java Collections Framework ................................................................................................................ 208 MORE ON DESIGN PATTERNS ....................................................................................................................... 209 ADAPTOR PATTERN ........................................................................................................................................... 210 FAÇADE PATTERN ............................................................................................................................................. 212 COMPARISON BETWEEN FAÇADE AND ADAPTOR PATTERNS .............................................................................. 213 ENCAPSULATION ............................................................................................................................................... 214 BRIDGE PATTERN .............................................................................................................................................. 216 COMPOSITE PATTERNS ...................................................................................................................................... 218 DELEGATION (APPENDIX 6) .......................................................................................................................... 219 OBJECTS AS STATES .......................................................................................................................................... 219 DELEGATION DEFINED ...................................................................................................................................... 221 Adapters ....................................................................................................................................................... 221 HANDLE-BODY IDIOMS ..................................................................................................................................... 223 Shared Bodies .............................................................................................................................................. 224 PRESENTATION AND CONTROL (APPENDIX 7) ......................................................................................... 228 THE MODEL-VIEW-CONTROLLER ARCHITECTURE ............................................................................................ 228 Static Structure ............................................................................................................................................ 229 GUI TOOLKITS .................................................................................................................................................. 230 Views and View Notification ........................................................................................................................ 230 Example: MFC's Document-View Architecture (Develop word processor) ................................................ 231 BUILDING AN APPLICATION FRAMEWORK (AFW) ............................................................................................. 234 5 AFW 1.0: A CUI APPLICATION FRAMEWORK ................................................................................................... 234 Design .......................................................................................................................................................... 234 Implementation ............................................................................................................................................ 235 Example: Account Manager ........................................................................................................................ 239 AFW 2.0: A GUI APPLICATION FRAMEWORK WITH MULTIPLE VIEWS .............................................................. 241 Design .......................................................................................................................................................... 241 Implementation ............................................................................................................................................ 241 COMMANDS AND COMMAND PROCESSORS ....................................................................................................... 246 AFW 3.0: A GUI APPLICATION FRAMEWORK WITH A COMMAND PROCESSOR ................................................. 248 DESIGN .............................................................................................................................................................. 248 Implementation ............................................................................................................................................ 249 EXAMPLE: POLYGON VIEWER CUSTOMIZATION OF VERSION 3.0 OF AFW ........................................................ 257 Design .......................................................................................................................................................... 260 Implementation ............................................................................................................................................ 260 MEMENTOS ....................................................................................................................................................... 262 Static Structure ............................................................................................................................................ 262 RESOURCE MANAGERS ..................................................................................................................................... 263 INTEGRATING PATTERNS IN THE DESIGN OF A HIERARCHICAL FILE SYSTEM ............................ 265 FOUR MAIN BENEFITS OF PATTERNS:.................................................................................................................. 265 GOALS FOR THE SYSTEM: .................................................................................................................................. 265 APPLICATION OF THE COMPOSITE PATTERN ...................................................................................................... 266 Intent of the Composite Pattern: .................................................................................................................. 268 Applicability Section - use Composite when ................................................................................................ 268 Directory...................................................................................................................................................... 269 Include a mkdir command ........................................................................................................................... 269 mkdir version 1 - problems: ......................................................................................................................... 270 mkdir version 2 - simplification via uniform treatment: .............................................................................. 271 mkdir version 3 - use of downcasting: ......................................................................................................... 272 ADDING SYMBOLIC LINKS: PROXY PATTERN .................................................................................................... 273 FILE SYSTEM CLASS STRUCTURE WITH COMPOSITE AND PROXY ...................................................................... 275 VISITOR PATTERN ............................................................................................................................................. 276 Visitor Code With No Common Operation Behavior................................................................................... 277 Visitor Code With Common Operation Behavior ........................................................................................ 279 Modifications to Code Using Visitor ........................................................................................................... 280 Visitor Benefit .............................................................................................................................................. 280 DELETING FILES AND DIRECTORIES WITHIN A SINGLE USER PROTECTION FRAMEWORK (E.G. SINGLE USER ON A PC, NOT A MULTI-USER SYSTEM UNDER E.G. UNIX) - THE TEMPLATE PATTERN ............................................... 281 SUMMARY OF DESIGN PATTERNS USED IN THE FILE SYSTEM DESIGN ............................................................... 285 ACTIVE AND DISTRIBUTED OBJECTS (APPENDIX 8) .............................................................................. 286 MULTI-THREADING ........................................................................................................................................... 286 IMPLEMENTING AND SCHEDULING THREADS ..................................................................................................... 286 INTER-THREAD COMMUNICATION ..................................................................................................................... 287 ACTIVE OBJECTS ............................................................................................................................................... 287 THE MASTER-SLAVE DESIGN PATTERN............................................................................................................. 287 JAVA THREADS ................................................................................................................................................. 288 SCHEDULING ..................................................................................................................................................... 289 Thread States ............................................................................................................................................... 289 Preemptive vs. Nonpreemptive Scheduling .................................................................................................. 289 EXAMPLE: BOUNCING BALLS ............................................................................................................................ 289 PRODUCER-CONSUMER PROBLEMS ................................................................................................................... 293 Example: A Joint Checking Account Simulation ......................................................................................... 293 MONITORS......................................................................................................................................................... 296 DIRECT COMMUNICATION ................................................................................................................................. 299 6 Example: A Date Client ............................................................................................................................... 300 A SERVER FRAMEWORK.................................................................................................................................... 301 Design .......................................................................................................................................................... 301 Implementation ............................................................................................................................................ 301 Example: A Command Server Framework .................................................................................................. 302 INDIRECT COMMUNICATION .............................................................................................................................. 304 Proxies ......................................................................................................................................................... 305 CONCLUSION ..................................................................................................................................................... 308 DOCUMENTATION AND CODING STANDARDS ......................................................................................... 309 SAMPLE CODING STANDARDS ........................................................................................................................... 311 SUMMARY OF NOTES MENTIONED IN THE STRING CLASS .................................................................................. 315 MISCELLANEOUS ISSUES ................................................................................................................................... 316 SWITCH. ...................................................................................................................................................... 316 TRY/CATCH/FINALLY.:.............................................................................................................................. 316 GENERAL COMMENTS: ...................................................................................................................................... 316 Names: ......................................................................................................................................................... 316 GENERAL PROGRAMMING ................................................................................................................................. 317 LAYOUT OF SOURCE FILES (*.JAVA).......................................................................................................... 317 SCRUM: AN EMPIRICALLY-BASED PROCESS FOR SOFTWARE PROJECT MANAGEMENT ............. 319 PRINCIPLES BEHIND THE AGILE MANIFESTO ...................................................................................................... 319 OVERVIEW ........................................................................................................................................................ 320 SCRUM SUMMARY ............................................................................................................................................ 323 SCRUM COMPONENTS AND PROCESSES– DEFINITIONS AND FUNCTIONALITY .................................................... 324 SCALABILITY ..................................................................................................................................................... 352 MANAGEMENT ISSUES....................................................................................................................................... 353 SCRUM NOTES FOR 668/868 .............................................................................................................................. 354 WEEKLY DELIVERABLES (DUE EACH FRIDAY)................................................................................................... 358 SQUEAK SMALLTALK EXERCISE .................................................................................................................. 359 PROJECT INFORMATION ................................................................................................................................ 361 PROJECT MILESTONES (DELIVERABLES INCLUDE MOCKUPS, SPECS, ARCHITECTURE DIAGRAMS AS DETERMINED BY THE INDICATED MILESTONE) ......................................................................................................................... 362 FINAL PROJECT DELIVERABLES ......................................................................................................................... 362 7 Object-Oriented Programming CSc 668/868 Dr. Barry Levine Thornton 949, x81661 Course Number: CSC 668/868 Course Title: Advanced Object Oriented Software Design and Development Number of Credits: 3 Schedule: Three hours of lecture/discussion per week. Prerequisite: Senior or graduate standing, and at least a C grade in CSC 413, or consent of instructor. Catalog Description Basic principles of object oriented analysis and design utilizing UML, advanced object oriented programming principles, design patterns, frameworks and toolkits; Agile software design processes. Development of a mid-size programming project working in teams. Paired with CSC 868. Students completing this course may not take CSC 868 later for credit. Extra fee required. Topics 1. An Introduction to Object-Oriented Analysis and Design 2. Some Notes on Java 3. Architectural Design 4. Java's Remote Method Invocation – RMI 5. Object Oriented Programming Principles 6. OOP and Software Engineering 7. Software Engineering Concepts 8. Unit Testing 9. Squeak/Smalltalk – 80 10. Reflection and Persistence 11. Programming and Design Principles 12. Event Notification 13. Design Patterns 14. Refactoring 15. Frameworks. Toolkits, and Polymorphism 16. Delegation 17. Presentation and Control 18. Active and Distributed Objects 19. SCRUM: An Empirically-Based Process for Software Project Management 20. Documentation and Coding Standards Course Objectives and Role in Program The prime objective of the course is to teach the student to analyze, design and implement object-oriented software systems by means of a mid-sized project. Students will learn the 8 application of software architectures in various settings, including the application of design patterns, frameworks and toolkits. The ability to architect software systems is basic to all subsequent courses in which software development is an integral part. In addition, many of the concepts facing the prospective developer in modern software technology, such as Refactoring and team-oriented Agile software development processes are examined in detail and integrated into general software practice. Learning Outcomes At the end of the course students will be able to Utilize processes and artifacts to work effectively in a team-oriented development environment Apply various software architectures, including frameworks and design patterns, when developing software projects Develop Smalltalk applications Program distributed applications in a Java environment Effectively construct medium-sized object-oriented programs. Project Student teams will propose and develop a medium-sized project of their own. Project Meetings We will have ongoing meetings to discuss the term project - you will bring project documentation to these meetings (class diagrams, etc.) so I can assess your design efforts and relevancy to your proposal Software Squeak Smalltalk (free) is available for download (see ilearn) Java is available on the PC’s (free) – you MUST use an IDE such as Netbeans Grading (approximate) CSc 668 Labs........................50% Class Participation.....5% Term project............45% CSc 868 Labs........................40% Class Participation.....5% Term project...........40% Presentation............15% Note: Students enrolled in CSc 868 are required to provide a presentation of a course-related article found in the current Computer Science literature. All students are required to be present at the 868 presentations. Up to 10% of the lab points will be deducted from the grade of students who do not attend the presentations. Texts Squeak: Object-Oriented Design with Multimedia Applications, Mark Guzdial, Prentice Hall, 2000 9 Applying UML and Patterns - an Introduction to Object-Oriented Analysis and Design by Craig Larman, Prentice Hall , [1998] (Optional) Core Java 2 - Volume II-Advanced Features (5th Edition) by C. Horstmann & G. Cornell, Prentice Hall, [December 10, 2001] (Optional) Recommended References: Design Patterns, Elements of Reusable Object-Oriented Software, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Addison Wesley, Reading, MA. 1995. "Software Patterns", Communications of the ACM, October, 1996. Refactoring to Patterns, Joshua Kertevsky, Addison-Wesley 2005 Design Patterns Explained: A New Perspective on Object-Oriented Design, A. Shalloway & J. Trott, Addison-Wesley, 2002 Pattern Hatching Design Patterns Applied, J. Vlissides, Addison-Wesley, 1998 Agile Software Development with Scrum, Ken Schwaber & Mike Beedle, Prentice-Hall, 2001 IEEE Computer, Special Issue on Agile Software Development, June 2003 Refactoring; Improving the Design of Existing Code, Martin Fowler, Addison-Wesley, 2000 Framework-Based Software Development in C++, Gregory F. Rogers, Prentice Hall, 1997 10 Note on Group Work In most cases programmers work in groups (for better or worse) so it's important that you check out and learn about group dynamics while in school...in fact, many CS programs require students to work in groups in most courses. There are many recipes for success/failure that you should learn. Therefore, if you are struggling with your group during the term I EXPECT you to check things out with me BEFORE you reach an untenable situation - be proactive! I will act as facilitator, not judge. My goal will be to help you resolve issues so you can continue in a productive manner. Again, SEE ME AS SOON AS POSSIBLE IF YOU ARE ENCOUNTERING GROUP PROBLEMS. In summary, All group members are expected to contribute to the best of their abilities Each group member MUST participate in teamwork and group work, attend meetings and be courteous and responsible All group members get the same grade UNLESS the instructor is told or discovers that somebody is not contributing fully You will select a group leader for each group Role of group leads Organize and schedule meetings Single point of contact with instructor Help resolve issues Can request meeting/input from instructor Participation in creating deliverables (a bit reduced for group lead tasks) Responsible for submitting documents, as described at the end of the Reader 11 CSC 868 Oral Presentations The additional requirement for people enrolled in CSc 868 is to present a review of an article found in the Computer Science literature on Object-Oriented Programming. You should peruse the literature in the library (SFSU library or UC Berkeley library or...) to determine a topic you are interested in studying. Then bring a copy of the article to me for PRIOR approval. You will present your review to the class during a regular meeting. We will agree upon a time for presentation. The presentation should last approximately 30 minutes (the time will depend on the article; in any case, you should practice your lecture to ensure that it doesn't exceed 25 minutes. This is to allow for any questions). I will give suggestions for each presentation. Library references are: - ACM SIGPLAN (especially the conference proceedings on object-oriented programming). - IEEE Software - Software: Practice and Experience - IEEE Transactions on Software Engineering - Communications of the ACM - Computer Language - The Journal of Systems and Software Unlike the 868 programming assignments, the reviews will be done on an individual basis. You are required to receive my approval on your proposed review ... (be sure to plan ahead since it is possible that I will not approve your initial request). BE SURE TO PREPARE TRANSPARENCIES/POWERPOINT PRESENTATION FOR YOUR LECTURES. USE EXAMPLES AND PICTURES. DO NOT DO NOT DO NOT PRESENT YOUR MATERIAL BY READING FROM YOUR SLIDES!!! DO NOT BRING ANY NOTES...YOUR PRESENTATION SHOULD FOCUS ON YOUR SLIDES (people have a tendency to use notes as a crutch and simply read from them) Of course, if you spend most of the time reading from your slides then it will not be enjoyable nor very interesting for the audience. Please see me for any suggestions. 12 After presenting a complete reference to your article you should present a coherent outline of the presentation (frequently refer back to this outline to help the audience follow the flow of your talk). You should give an introduction, overview of your presentation, then the main body of your talk and end with any conclusions. BE SURE TO USE PICTURES AND EXAMPLES to help the audience understand your talk. The weight of the review is approximately 15% of your grade for 868. In any case, if you do not present a review then you will not receive a grade higher than a "C" for the course. After I approve your article you need to send me the following information: Your name, Title, author(s), date of pub, where published 13 An Introduction to Object-Oriented Analysis and Design Plan/Elaborate Preliminaries Build Thorough analysis/design/ construction Deploy in field - testing, delivery to customers Macro Process 14 Plan/Elaborate Schedule resources, budget, etc. Preliminary investigation report Requirements spec Glossary of terms Prototype (optional) Investigate some use cases Build: repeated (iterative) development cycles Refine plan derived during plan/elaborate Analyze system - refine use cases, define significant use cases Design - construct other artifacts dealing with software system Construct - translate into software Test Deploy in field - testing, delivery to customers Macro Process: Expanded 15 Ongoing: Synchronize artifacts as system evolves OOA/D Requirements Analysis Domain Analysis Responsibility Assignments Associated Documents Use Cases Conceptual Model Design class diagrams Interaction design Collaboration diagrams Consider a Dice Game Scenario Use Case: Play a game Actors: Player Description: Player rolls dice, win on 7 else lose Player 1 Rolls 2 name Die faceValue 1 2 (# Die) Plays 1 Dice Game 1 (# games) Includes association Conceptual Model (actors, associations, cardinalities) 16 play() :Player d1:Die 1: r1 = roll() 2: r2 = roll() d2:Die Collaboration Diagram (flow of messages between instances/invocation of methods) 17 Player Die Rolls name faceValue 1 2 play() roll() 1 2 Plays 1 DiceGame 1 Includes init() Design Class Diagram (methods, object associations) 18 Class Diagrams (succinct notation depicting class structure to be used this term) Concrete Class: Class Name attributes/properties ... returnType method(Type parameter,…) ... From sub- to superclass Abstract Class: aggregation with ownership Class Name attributes/properties ... returnType method(Type parameter,…) returnType abstractMethod(): ... ... aggregation without ownership Interface: <interface> Interface Name returnType method(Type parameter,…) ... 19 Class Diagram Examples Payment double amountDue void makePayment(double amountPaid) double getAmouontDue() CashPayment CreditPayment void makePayment(double amountPaid) void makePayment(double amountPaid) SfsuClass SfsuStudent String courseName String name Vector getStudents note: non-ownership Registrar might track students’ status so Students would be associated with a class and the registrar’s office Car Engine ... String type ... note: ownership a specific engine is made for a specific car 20 String getName() String getType() <<<OOADwithUML.ppt>>> <<<CH1-HIST.PPT>>> Macro Level Process 1. Plan/Elaborate define requirements, prototypes, etc. - iterative development a. Plan: schedule resources, budget, etc. b. Preliminary investigation report: motivation, alternatives, business needs c. Requirements specification: needs for product (overview, customers, goals, system functions/attributes) d. Glossary: of terms, constraints, rules (ongoing) e. Prototype: to aid understanding - optional (may do later) f. Use Cases: prose description of domain processes g. Use Case Diagrams: illustration of Use Cases and their relationships h. Draft Conceptual Model: to help understand vocabulary of domain vis-a-vis Use Cases and requirements spec. (representation of concepts, objects in problem domain) 2. Build the System development cycles: refine plan, analyze, design, construct, test (-release) 3. Deploy -releases Each development cycle should be completed in a very definite time bound (e.g. 2 weeks - 2 months) 21 Object-Oriented Analysis OOA attempts to analyze/understand real life system, domain concepts (e.g. object types, associations and state changes), requirements, participants (objects), interactions and responsibilities. It answers the question of what the software will do without concern for how it will do it. 1. Define Essential Use Cases 2. Refine Use Case diagrams 3. Refine Conceptual Model 4. Refine Glossary 5. Define System Sequence Diagrams 6. Define Operation Contracts 7. Define State Diagrams Object-Oriented Design OOD involves architecting the software system and software artifacts (classes, etc.) gleaned from artifacts produced in OOA. 1. Define Real Use Cases 2. Define reports, user interface/storyboards 3. Define System Architecture 4. Define Interaction Diagrams 5. Define Design Class Diagrams 6. Define Database Schema 22 Note that in OOA one doesn’t deal in software. You might consider that the OOA can be done by a business person rather than a programmer. OOD is done by a software engineer. IMPLEMENT A STORE/POST (Point of Sale Terminal) SYSTEM GUI (used to init Store - stock, catalog, POST(s), sales log and get Store information) STORE GUI (customer purchases) POST Product Catalog … Client Level - user interface GUI POST Inventory Business Logic Sales Log Authorization Service (e.g. Verity) Checks, credit cards 23 Database Servers DOMAIN EXPERT: Dr. Levine POST Product Quantity Total Tendered Enter Item Balance End Sale Make Payment Point of Sale Terminal (POST - cash register) The POST has a bar code scanner (UPC codes), a cash drawer and a credit card scanner We need the software to run the terminal Our modification to the POST GUI: Users will select products from a drop-down product selection list; the product will be posted, along with the price. A running subtotal will be displayed. 24 Databases PRODUCT CATALOG FIELDS Text Description gif (picture of product) Price OPERATIONS Add new product specification Get record, given product id (i.e. its position in the list of products) Print catalog in sorted order by product name (print text descriptions, price, quantity) SALES LOG FIELDS Customer name Date/time of transaction Invoice information OPERATIONS Add new sale Print log ordering by date/time Print all purchases by customer 25 Object-Oriented Analysis Investigate the problem with the perspective of the relevant objects. Find and describe real life objects and concepts in the problem domain. A. Specify the requirements of the product - document what is really needed Overview: system should do ???? Customers Goals: e.g. increase automation, faster services System functions: e.g. the system should do ?? (credit payment authorization) System attributes: e.g. ease of use B. Develop the Glossary C. Specify Use Cases: a narrative document that describes the sequence of events of an actor using a system to complete a process. The events are external events (system events) caused by an actor outside the system boundary. Expand the Critical Use Cases Essential Use Cases: expanded use cases; still free of any technology commitments/implementation details; still abstract; e.g. the actor action might be: the customer identifies themselves and the system responds with a set of options; note that there are no details such as what identifying information the customer presents, etc. Use Case Diagrams: show relationships amongst use cases. D. Develop the Conceptual Model: different categories of things in the domain; don’t make too complex; not fine grained; later refine it. These things are found in the real world, not 26 software components. Better to overspecify rather than under specify. Note that a concept corresponds to the real world, whereas a class is a software artifact. Associations between the concepts are depicted by labeled edges. Attributes are included in each of concept specifications, as appropriate. 27 Records-sale-of Described-by 1 Product Catalog Product Specification Contains 1 1..* description price UPC 1 Describes Used-by 0..1 * * * Sales LineItem Stocks Store address name 1 1 1 Item * quantity 1..* 1 Contained-in Logscompleted 1 Houses 1..* * Sale date time POST 1 1 Manager Captured-on Started-by 1 1 1 1 1 Initiated-by Records-sales-on Paid-by 1 Payment amount 1 Customer 1 Cashier Conceptual Model for POST (objects found in the real world – nouns drawn from the requirements/specification) 28 E. Develop the System Sequence Diagrams: Used to better understand the external actors and the system events they generate - the behavior of the system; derived from the use cases. These depict a single path through a flow of events. Used to help identify the system’s objects/relationship between them. The messages are interactions among objects (identifies behavior). These objects are used to help discover classes. Note that good use cases will help determine objects, which will help determine classes. NOTE: There should be one sequence diagram for each use case. 29 Object-Oriented Design Specify the solution of the problem; the software components A. Develop Real Use Cases: describes real or actual design of the Essential Use Case in terms of concrete input/output and its overall implementation - provide sample GUI’s if needed. Depict actor actions and system responses - e.g. in an essential use case we might say that customers identify themselves; in a real use case we might say that the customer inserts their card (a very concrete realization of the essential use case) B. Specify Interaction Diagrams: Illustrates the message interactions between instances/classes in the class model. The two types of Interaction Diagrams that are used are 1. Collaboration Diagrams and 2. Sequence Diagrams. C. Consider the use of Patterns: named description of a problem and solution that can be applied in new contexts; e.g. the Controller pattern. D. Build the Design Class Diagrams: specify the software classes/interfaces which participate in the software solution; annotate them with design details, such as methods.. Group components into layered architecture and packages (used to define nested name spaces) to enhance the specification (i.e. ease of understanding, etc.). We will generate the class definitions from the Design Class Diagrams. The Collaboration Diagrams will be used to help determine the class methods. 30 High-level Stages in the STORE/POST Software System: Startup: init Store Stock Catalog POST Sales log While open for business: Customer purchases items1 Arrives at a date and time Identifies themselves Buys one or more items (might have more than one of an item) Ends sale: pays cash, credit, check (authorization service used to verify checks/credit cards; assume 10% of the authorization requests are denied) Receipt is issued Close STORE 1 Initially, these activities will be simulated with information from a file. Be sure to design system to minimize changes resulting from plugging in a GUI for interactions with a client rather than a file. Note that you will plug in a GUI in a subsequent stage. 31 POST Assignments 1. Write a Java program for the POST Use file I/O for testing. A note on naming: begin all interface names with "I" e.g. IPost would be an interface whereas Post would be a class 2. Write Java GUI’s for the STORE/POST Hook up the GUI’s to the Java program for POST using RMI (described later) 32 1. Write a Java program for the Post example in the text. In addition to the Post classes, as motivated by the conceptual model, you should include the following classes: Customer The customer will have a name and provide a series of upc/quantity pairs where the upc is a string of 4 chars and quantity is an integer; be careful to check that the user provides valid upc's (hint: the Customer will get the transaction from file and call on POST methods to do the transaction) Store The store will hold information including the product catalog Manager The manager will open the store, set up Post(s), put together the product catalog Be sure to use packages, where appropriate. Do not provide a GUI for this application. Initialize a simple database from a file (products.txt) Product Record - one per line UPC: 4 characters Text description: 20 columns Price: floating number as xxxx.xx (columns 1-4) (columns 10-29) (column 35 ..) Transaction file (transaction.txt) composed of 1 or more multi-line transaction records: Each part is on a separate line: Identifying information: Customer name Item: upc quantity OR upc if quantity is one … (upc in columns 1-4; quantity in column 10 ...) Item Payment: <CASH/CHECK $xxxx.xx> OR <CREDIT ddddd> ddddd is credit card # BLANK LINE Invoice Printed by Program STORE NAME Customer Name Date Time Item: <text quantity @ unit price subtotal> … Item -----Total $xxxx.xx Amount Tendered: xxxx.xx OR Paid by check OR Credit Card ddddd Amount Returned: xxxx.xx 33 You should turn in the hardcopy in the following order:: 1. Output 2. UML Specs: Use-Case Diagrams Collaboration Diagrams - only Sequence Diagrams (each sequence diagram will depict a single Use-Case) Class Diagrams for ALL classes; be sure to show all instance variables and public member functions with arguments, their types and return types 3. *.java files with appropriate comments Sample File I/O API Consider the following class specs: class ProductReader { // Product catalog will use this for initialization ProductReader(String productFile) {…} boolean hasMoreProducts() // are there more products to read? ProductSpec getNextProduct() // get/build next product spec } class TransactionReader { TransactionReader (Store store, String transactionFile) {…} boolean hasMoreTransactions() // are there more Transactions to read? Transaction getNextTransaction () // get next Transaction ... } class Transaction { TransactionHeader header; // provide a class for header info TransactionItem transItems[100]; // assume less than 100 items int numTransItems; Payment payment; ... } 34 2. Write Java GUI’s for the STORE/POST Hook up the GUI’s to the Post program using RMI STORE - init Name, current date/time POST - purchases Customer provides a name. Use drop-down selection list for upc (retrieve info from server program via RMI) Use list to select quantity Maintain running total display Create Payment screen: select from options then display appropriate dialog Cash: for amount of purchase Credit card: request number Check: for amount of purchase Change: to simplify always assume exact payments are made You should turn in the hardcopy in the following order:: 1. Appropriate screen snapshots – a few interesting screens; also provide any output 2. External Documentation: Indicate where objects are placed with respect to the client app and server app. Refer to slides in this reader “RMI Client-Server Implementation of the POST Problem”. Be sure to identify client(s)/server(s) – don’t just say, e.g., client without indicating if you are identifying the POST or Store. Also, include a written description of your solution. 3. Sequence diagram indicating collaborations between remote objects. 4. Class Diagrams - structured, to aid in understanding your architecture/deployment of classes; printed from CASE tool. Be sure to include all classes that are part of this problem, including classes used both in the first POST lab and this lab. 5. Your code with appropriate comments, ordered appropriately to ease understanding (i.e. not alpha-order) Note: When working with GUI’s one normally requires an application coordinator to mediate between the GUI and the domain layer (domain concepts - e.g. the conceptual model). Various controllers will be used. 35 Sample Screen 36 Indicate which IDE you are using. Provide appropriate documentation - protocol for communication with the Java middle tier program, class descriptions, choose good variable identifiers. Break up the program into packages. 37 Use Case View 1. Use Cases - provide a view from the outside Brief description of purpose Flow of events Use Case Diagrams - shows relationship among use cases and actors Communication Extends (e.g. one use case extends another) AB : B may include A’s behavior Uses (AB : A includes B’s behaviour) 2. Sequence Diagrams - scenarios are used to describe how use cases are realized as object interactions; includes actors, objects, and messages (one diagram per use case). Note that there should be one sequence diagram that depicts the interactions for each use-case. 3. Collaboration Diagrams - another view of a sequence diagram Can generate automatically from sequence diagram Includes actors, objects, links, messages and data flows 4. Packages - a mechanism for logical groupings (e.g. of use cases, etc.) Logical View Examines classes and their static relationships; also dynamic nature of classes Includes class diagrams and state transition diagrams Construct different diagrams each showing relevant information (easier to focus on items of interest) Relationships Association - general association information with roles and multiplicity Aggregation - part-of (e.g. an arm is a part-of a person) Dependency - client depends on supplier for service (e.g. global objects/classes or supplier passed as an argument to client class function or supplier declared locally) Inheritance State Transition Diagrams - for life history of objects in a class; for objects that exhibit significant dynamic behavior 38 Component View Different software system components User drags classes from the logical view to the containing component 39 Some Notes on Java this in Java this behaves in the same fashion as the self pseudo-variable in Smalltalk - it refers to the current instance. Consider the following code example below: class A { public void m() {System.out.println("Class A, method m ."); } } class B extends A { public void m() { System.out.println("Class B, method m. Now calling this.m1()."); this.m1(); } public void m1() { System.out.println("Class B, method m1 ."); } } public class C extends B { public void m1() { System.out.println("Class C, method m1."); // this.m(); // What happens if we include this line? } public static void main(String args[]) { A a = new A(); B b = new B(); C c = new C(); System.out.print("Calling A.m() "); System.out.print("Calling B.m() "); System.out.print("Calling C.m() "); System.out.print("Calling ((B)c).m1() a.m(); b.m(); c.m(); "); ((B)c).m1(); } } Output: Calling A.m() Class A, method m . Calling B.m() Class B, method m. Now calling this.m1(). Class B, method m1 . Calling C.m() Class B, method m. Now calling this.m1(). 40 Class C, method m1. Calling ((B)c).m1() Class C, method m1. this Used in Method Calls Note that when methods are invoked this is used implicitly as the instance for method searches. Consider the following program/output to clarify. import java.util.*; class SubClass extends BaseClass { void m1() { System.out.println("m1 in SubClass"); } } class BaseClass { void m() { System.out.println("m in BaseClass"); m1(); } // this.m1() void m1() { System.out.println("m1 in BaseClass"); } } public class test1 { public static void main (String args[]) { SubClass s = new SubClass(); s.m(); } } Output: m in BaseClass m1 in SubClass 41 Enumerated Types and Type Safety These are types with a finite set of values. Typically, we define symbolic constants to represent these types. Consider the java.util.Calendar class enumerated types: public static final int JANUARY = 0; public static final int FEBRUARY = 1; ... public static final int DECEMBER = 11; This is not very satisfactory since the compiler cannot check type errors: e.g., int month = Calendar.DECEMBER; month++; // not a valid month Consider the following solution seen in java.awt.Color: public final static Color white = new Color(255, 255, 255); In this case, all values are of type Color. We can only manipulate Colors through the Color class interface – this ensures type safety. 42 Architectural Design (Appendix 2) Architectural Patterns Sometimes the same architectural design recurs in diverse applications. We call this an architectural pattern. Here are a few examples. Model-View-Control Client-Server 43 Reflection Layers Package Structures There are a few other patterns that are used for developing packages. To loosen the coupling between packages, the facade pattern designates one class as the package interface to the others. A package only exports its facade: 44 The mediator minimizes the connections between classes within a package: 45 Java's Remote Method Invocation - RMI (A nice reference: http://java.sun.com/docs/books/tutorial/rmi/index.html) Similar to CORBA (Common Object Request Broker Architecture) but provides a distributed Java environment. It is an attempt to provide object location transparency - the client doesn't write code that depends on the location of the objects; the objects can be local or on some remote machine. Client Program Server Program Interface Implementation remote objects, primitive objects, serializable objects marshalled parameters copies of primitive type parameters marshalled results copies of primitive type results remote exception(s)? Figure 1 46 Client Program Server Program Gets proxy for remote object and calls its methods Contains (remote) objects - remote clients make calls on the methods of these objects Proxy (stub) Implementation RMI transport marshalled parameters copies of primitive type parameters marshalled results copies of primitive type results remote exception(s)? Figure 2 A client program makes method calls on the proxy object, RMI sends the request to the remote JVM, and forwards it to the implementation. Any return values provided by the implementation are sent back to the proxy and then to the client's program. 47 Client Program RMI System Server Program Stubs/Skeletons Stubs/Skeletons Remote Reference Layer Remote Reference Layer Transport Layer Figure 3 The RMI implementation is essentially built from three abstraction layers. The first is the Stub and Skeleton Layer, which lies just beneath the view of the developer. This layer intercepts method calls made by the client to the interface reference variable and redirects these calls to a remote RMI service. The skeleton understands how to communicate with the stub across the RMI link. The skeleton carries on a conversation with the stub; it reads the parameters for the method call from the link, makes the call to the remote service implementation object, accepts the return value, and then writes the return value back to the stub. In the Java 2 SDK implementation of RMI, the new wire protocol has made skeleton classes obsolete. RMI uses reflection to make the connection to the remote service object. You only have to worry about skeleton classes and objects in JDK 1.1 and JDK 1.1 compatible system implementations. Note that when remote methods are invoked the RMI system must prepare the method arguments to be transported across the internet to the server. This argument value preparation is termed marshalling. If the argument is a primitive type then its copy is passed. If the argument represents an object then its value is serialized. The stub is responsible for marshalling the arguments. On the receiving end, the skeleton is responsible for unmarshalling the arguments in preparation for the method call on the server object. Object serialization essentially flattens an object and any objects it references. This can be a very involved process for objects that contain references to other objects, and those objects reference others, and so on. Conversely, when a result value is returned to the client then the skeleton marshals the value and the stub unmarshals the value. If there are network problems, etc., then a RemoteException might be raised and must be handled in an appropriate fashion. The stubs and skeletons are generated via the rmic program included in the Java system. The next layer is the Remote Reference Layer. This layer understands how to interpret and manage references made from clients to the remote service objects. In JDK 1.1, this layer connects clients to remote service objects that are running and exported on a server. The connection is a one-to-one (unicast) link. In the Java 2 SDK, this layer was enhanced to support the activation of dormant remote service objects via Remote Object Activation. 48 The Transport Layer is based on TCP/IP connections between machines in a network. It provides basic connectivity, as well as some firewall penetration strategies. Note that even if you are using RMI on a single system you must have an operational TCP/IP configuration on your computer. When an object is passed to a remote method, unlike normal parameter passing semantics, RMI sends the object itself, not its reference, between JVMs. It is the object that is passed by value, not the reference to the object. Similarly, when a remote method returns an object, a copy of the whole object is returned to the calling program. Distributed Garbage Collection The RMI system provides a reference counting distributed garbage collection algorithm based on Modula-3's Network Objects. This system works by having the server keep track of which clients have requested access to remote objects running on the server. When a reference is made, the server marks the object as "dirty" and when a client drops the reference, it is marked as being "clean." Object Registration Objects that can be referenced by remote clients must be registered as such on the server machine by using the java registry service rmiregistry. Clients can then lookup remote object(s) on servers by accessing the naming service on the server. Consider the following RMI example using remote account objects (perhaps useful within an ATM context). RMI Example - Accounts used by ATM's Server files: Interfaces: Account.java Implementation files: AccountImpl.java, RegAccount.java Client files: AccountClient.java, account.jar /* * Account.java * */ package accountInterfaces; import java.rmi.Remote; import java.rmi.RemoteException; 49 public interface Account extends java.rmi.Remote { public String getName() throws RemoteException; public void setName(String name) throws RemoteException; public float getBalance() throws RemoteException; public void withdraw(float amt) throws RemoteException; public void deposit(float amt) throws RemoteException; } /* * AccountImpl.java * */ package serverAccounts; import accountInterfaces.Account; import java.rmi.server.UnicastRemoteObject; import java.rmi.RemoteException; public class AccountImpl extends UnicastRemoteObject implements Account { private float balance = 0; private String name = ""; public AccountImpl(String name) throws RemoteException { this.name = name; } public String getName() throws RemoteException { return name; } public void setName(String name) throws RemoteException { this.name = name; } public float getBalance() throws RemoteException { return balance; } 50 public void withdraw(float amt) throws RemoteException { balance -= amt; // Make sure balance never drops below zero balance = Math.max(balance, 0); System.out.println("Withdrawn from " + name + ": " + amt + " New Balance: " + balance); } public void deposit(float amt) throws RemoteException { balance += amt; System.out.println("Deposit to " + name + ": " + amt + " New Balance: " + balance); } // Move some funds from another (remote) account into this one public void transfer(float amt, Account src) throws RemoteException { src.withdraw(amt); this.deposit(amt); } } /* * RegAccount.java * */ package serverAccounts; import accountInterfaces.Account; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class RegAccount { public static void main(String args[]) { try { /* if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); } */ for (int i = 0; i < args.length; i++) { Account acct = new AccountImpl(args[i]); 51 // Register it with the local naming registry Registry registry = LocateRegistry.getRegistry(); registry.rebind(args[i], acct); System.out.println("Registered account for: " + args[i]); } } catch (RemoteException e) { System.out.println("********* " + e); e.printStackTrace(); } } } /* * AccountClient.java * */ package accountClient; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import accountInterfaces.*; public class AccountClient { public static void main(String args[]) { try { Registry registry = LocateRegistry.getRegistry("localhost"); // Registry registry = LocateRegistry.getRegistry("rmi://thecity.sfsu.edu/Mary"); for (int i = 0; i < args.length; i++) { String accountHolderName = args[i]; Account acct = (Account)registry.lookup(accountHolderName); acct.deposit(5000); System.out.println("Deposited 5000 into account owned by " + acct.getName()); System.out.println("Balance total: " + acct.getBalance()); } } catch (Exception e) { System.out.println("Error while looking up account: " + e); } } } 52 Steps to run this example (JDK 1.5 on Windows XP): 1. Compile all interface files using Java 1.5: C:\rmi\server>javac accountInterfaces\*.java 2. Package interface files to be shipped to clients C:\rmi\server>jar cvf account.jar accountInterfaces\*.class added manifest adding: accountInterfaces/Account.class(in = 383) (out= 239)(deflated 37%) 3. Compile all server files, including implementation files – use jar file of interfaces C:\rmi\server>javac -cp account.jar serverAccounts\*.java C:\rmi\server>dir Volume in drive C has no label. Volume Serial Number is 0C13-E520 Directory of C:\rmi\server 12/17/2006 03:23 PM <DIR> . 12/17/2006 03:23 PM <DIR> .. 12/17/2006 03:23 PM 738 account.jar 12/17/2006 03:12 PM <DIR> accountInterfaces 12/17/2006 03:13 PM <DIR> serverAccounts 1 File(s) 738 bytes 4 Dir(s) 137,310,576,640 bytes free 4. Move jar file to client and compile client files: C:\rmi\client>dir Volume in drive C has no label. Volume Serial Number is 0C13-E520 Directory of C:\rmi\client 12/17/2006 03:23 PM <DIR> . 12/17/2006 03:23 PM <DIR> .. 12/17/2006 03:23 PM 738 account.jar 12/17/2006 03:18 PM <DIR> accountClient 1 File(s) 738 bytes 3 Dir(s) 137,310,576,640 bytes free 53 C:\rmi\client>javac -cp account.jar accountClient\*.java 5. Start rmiregistry: C:\rmi>start rmiregistry C:\rmi> 6. Run server, register exported object(s): C:\rmi\server>java -cp account.jar;C:\rmi\server -Djava.rmi.server.codebase=file:C:/rmi/server/account.jar -Djava.rmi.server.hostname=localhost serverAccounts.RegAccount Jack Mary Registered account for: Jack Registered account for: Mary 7. Run client; note that when it’s re-run it continues to use the same server objects: C:\rmi\client>java -cp account.jar;C:\rmi\client -Djava.rmi.server.codebase=file:C:/rmi/client/account.jar accountClient.AccountClient Jack Mary Deposited 5000 into account owned by Jack Balance total: 5000.0 Deposited 5000 into account owned by Mary Balance total: 5000.0 C:\rmi\client>java -cp account.jar;C:\rmi\client -Djava.rmi.server.codebase=file:C:/rmi/client/account.jar accountClient.AccountClient Jack Mary Deposited 5000 into account owned by Jack Balance total: 10000.0 Deposited 5000 into account owned by Mary Balance total: 10000.0 8. Check output on server C:\rmi\server>java -cp account.jar;C:\rmi\server -Djava.rmi.server.codebase=file:C:/rmi/server/account.jar -Djava.rmi.server.hostname=localhost serverAccounts.RegAccount Jack Mary Registered account for: Jack Registered account for: Mary Deposit to Jack: 5000.0 New Balance: 5000.0 Deposit to Mary: 5000.0 New Balance: 5000.0 Deposit to Jack: 5000.0 New Balance: 10000.0 Deposit to Mary: 5000.0 New Balance: 10000.0 54 RMI Example - Remote methods returning remote objects As described above, a client program can obtain a reference to a remote object through the RMI Registry program. In addition, it can be returned to the client from a method call. When a method returns a local reference to an exported remote object, RMI does not return that object. Instead, it substitutes the remote proxy for that service in the return stream1. The following example will illustrate this situation. Server files: Interfaces: Account.java, AccountManager.java Implementation files: AccountImpl.java, AccountManagerImpl.java, AccountManagerReg.java Client files: AccountManagerTest.java, manageAccount.jar /* * Account.java */ package manageAccountInterfaces; import java.rmi.Remote; import java.rmi.RemoteException; public interface Account extends java.rmi.Remote { public String getName() throws RemoteException; public void setName(String name) throws RemoteException; public float getBalance() throws RemoteException; public void withdraw(float amt) throws RemoteException; public void deposit(float amt) throws RemoteException; } /* * AccountManager.java */ package manageAccountInterfaces; import java.rmi.Remote; 1 Non-remote objects either passed as a parameter or returned as a result from one VM to another VM must be serializable – a copy of the object (not a stub) is actually passed. Note that Strings are passed as args and returned as results. Since Strings are not declared as remote, they are copied/serialized and then shipped. 55 import java.rmi.RemoteException; public interface AccountManager extends Remote { public Account getAccount(String name) throws RemoteException; public Account newAccount(String name) throws RemoteException; } /* * AccountImpl.java */ package manageAccountServer; import manageAccountInterfaces.*; import java.rmi.server.UnicastRemoteObject; import java.rmi.RemoteException; public class AccountImpl extends UnicastRemoteObject implements Account { private float balance = 0; private String name = ""; public AccountImpl(String name) throws RemoteException { this.name = name; } public String getName() throws RemoteException { return this.name; } public void setName(String newName) throws RemoteException { this.name = newName; } public float getBalance() throws RemoteException { return balance; } public void withdraw(float amt) throws RemoteException { balance -= amt; // Make sure balance never drops below zero balance = Math.max(balance, 0); System.out.println("Withdrawn from " + name + ": " + amt + " New Balance: " + balance); } public void deposit(float amt) throws RemoteException { 56 balance += amt; System.out.println("Deposited into " + name + ": " + amt + " New Balance: " + balance); } public void transfer(float amt, Account fromAccount) throws RemoteException { fromAccount.withdraw(amt); this.deposit(amt); } } /* * AccountManagerImpl.java */ package manageAccountServer; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.rmi.registry.Registry; import java.rmi.registry.LocateRegistry; import manageAccountInterfaces.*; public class AccountManagerImpl extends UnicastRemoteObject implements AccountManager { private static java.util.HashMap<String,Account> accounts = new java.util.HashMap<String,Account>(); public AccountManagerImpl() throws RemoteException {} public Account getAccount(String name) throws RemoteException { Account acct = accounts.get(name); if (acct == null) throw new RemoteException("Account not found: " + name); return acct; } public Account newAccount(String name) throws RemoteException { Account acct = new AccountImpl(name); if (accounts.get(name) != null) throw new RemoteException("Attempt to create duplicate account: " + name); accounts.put(name,acct); System.out.println("New account for: " + name); 57 return acct; } } /* * AccountManagerReg.java * */ package manageAccountServer; import manageAccountInterfaces.*; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class AccountManagerReg { public static void main(String argv[]) { try { AccountManager mgr = new AccountManagerImpl(); Registry registry = LocateRegistry.getRegistry(); registry.rebind("manager", mgr); System.out.println("Registered account manager"); } catch (RemoteException ex) { System.out.println("*********** " + ex); ex.printStackTrace(); } } } /* * AccountManagerTest.java * */ package client; import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import manageAccountInterfaces.*; 58 public class AccountManagerTest { public static void main(String argv[]) { try { Registry rmtReg = LocateRegistry.getRegistry(); AccountManager mgr = (AccountManager)rmtReg.lookup("manager"); Account m; try { m = mgr.getAccount("Mary"); } catch (RemoteException ex) { m = mgr.newAccount("Mary"); } m.deposit(2000); System.out.println("Deposited 2000 into account owned by " + m.getName()); System.out.println("Balance now totals: " + m.getBalance()); if (m.getBalance() > 4000) { m.withdraw(25); System.out.println("Withdrawn 25 from account owned by " + m.getName()); System.out.println("Balance now totals: " + m.getBalance()); } } catch (RemoteException ex) { System.out.println("*********** " + ex); ex.printStackTrace(); } catch (NotBoundException ex) { ex.printStackTrace(); } } } Steps to run this example (JDK 1.5 on Windows XP): 1. Compile all interface files using Java 1.5: C:\rmi\server>javac manageAccountInterfaces\*.java 2. Package interface files to be shipped to clients C:\rmi\server>jar cvf manageAccount.jar manageAccountInterfaces\*.class added manifest adding: manageAccountInterfaces/Account.class(in = 389) (out= 242)(deflated 37%) 59 adding: manageAccountInterfaces/AccountManager.class(in = 317) (out= 198)(deflat ed 37%) 3. Compile all server files, including implementation files – use jar file of interfaces C:\rmi\server>javac -cp manageAccount.jar manageAccountServer\*.java C:\rmi\server>dir Volume in drive C has no label. Volume Serial Number is 0C13-E520 Directory of C:\rmi\server 12/17/2006 02:05 PM <DIR> . 12/17/2006 02:05 PM <DIR> .. 12/17/2006 01:57 PM 1,131 manageAccount.jar 12/17/2006 01:55 PM <DIR> manageAccountInterfaces 12/17/2006 01:59 PM <DIR> manageAccountServer 1 File(s) 1,131 bytes 4 Dir(s) 137,315,061,760 bytes free 4. Move jar file to client and compile client files: 12/17/2006 02:01 PM <DIR> . 12/17/2006 02:01 PM <DIR> .. 12/17/2006 02:03 PM <DIR> client 12/17/2006 11:39 AM 1,131 manageAccount.jar 1 File(s) 1,131 bytes 3 Dir(s) 137,315,127,296 bytes free C:\rmi\client>javac -cp manageAccount.jar client\*.java 5. Start rmiregistry: C:\rmi>start rmiregistry C:\rmi> 6. Run server, register exported object(s): C:\rmi\server>java -cp manageAccount.jar;C:\rmi\server -Djava.rmi.server.codeba se=file:C:/rmi/server/manageAccount.jar -Djava.rmi.server.hostname=localhost man ageAccountServer.AccountManagerReg Registered account manager 7. Run client; note that when it’s re-run it continues to use the same server objects: 60 C:\rmi\client>java -cp manageAccount.jar;C:\rmi\client -Djava.rmi.server.codeba se=file:C:/rmi/client/manageAccount.jar client.AccountManagerTest Deposited 2000 into account owned by Mary Balance now totals: 2000.0 C:\rmi\client>java -cp manageAccount.jar;C:\rmi\client -Djava.rmi.server.codeba se=file:C:/rmi/client/manageAccount.jar client.AccountManagerTest Deposited 2000 into account owned by Mary Balance now totals: 4000.0 8. Check output on server C:\rmi\server>java -cp manageAccount.jar;C:\rmi\server -Djava.rmi.server.codeba se=file:C:/rmi/server/manageAccount.jar -Djava.rmi.server.hostname=localhost man ageAccountServer.AccountManagerReg Registered account manager New account for: Mary Deposited into Mary: 2000.0 New Balance: 2000.0 Deposited into Mary: 2000.0 New Balance: 4000.0 61 RMI Client-Server Implementation of the POST Problem (Documentation) In any client-server application it is very important to indicate the deployment of the objects of interest to better understand the nature of the computation. There are many issues that must be considered regarding the location of the participating objects, including security, scalability, network traffic, network integrity and other efficiency concerns. These considerations must be addressed during the analysis of the system. For instance, consider the situation wherein the central store computer (server) crashes. Should this prevent the POST(s) from serving customers? I have provided two simple tables below that depict the deployment of the objects. Be sure to study these tables so you might better understand how to provide similar documentation for your projects. Main Objects Shipped from Server (Store) to Client (POST)2 <<You should fill in the table entries appropriately>> Main Objects Shipped from Client to Server ProductCatalog Server (Store) Post Client (POST) Store (stub) Store (skeleton) Invoice 2 For each object, indicate whether its remote reference is shipped or a copy is shipped. 62 Objects on Client (POST) CashPayment Objects on Server (Store) CashPayment Comments Various Payments are created on the client and shipped to the server as part of an invoice <<You should fill in the remaining table entries appropriately>> In addition to these deployment tables be sure to provide adequate documentation so I can understand the main object interactions (sequencing). Also, note that the GUI classes should not contain any business logic; they are only concerned with the presentation layer. 63 Object Oriented Programming Principles Documentation User Manual: prepare before design effort to ensure an understanding of the clients’ desires Design documentation: document important design decisions to help understand factors influencing choices; important reference information Prepare for change: predict sources of change Message passing versus function calls messages are passed to instances (receivers); methods are determined by class of receiver; the instance is passed as an implied argument (use self, this to reference the argument) function calls are matched to function bodies based on the types (classes) of (all of) the arguments 64 Quality of an Interface 1. Cohesion – all of its methods are related to a single abstraction e.g., public class MyContainer { public void addItem(Item anItem) {….} public Item getCurrentItem() { … } public Item removeCurrentItem() { … } public void processCommand(String command) { … } … } Note that processCommand might fit better in another class; leave the MyContainer class to do what it does best – store Items. 2. Completeness – interface should support all operations that are a part of the abstraction that the class represents. e.g., The Stack class should contain a method to check if the Stack is empty 3. Convenience – ensure that the interface makes it convenient for clients to do associated tasks, especially common tasks. e.g., Prior to Java 5 System.in could not read lines of text or numbers. This was fixed by the addition of the java.util.Scanner class 4. Clarity – the interface should be clear to programmers e.g., LinkedList<String> list = new LinkedList<String>(); list.add(“A”); list.add(“B”); list.add(“C”); … ListIterator<String> iterator = list.listIterator(); While (iterator.hasNext()) System.out.println(iterator.next()); … ListIterator<String> iterator = list.listIterator(); // cursor is just before the first element iterator.next(); // advance cursor iterator.add(“X”); // AXBC … iterator.remove(); // the documentation is confusing – is X or A removed? 65 5. Consistency – the operations in a class should be consistent with each other with respect to names, parameters and return values, and behavior. e.g., from the Java library: new GregorianCalendar(year, month-1, day) The month is between 0 and 11 but the day is between 1 and 31! 66 Inheritance (type/subtype relationship) A B C B is_a A Substitutability: A’s can be replaced by B’s or C’s; they have the same behavior (maybe B has more behaviors or refines behaviors of A - all public methods of A are available in B) Subclassing heuristics Specialization: subtype B is_a A ==> B is a subclass of A (Lion is_a Mammal; Toyota is_a Car; ...) If we have B has_a_component A then we should create an instance variable to record A (A is an attribute of B..e.g. Human has_a Arm) Specification: parent has abstract methods (like an interface); specifies behavior that the subclass implements Construction: use parent based on its behavior; e.g. a Stack is_a List (limit adding/removing operations) - this violates the is_a relationship so the Stack is not really a subtype of List (dangerous) Consider a symbol table for a compiler where the keys are symbol names and the values are fixed fields; the attributes of symbols. An Array has keys that are integer values. A Dictionary is like an Array but the keys are arbitrary values. A) class SymbolTable : Dictionary B) class SymbolTable : Object or (we will use an instance variable to record the Dictionary) A) is shorter to code (an important consideration) than B) since we are using inheritance rather than rewriting methods; but we can manipulate a SymbolTable as a Dictionary !!!!! 67 This would allow us to use the protocol of Dictionary in (perhaps) a meaningless fashion. What if we want to re-implement SymbolTable as a HashTable...users’ code might use Dictionary protocol !!!! Solution B) is more clear to users. We are using Dictionary to help construct SymbolTable. Generalization/Extension: In this case the subtype extends (or, generalizes on) the behaviour of the supertype (e.g. use when building on the system classes); It’s better to use Specialization rather than Generalization (except when it’s necessary -- we can’t change the system classes). For example, suppose when we define the system we include a Window class where the background is only black and white. Then we might add ColoredWindow : Window class where ColoredWindow contains a field to indicate the additional background colors Limitation: limit parent’s behavior Consider defining class Stack : Deque where in Stack we override the irrelevant parts of Deque (a Double ended queue) protocol with error messages. This violates the is_a relation and should be avoided where possible. We are imposing limitations on the Deque. 68 Variance: child is a variation of parent. Suppose we have Mouse and GraphicsTablet classes with similar code but neither is really a subclass of the other (these are variations of pointing devices). Then maybe we should make one a subclass of the other and use limitation to override methods where appropriate (e.g. class GraphicsTablet : Mouse ). It would be better to use an abstract superclass such as: class PointingDevice { … } class Mouse : PointingDevice { … } class GraphicsTablet : PointingDevice { … } Combination: inherit features from more than one parent (multiple inheritance) 69 Costs of Inheritance Benefits of Inheritance execution speed (associated with dynamic Software Reuse binding) program size (use general classes) code sharing message passing overhead improved reliability - build from pre-tested components program complexity (complex inheritance consistency of interface - abstract classes hierarchy - dependencies) rapid prototyping (due to reuse) polymorphism information hiding Software Reuse via inheritance via composition (has_a relation; Person has_a Arm; Class fields) The right culture is needed to support reuse must build solid reusable components must provide guidelines for producing reusable components willingness to reuse by programmers willingness of managers to pay for development 70 Subclass/Subtypes 1. Static vs dynamic typing: how are variables and values related; id’s/variables denote values (strings of bits) Static typing - the type is associate with a variable - e.g. float f; … f = “xxx”; the compiler catches this error Dynamic typing - the type is associated with a value; normally a reflection mechanism is used to query the type of the value 2. Static vs dynamic binding: method invocation Should the binding for information be associated with the static class of a variable or the A dynamic class? B C A a; // static class is A; dynamic class could be B or C Efficiency - static is faster Error detection - with static one can catch errors at compile time Flexibility - static is less flexible, creates rigidity, inhibits reuse (can’t predict subclasses which would want to refine methods) 3. Subclass vs subtype: how a class can be used in place of another 71 Polymorphic Variables is_a class B : public A { … } A a; B b; b is in essential respects a representative of A values of type B can be assigned to variables of type A; reverse is not normally permitted e.g. a = b; // ok b = a; // not ok ; this is Reverse Polymorphism A subclass is a class that can be substituted (using polymorphism) with no observable effect. Not all subclasses are subtypes. C++ - use subclasses for subtypes Java - use subclasses or interfaces for subtypes Reverse Polymorphism class B: public A {…} B b; A a; … b = a: // ?? problem of identity - can I tell if “b” is holding a “A”? task of assignment assign value of A to variable of B? problem solved if reflection is available polymorphic variables: values maintain their types (reflection) 72 Smalltalk Dynamic type polymorphic variables legality of message passing checked at runtime reverse polymorphism is not a problem C++ Instances of classes are statically typed, not polymorphic; pointers and references can hold polymorphic values Values don’t have type information Method binding can be performed using dynamic class (pointers and refs - virtual) Legality of message passing checked at compile time based on static type No facilities for testing reverse polymorphism Assignment of reverse polymorphic variables can be performed using a cast RunTime Type Identification, a reflection mechanism, was added; now we can do: b = dynamic_cast <A*>(a) -- this is a downward cast Java All variables know their dynamic type Reverse polymorphism is alright with a cast dynamically checked Reflection mechanism is available Data fields can be overwritten (not like in C++, etc.) Subtype hierarchy formed using interfaces Interfaces (subtypes) are like abstract classes; supports multiple inheritance 73 Replacement vs Refinement Replacement: child class method replaces parent class method Refinement: merge methods with the same name in child and parent classes (e.g. if hierarchy has Parent and Employee; both classes might have a print method) guarantees behavior in the parent class Simula has this; in other languages users can call parent method e.g. in C++: class A: public B { … int f() { B::f(); … } } in Smalltalk we use the super pseudo-variable If the language uses replacement semantics (Java) then refinement semantics can be simulated (the print method in the subclass can call the print method in the superclass). If the language uses refinement semantics then we cannot simulate replacement semantics. Replacement semantics voids the is_a property Define different behaviors Other methods that depend upon behavior from parent class may fail prone to errors C++ use the virtual declaration in the parent class to indicate replacement the virtual declaration introduces polymorphic methods (binding based on dynamic class) without virtual declaration - binding based on static class Smalltalk 74 no syntax provided to indicate replacement constructors use refinement; all other methods use replacement (method in child with same name as parent indicates replacement) Java can replace data fields can use the final declarator to prohibit method replacement or subclassing 75 Implementation Issues is_a class Person { public: virtual void f(); private: Name n; } class Professor : public Person { public: virtual void f(); private: Classes c; } Professor is_a Person variable of type Person should be able to hold values of type Professor Person per; Professor prof; Options for allocating memory for per: 1. Allocate only for the base: C++ uses this policy; what if we want to assign per = prof? The extra fields are sliced off. Slicing information can be dangerous (C++ uses slicing - the c data field of prof would be sliced Member function binding: use static type (whether virtual or not); for virtuals with refs or pointers use dynamic information e.g. per = prof; per.f(); // Person Person* pper; Professor* pprof; pper = &prof; pper->f(); // Professor pprof = &prof; pprof->f(); // Professor 76 2. Allocate memory that can accommodate the largest subclass This solves the slicing problem but often allocate too much space; must know all classes before allocating 3. Allocate space for pointers The data is on the heap; Smalltalk and Java use this option; the user explicitly allocates space (e.g. Person p = new Person();) Leads to pointer semantics for assignment and equality testing Copy vs pointer semantics - make new copies of object or refer to the same copy If x = y and change x then y is changed (they both refer to the same object) If you free x then what happens to y???? C++ : overload the equal operator for any desired meaning Smalltalk/Java : use pointer semantics and perhaps construct clones Equality testing recursively test fields 77 Covariance and Contravariance (Overriding methods) Covariance: argument(s) are made more general redefine method with an argument as e.g. Person rather than Female Contravariance: argument(s) are made more restrictive redefine method with an argument as e.g. Male rather than Person Both of these can destroy the is_a relation; semantics are tricky; most languages forbid both Covariant Return Types You cannot have two methods in the same class with signatures that only differ by return type. Until the J2SE 5.0 release, it was also true that a class could not override the return type of the methods it inherits from a super class. This feature removes the need for excessive type checking and casting. Consider the following examples, which help to describe the advantage of this change to the Java language. public class Tree { ... public Tree getKid(int i) {...} ... } public class BinaryTree extends Tree { public BinaryTree getKid(int i) {...} public BinaryTree getLeftKid() {...} // getLeftKid is not defined in Tree ... } If you use a version of the JDK prior to J2SE 5.0, BinaryTree will not compile. However, with J2SE 5.0 BinaryTree will compile. In fact, you would be able to do something like ... BinaryTree bt = new BinaryTree(t1, t2); ... BinaryTree btt = bt.getKid(2).getLeftKid(); Note that this avoids casting bt.getKid(2) to a BinaryTree prior to invoking getLeftKid 78 Multiple Inheritance Consider: class Animal { … } class EndangeredSpecies { … } class Leopard: public Animal, public EndangeredSpecies { … } Classes Animal and EndangeredSpecies are orthogonal (their features/behaviors don’t overlap) Problems: Name ambiguity: class A{ .. void move(); … } class B { … void move(); … } class C: A, B{…} C c; c.move(); // which move will be executed?? Common ancestor Employee Programmer Manager VicePresident how many Employee subparts does a VicePresident possess?? Interfaces were included in Java to support multiple inheritance while avoiding the above problems 79 Polymorphism (deals with types) Variable polymorphism: variables hold values of different types. Names might be associated with different function bodies Functions with arguments that are polymorphic Forms of Polymorphism Variable: variables hold values from subclass type; the dynamic type must be a subclass of the static type; C++ only allows pointer and reference values with polymorphic variables; this is the basis for much of the power of OOP Pure polymorphism: functions with polymorphic arguments Consider the provision of looping mechanism with Smalltalk Collections Collection do: aBlock LinkedList: Collection |i| i = self first. (i nonNil) ifTrue: [aBlock value: i. first Set: Collection first … … next next … … i = self next] the argument aBlock is really any object that responds to the value: message (with one argument) 80 Overloading: function names that can denote two or more function bodies e.g. note + can denote integer addition or float addition or cout << “a string” << 5; The type signature (number, ordering and type of arguments)determine the body. The language can use either the static type signature or dynamic type signature. Overriding: when child class changes the meaning of the function in the parent class Different child classes can override in different ways Parent class can have default behavior Contributes to code sharing Ensures common interfaces e.g. in Java: public abstract class Component … { … public void addnotify(); … } public class Button extends Component { … public void addnotify(); … } Deferred methods: generalization of overriding; parent doesn’t have the body of the method; child must have the body (else it’s an abstract class, too) e.g. in C++ class Shape { class Circle: public Shape { … … void virtual draw() = 0; … } void draw() { drawCircle(); } … } Forces operating on polymorphism: execution efficiency vs ease of development 81 Visibility and Dependence Visibility: attribute of names Dependency: the degree to which one software component relies on another to perform its responsibilities (limits code reuse) Intentional Dependency: the dependee doesn’t know the dependent - e.g. consider the Model View Controller design pattern where a dependency manager is used so when an object changes, the manager is notified; the manager then notifies the registered dependents. Coupling and Cohesion Modules (Classes) should exhibit internal cohesion and minimize external coupling Coupling: relationships between modules least desirable Internal Data Coupling Global Data Coupling Control (or sequence) Coupling Parameter Coupling Subclass Coupling most desirable Internal Data Coupling - One module modifies local data (e.g. instance variables) of another ...BAD. Hard to understand and reason about. 82 Global Data Coupling - Modules use common global data structures...BAD. Should make a class to manage data e.g. double temperature; class A { public modulateDevice() { if (surfaceTemp > 50.5) temperature += 10.0; } } class B { public printTemp() { System.out.println(“Current temperature: “ + temperature); } } Note that with Global Data Coupling it is difficult to understand classes in isolation; each class by itself is incomplete. Control Coupling - Perform operations in a fixed order but the order is controlled by another module. e.g. A database system does initializing, updating, etc. but other modules call these routines (the database system should check sequencing of requests). Parameter Coupling - the actual and formal parameters are coupled (not much problem here) Subclass Coupling - describes the sub/super-class relationship 83 Cohesion: relationships within a module least desirable Coincidental Cohesion Logical Cohesion Temporal Cohesion Communication Cohesion Sequential Cohesion Functional Cohesion Data Cohesion most desirable Coincidental Cohesion - no apparent reason for grouping elements -- poor design (e.g. a class has unrelated methods) Example: public class Mailbox { public addMessage(Message aMessage) {...} public Message removeCurrentMessage() {...} public Message getCurrentMessage() {...} public void processCommand(String command) {...} ... } Note how processCommand seems to be quite different from the other methods – the other methods deal with a single abstraction: a mailbox that holds messages. Since there are many issues that must be dealt with when considering commands, it would be best to use another class for that abstraction. Logical Cohesion - a logical connection among elements but no data or control connection (e.g. consider a math library containing functions for sin, cos, etc. ) Temporal Cohesion - elements are used at about the same time (e.g. program level initializations -- maybe we should disburse the initialization activities to appropriate modules) 84 Communication Cohesion - all elements access same input/output data or devices; the class acts as a manager for the data (device) Sequential Cohesion - to avoid sequential coupling; the elements must activate in a particular order. Functional Cohesion - elements are all related to performing a single function Data Cohesion - e.g. when a class is used to implement a data abstraction Closely associated with the notions of coupling and cohesion is the Law of Demeter (K. Lieberherr, I. Holland, and A. Riel, Object-Oriented Programming: An Objective Sense of Style, Proc. ACM OOPSLA 1988 conf., San Diego, California, September 1988). This is stated as follows: Law of Demeter For all classes C, and for all methods M attached to C, all objects to which M sends a message must be instances of classes associated with the following classes: 1. The argument classes of M (including C). 2. The instance variable classes of C. (Objects created by M, or by functions or methods which M calls, and objects in global variables are considered as arguments of M.) Strong Law of Demeter The Strong Law of Demeter defines instance variables as being ONLY the instance variables which make up a given class. Inherited instance variable types may not be passed messages. This Strong Law of Demeter means that a method should only use: Instance fields of its class 85 Parameters Objects that it constructs with new Thus, the following situation, where an accessor is used to return a component upon which a class is constructed, is not acceptable: Consider the use of a MailSystem with a findMailbox method to yield a mailbox object, mbox. Subsequently, the Connection method might request the mbox to add/delete messages. This would break the encapsulation of the MailSystem. Perhaps a future version might use, e.g., a database to hold messages. In this case, the developer would have to manufacture Mailbox objects for backward compatibility! A possible solution would be to have the Connection object request services of the MailSystem, which in turn would delegate the message to the Mailbox object it maintains. Although this would be a bit tedious and time-consuming, there are compilation techniques that can be used to optimize the process. Weak Law of Demeter The Weak Law of Demeter defines instance variables as being BOTH the instance variables which make up a given class AND any instance variables inherited from other classes. Note that each of the above variants rules out one object directly manipulating internal data values of another object must use methods to effect change of state of other objects Refer to the aforementioned article, as well as the article [M.Sakkinen, Comments on "The Law of Demeter" and C++, SIGPLAN NOTICES,v.23 (12), December, 1988] for further discussion on these issues of programming style. 86 Overriding versus Shadowing Shadowing: class A { int x; public void m(int x) { // this parameter ‘x’ shadows the instance variable x int y = x+1; // we can refer to the instance variable ‘x’ as this.x while (y > 0) { int x = 2; // this ‘x’ shadows the parameter ‘x’ y -= 1; } } } class Parent { int x = 1; } class Child extends Parent { int x = 5; // this x shadows the x in the Parent } Parent p = new Parent(); System.out.println(p.x); 1 Child c = new Child(); System.out.println(c.x); 5 p = c; System.out.println(p.x); 1 87 Overriding: Accessibility and Exceptions When you override a method, you cannot make it less accessible – e.g., if a method in a superclass is public then you cannot make the method in the subclass private. Consider the following situation: public class Parent { ... public void print() {...} ... } public class Child extends Parent { ... private void print() {...} ... } Parent p = new Parent(); p.print(); // this is fine p = new Child(); // variable polymorphism allows this p.print(); // since dynamic binding is used in Java, it expects the print method in // the class associated with the object referred to by p!! When you override a method, you cannot throw more checked exceptions than are already declared in the superclass method. public class Parent { ... ... public void print() throws RuntimeException {...} ... } } public class Child extends Parent { ... public void print() throws RuntimeException, SecurityException {...} ... } Parent p = new Parent(); try { p.print(); } catch (RuntimeException e) {} // this is fine p = new Child(); try { p.print(); } catch (RuntimeException e) {} // this would not compile!! 88 ... C++: Requires an explicit indication of overriding; it will perform a shadowing if the virtual reserved word is not provided. class Parent { public: void example() { cout << “Parent”<<endl; } } class Child: public Parent { public: void example() { cout << “Child”<<endl; } } Parent *p = new Parent(); p->example(); Parent Child *c = new Child(); c->example(); Child p = c; p->example(); Parent We can use overriding by declaring example as a virtual function in Parent. Overriding: The type signatures are the same in both parent and child classes, and the method is declared as virtual in the parent class. Shadowing: The type signatures are the same in both parent and child classes, but the method was not declared as virtual in the parent class. Redefinition: The type signature in the child class differs from that given in the parent class. 89 A Note on Protected Variables and Methods Consider a Shape hierarchy: public class CompoundShape { private GeneralPath path; public CompoundShape() { path = nwe GeneralPath(); } protected void add(Shape s) { path.append(s); } ... } public class HouseShape extends CompoundShape { public HouseShape(...) { Rectangle2d.Double base = ...; add(base); ... } ... } Note that the CompoundShape class provides the add method as part of its interface provided to subclasses. Thus, we distinguish two kinds of interfaces – the public interface (allowing any client to access the resource) and the protected interface (allowing subclasses access to resource). However, it would not be desirable for malicious subclasses to misuse the protected interface. With this in mind, the following security measure applies: methods can use protected features only on objects of their own class. This is to prevent the following attack: public Attacker extends CompoundShape { // tries to call protected add method void uglify(HouseShape house) { ... house.add(aShape); // won’t work – can only call add on other Attacker objects } } It would not be advisable to change the modifier for the path instance variable to protected from private. This would allow other classes in the same Java package to access the variable! 90 Frameworks and Patterns Use groups of classes already organized for various purposes. Application framework series of classes that describe a skeleton application provides structure, not content subclass and override to provide content extensive use of polymorphism e.g. Microsoft Foundation Class Framework includes a DropDownList class; the user can subclass this class (e.g. MyDropDownList) to add new behavior. Contrast a Framework with a library: Main program User Code Framewor k Library code Framework Library User Code The Framework has real code; just add code to change and embellish its behavior Design patterns describe how to organize classes to solve problems - e.g. MVC 91 Another Look at Classes They are templates for making clones of some object They are generalizations of records It’s the mechanism used for encapsulation It’s a special kind of type It’s a special kind of object: As an object it has the following responsibilities: Maintains information about the class (e.g. instance variables, methods) Generates instances If a class is an object and every object is an instance of a class what is the class of a class? A metaclass is the class of a class; this suggests another class hierarchy, which includes metaclasses, which help describe more aspects of an Object Oriented Programming Language. The use of metaclasses provides the capability to implement a more fluid language - the behavior of classes is provided by the user (most likely, still the implementer) of the language rather than the implementers of the language. This allows users to augment the basic behavior of classes for language experimentation. Implementers will be presented with a less complex task (e.g. for porting). Finally, the documentation of these aspects is provided in the language of interest, rather than externally by the implementor. 92 OOP AND SOFTWARE ENGINEERING ABSTRACTION extract essential details omit inessential details each level of decomposition represents an abstraction each level must be completely understood as a unit our view of the world forms ladders of abstraction INFORMATION HIDING (used to support the management of abstractions) make details of an implementation inaccessible enforce defined interfaces focus on the abstraction of an object by suppressing the details prevent high level decisions from being based on low level characteristics 93 ABSTRACTION AND INFORMATION HIDING supported by OOP's CLASS mechanism directly supports the management of complexity LOCALIZATION physical proximity collection of logically related entities definitions of program entities should appear local (close) to the uses of the entities 94 Software Engineering Concepts SURFACE AREA The SURFACE AREA of a programmer's code is the amount of information that must be understood and properly dealt with for another programmer to use it correctly. SMALL IS GOOD; LARGE IS BAD FACTORS INFLUENCING SURFACE AREA 1. Information hiding -- minimize the number of names that a consumer must know to use a supplier's code correctly. 2. Function/procedure profiles -- users of functions/procedures must know number/types/order of parameters, return type(s) 3. Timing -- if a consumer must remember to allocate object then initialize then use then dispose 4. Concurrency -- consumer must be aware of any timing problems. HARDWARE IC's: the chip manufacturers can deliver a tightly encapsulated unit of functionality; independent of any particular application; it's reusable SOFTWARE IC's: objects are tightly encapsulated; designed, developed, documented independent of any particular application; it's reusable consider a SET IC or SYMBOL TABLE IC or OFFICE MAILBOX IC. consumers can shop for objects with the best features for their applications. 95 SOFTWARE QUALITY External factors: qualities observed by users - correctness perform the tasks as defined by requirements and specifications - robustness performance under abnormal conditions - extensibility ease with which it can be adapted to changes in specs need: design simplicity decentralization minimize module interdependence - reusability reuse for new applications - compatibility easy to combine with other software (e.g. that's why file formats, etc. are standardized) - efficiency (of resource usage) - portability to new hardware/software environments - integrity protection against unauthorized access/modification - ease of use 96 MODULES 1. The program design language must have a strong relation to the language used to implement the design 2. Every module should communicate with as few others as possible 3. If the modules communicate, they should exchange as little info as possible 4. If modules communicate ==> it should be obvious from text 5. All info should be private unless specifically declared to be public 97 TOP-DOWN VS OBJECT-ORIENTED DESIGN TOP-DOWN: stepwise refinement e.g. translate a C++ program into VAX code faults doesn't support extensibility addresses tasks rather than data structures does not promote reusability subtasks are too strongly coupled - e.g. to do the main task we must do subtask A then subtask B then ... Clearly, changes to A will require a rippling effect of changes to B, etc. OBJECT-ORIENTED: most real systems (e.g. operating systems) don't have a single top level function; rather they are a collection of subsystems working together systems are modularized on the basis of their data structures a class may be defined as an extension or restriction of another program entities should be permitted to refer to objects of more than one class, and operations should be permitted to have different realizations in different classes (polymorphism/dynamic binding support these requirements) it should be possible to declare a class as heir to more than one class, and more than once to the same class (multiple inheritance) 98 Unit Testing Quality Assurance experts prior to release test entire applications. It is also important for developers to develop test harnasses to test smaller units of code such as classes. It is critical for the development environment to provide support for this unit testing. The support includes the provision of a test infrastructure that is integrated in the develoment environment. A popular Java OpenSource project that has been developed for unit testing is the JUnit project (http://www.junit.org/index.htm). JUnit is available as a plugin to eclipse. Refer to the section in the Help menu for information on running JUnit (JUnit Cookbook). The following unit test was provided to check the Lexical Analyzer module included in a compiler project: /* * Created on Dec 1, 2003 * * To change the template for this generated file go to * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments */ package lexer; import junit.framework.TestCase; /** * @author Dr. Barry Levine * * To change the template for this generated type comment go to * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments */ public class LexerTest extends TestCase { Lexer lex; /** * Constructor for LexerTest. * @param arg0 */ public LexerTest(String arg0) { super(arg0); } public static void main(String[] args) { junit.swingui.TestRunner.run(LexerTest.class); } /* * @see TestCase#setUp() */ protected void setUp() throws Exception { super.setUp(); try { lex = new Lexer ("D:\\compiler\\simple.x"); }catch (Exception e) {fail(); } } 99 /* * @see TestCase#tearDown() */ protected void tearDown() throws Exception { super.tearDown(); } /* * Main test harnass – only used when running test suites */ public void testLexer() { Token tok = null; try { while (true) { tok = lex.nextToken(); String p = "L: " + tok.getLeftPosition() + "R: " + tok.getRightPosition() + " " + TokenType.tokens[tok.getKind()]; if (tok.getKind() == Sym.Identifier) { p += tok.toString(); } else if (tok.getKind() == Sym.INTeger) { p += tok.toString(); } System.out.println(p + ": " + lex.getSourceLineNumber()); } } catch (Exception e) { assertEquals(tok, null); } } } } Note that the test harnass is project with/without the test changes affecting the classes convenient to re-run the test the eclipse documentation for included in the project. Eclipse can run the harnass. Therefore, when there are code associated with the test harnass it is very prior to building the project. Please refer to details on using JUnit. 100 Squeak/Smalltalk - 80 <<<CH2-TOUR.PPT>>> <<<ch3-joe-sum02.ppt>>> EVERYTHING is an object. A COMPUTATION == MESSAGE passing to objects to cause execution of METHODS. PROTOCOL for a class == set of messages for the class. 101 Identifiers: - instance variables - Class names (begin with a cap letter) - pseudo variables (self, super, nil, smalltalk, args for a method Message Expressiongs Message expr == receiver + message selector + args Unary: no args, parse left to right 3 factorial == 6 3 factorial sqrt == 2.4.. Binary: 1 arg, 1 or 2 adjacent non-alpha chars, parse left to right 7 + 4 == 11 (7 is receiver, + is message, 4 is arg) 7+4*3 == 33 Unary has greater precedence than Binary 7+4 sqrt == 9.0 Keyword: 1 or more keywords (id:) ; 1 arg per key, lowest precedence 7 max:5 == 7 7 max: 4 max: 3 ==> must use parens: (7 max:4)max:3 8 between: 4 sqrt and: 6 + 8 ==> 8 between: (4 sqrt) and: (6+8) Blocks: [ statementsSeparatedByPeriod ] --like a function body --use static scoping for variables --a procedural abstraction [i<-i+1. i print] BLOCKS ARE OBJECTS and can be used wherever objects are needed [...] value ==> evaluate block the value of the block is the last value computed [:p1 :p2 ...:pn | statements] value:a1 ... value:an 102 SELF "self" ALWAYS refers to the ORIGINAL receiver of the message. The justification for this follows: A--->B--->C Consider the class hierarchy where A is a superclass of B and B is a superclass of C. With respect to the "is-a" relation a C is-a B is-a A. Thus, C inherits the behaviour (protocol, methods) of B and A. Suppose a C object (objC) is sent a message and somehow a method that belongs to B (methB) starts executing. Since C "owns" all of its inherited behaviour, it owns methB. Therefore, if methB references "self" it is clearly talking about objC, the ORIGINAL receiver of the message. 103 Class Variables Object subclass: #ClassVariableTest instanceVariableNames: '' classVariableNames: ' cl ' poolDictionaries: '' category: nil. ! !ClassVariableTest class methodsFor: 'set class variable'! cl: val cl _ val ! cl ^cl !! !ClassVariableTest methodsFor: 'all'! cl ^cl !! ClassVariableTest cl: 5. 'class variable set to 5...it is: ' print. ClassVariableTest cl printNl. 'class variable set to 1...it is: ' print. ClassVariableTest cl: 1. ClassVariableTest cl printNl. " use ! following all Smalltalk messages " Smalltalk at: #t put: ClassVariableTest new! 'instance accessing class variable...value: ' print. t cl printNl ! 104 Generators - yield elements in a collection - messages are first, next -- return nil (undefined) when done; once we start generating items DON'T modify the collection. Consider the following (modified) Interval class drawn from the Gnu Smalltalk-80 system: SequenceableCollection subclass: #Interval instanceVariableNames: 'start stop step iterVar' classVariableNames: '' poolDictionaries: '' category: nil. Interval comment: 'My instances represent ranges of objects, typically Magnitude type objects. I provide iteration/enumeration messages for producing all the members that my instance represents.' ! !Interval class methodsFor: 'instance creation'! from: startInteger to: stopInteger by: stepInteger | int | ^self new initializeFrom: startInteger to: stopInteger by: stepInteger ! from: startInteger to: stopInteger ^self from: startInteger to: stopInteger by: 1 !! !Interval methodsFor: 'basic'! first iterVar _ start. ^ iterVar ! next iterVar _ iterVar + step. ^(step > 0) ifTrue: [ (iterVar > stop) ifFalse: [ iterVar ] ] ifFalse: [ (iterVar < stop) ifFalse: [iterVar] ] ! 105 do: aBlock |i| i _ self first. (i notNil) whileTrue: [ aBlock value: i. i _ self next ] ! size step > 0 ifTrue: [ stop >= start ifTrue: [ ^(stop - start) // step + 1 ] ifFalse: [ ^0 ] ] ifFalse: [ start >= stop ifTrue: [ ^(start - stop) // step + 1 ] ifFalse: [ ^0 ] ] ! add: newObject self error: 'elements cannot be added to an Interval' !! !Interval methodsFor: 'testing'! = anInterval ^(start = anInterval start) & (stop = anInterval stop) & (step = anInterval step) !! !Interval methodsFor: 'private methods'! initializeFrom: startInteger to: stopInteger by: stepInteger start _ startInteger. stop _ stopInteger. step _ stepInteger ! start ^start ! stop ^stop ! step ^step !! 106 CLASSES AND METACLASSES Classes are used to describe the behaviour of similar objects (the instances) while metaclasses are used to describe the behaviour of a single object -- the class. Each class designation will include the description of the behaviour of the instances (the class description) and the description of the behaviour of the class object, itself (the metaclass description). In Smalltalk 80 the behaviour of the instances is described using the following form (consider the Date class as an example): Magnitude subclass: #Date instanceVariableNames: ‘days’ classVariableNames: ‘’ poolDictionaries: ‘’ category: nil. ! !Date methodsFor: ‘basic’! addDays: dayCount days _ days + dayCount ! subtractDate: aDate ^days - aDate days !! Note that the methodsFor: message is sent to the Date class. This instructs the interpreter to include the basic category as part of the class description and add the methods below (until the double bang....!!) as instance methods (the methods to be added are addDays: and subtractDate:). Again, the class description describes the behaviour of instances of the class. On the other hand, the behaviour of the class is described using the following form: !Date class methodsFor: ‘instance creation’! today | now date | now _ Time secondClock. date _ now / (24 * 60 * 60). ^self new setDays: date + 25202 "(69 * 365 + 17)" !! 107 This time the Date class message yields the class in which Date belongs (its metaclass). Then we pass the methodsFor: message to include the category instance creation as part of the metaclass description and add the method today. Again, the metaclass description describes the behaviour of the only instance of the metaclass -- the Date class (indicating how Date instances should be created) Note that the inclusion of metaclasses indicates the necessity of parallel hierarchies; the class hierarchy and the metaclass hierarchy. Now we will consider partial descriptions of the Date and Magnitude (Date’s superclass) classes. All source code descriptions of system classes is accessible in the blevine directory. You should peruse these descriptions to help you learn about Smalltalk 80 programming. "============================================================== | | Magnitude Method Definitions | ==============================================================" "============================================================== | | Copyright (C) 1990, 1991 Free Software Foundation, Inc. | Written by Steve Byrne. | | This file is part of GNU Smalltalk. | | GNU Smalltalk is free software; you can redistribute it | and/or modify it | under the terms of the GNU General Public License as | published by the Free | Software Foundation; either version 1, or (at your option) | any later version. | | GNU Smalltalk is distributed in the hope that it will be | useful,but WITHOUT | ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS | FOR A PARTICULAR PURPOSE. See the GNU General Public License | for more details. | | You should have received a copy of the GNU General Public | License along with | GNU Smalltalk; see the file COPYING. If not, write to the | Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, | USA. ==============================================================" " | Change Log 108 | =============================================================== | Author Date Change | sbyrne 25 Apr 89 created. | " Object subclass: #Magnitude instanceVariableNames: ‘’ classVariableNames: ‘’ poolDictionaries: ‘’ category: nil. Magnitude comment: ‘I am an abstract class. My objects represent things that are discrete and map to a number line. My instances can be compared with < and >.’ ! !Magnitude methodsFor: ‘basic’! "Relational operators. Object" ‘==’, ‘~=’, ‘~~’ are inherited from = aMagnitude self subclassResponsibility ! < aMagnitude self subclassResponsibility ! > aMagnitude self subclassResponsibility ! <= aMagnitude ^(self < aMagnitude) | (self = aMagnitude) ! >= aMagnitude ^(self > aMagnitude) | (self = aMagnitude) !! !Magnitude methodsFor: ‘misc methods’! between: min and: max "Returns true if object is inclusively between min and 109 max." ^(self >= min) and: [ self <= max ] ! min: aMagnitude self < aMagnitude ifTrue: [ ^self ] ifFalse: [ ^aMagnitude ] ! max: aMagnitude self > aMagnitude ifTrue: [ ^self ] ifFalse: [ ^aMagnitude ] !! "============================================================== | | Date Method Definitions | ==============================================================" " " | Change Log | =============================================================== | Author Date Change | sbyrne 25 Apr 89 created. | " Magnitude subclass: #Date instanceVariableNames: ‘days’ classVariableNames: ‘‘ poolDictionaries: ‘‘ category: nil. Date comment: ‘My instances represent dates. My base date is defined to be Jan 1, 1901.I provide methods for instance creation (including via "symbolic" dates, such as "Date newDay: 14 month: #Feb year: 1990"‘ ! Smalltalk at: #DayNameDict put: Dictionary new! Smalltalk at: #MonthNameDict put: Dictionary new! !Date class methodsFor: ‘basic’! initialize self initDayNameDict. 110 self initMonthNameDict ! initDayNameDict | dayNames | dayNames _ #( (monday mon) "1" (tuesday tue) "2" (wednesday wed) "3" (thursday thu) "4" (friday fri) "5" (saturday sat) "6" (sunday sun) "7" ). 1 to: dayNames size do: [ :dayIndex | (dayNames at: dayIndex) do: [ :name | DayNameDict at: name put: dayIndex ] ]. ! initMonthNameDict | monthNames | monthNames _ #( (january jan) "1" (february feb) "2" (march mar) "3" (april apr) "4" (may) "5" (june jun) "6" (july jul) "7" (august aug) "8" (september sep) "9" (october oct) "10" (november nov) "11" (december dec) "12" ). 1 to: monthNames size do: [ :monthIndex | (monthNames at: monthIndex) do: [ :name | MonthNameDict at: name put: monthIndex ] ]. ! dayOfWeek: dayName ^DayNameDict at: dayName asLowercase asSymbol ! nameOfDay: dayIndex ^#(Monday Tuesday Wednesday Thursday Friday Saturday Sunday) at: dayIndex ! 111 nameOfMonth: monthIndex ^#(January February April May July August October November ! March June September December) at: monthIndex daysInMonth: monthName forYear: yearInteger | monthIndex | monthIndex _ self indexOfMonth: monthName. ^self daysInMonthIndex: monthIndex forYear: yearInteger ! daysInYear: yearInteger ^365 + (self leapYear: yearInteger) ! leapYear: yearInteger (yearInteger \\ 4 = 0 and: [ yearInteger \\ 100 ~= 0 or: [ yearInteger \\ 400 = 0 ] ]) ifTrue: [ ^1 ] ifFalse: [ ^0 ] !! !Date class methodsFor: ‘instance creation’! today | now date | now _ Time secondClock. date _ now / (24 * 60 * 60). ^self new setDays: date + 25202 "(69 * 365 + 17)" !! !Date class methodsFor: ‘private methods’! yearAsDays: yearInteger "Returns the number of days since Jan 1, 1901." yearInteger _ yearInteger - 1900. ^(yearInteger - 1) * 365 + (yearInteger // 4) - (yearInteger // 100) + (yearInteger // 400) 112 ! daysInMonthIndex: monthIndex forYear: yearInteger | days | days _ #(31 28 31 "Jan Feb Mar" 30 31 30 "Apr May Jun" 31 31 30 "Jul Aug Sep" 31 30 31 "Oct Nov Dec" ) at: monthIndex. monthIndex = 2 ifTrue: [ ^days + (self leapYear: yearInteger) ] ifFalse: [ ^days ] !! !Date methodsFor: ‘basic’! addDays: dayCount days _ days + dayCount ! subtractDate: aDate ^days - aDate days !! !Date methodsFor: ‘private methods’! days ^days ! setDays: dayCount days _ dayCount !! 113 Lets consider the hierarchy including Date, Magnitude and Object (partial protocols are included to aid in distinguishing between classes and their metaclasses). Note that gray arrows indicate the instance relation, whereas, black arrows indicate the subclass/superclass relation. Everything is an Object. We need to extend the hierarchy to include classes that describe the behaviour of all classes. This enables us to deal with the behaviour in common with all classes. The following hierarchy extends the one given above to include classes/metaclassses to deal with behaviour in common with all classes. Page 272 of the text shows how the entire hierarchy is put together. 114 Note the circularity between Metaclass and Metaclass Metaclass. Since every metaclass is an instance of the Metaclass class we must include the arrow to the left. But we also have every class is the single instance of its metaclass so we must include the arrow to the right. 115 Reflection and Persistence (Appendix 5) Reflection Abstraction principle: The implementation of a module should be independent of its interface. Next to modularity, abstraction is probably the most important principle in engineering for controlling the complexity of large systems. A module that hides its implementation from its clients is called a black box module - easier to use and easier to replace. Reflective module - clients can inspect and alter some aspects of its implementation. Reflective Systems A reflective or open implementation (OI) system: 1. Base level - objects provide application services through the system's normal user interface. 2. Meta level – encapsulate policies, structures, and strategies used by a base-level object to implement these services 116 A meta interface or meta object protocol (MOP) allows clients - users and base-level objects - to access meta-level objects. Through the meta interface a client can inspect, modify, or replace a meta-level object, thus dynamically changing the behavior of the associated baselevel objects. Example: Reflective Interpreters and Compilers LISP programs are executed directly by programs called interpreters. Executing a program involves many policies that determine the semantics of the language. These policies determine things such as how parameters are passed (reference, value, name, etc.), how assignments are made (shallow or deep copy), how non-local variables are located (static or dynamic scoping), procedure call evaluation algorithms (lazy or eager), etc. Adding reflection (i.e., a meta level) to an interpreter allows programs and users to modify these policies to optimize performance: C++ keywords such as virtual, inline, and register can be seen as part of the C++ compiler's meta interface. Example: Reflective Operating Systems 117 Reflective operating systems can provide process meta interfaces to expose scheduling and synchronization policies, memory management meta interfaces like Mach's External Memory-Management Interface (EMMI) to expose page fetch and replacement policies, and file system meta interfaces like AFS to expose buffer management and disk head scheduling policies. Example: Database Systems All database systems are reflective systems - database includes a system catalog (also called a data dictionary or a data directory) that contains meta-data, or data about the data in the database. For example, the data in a relational database is organized into tables. The system catalog is a meta-level table that contains a row describing each base-level table. The information in this row might include: 1. Names, types, and sizes of each column of the base-level table. 2. Names of relationships to other base-level tables. 3. Names of users authorized to access the base-level table. 4. Constraints on the data in the base-level table. 5. Usage statistics. For example, the system catalog might include a table of the form: In addition, the system catalog might also tell us that salaries must be non-negative, etc. 118 Applications and users query a database through a database management system (DBMS). The DBMS uses a component called the catalog manager to consult the database's system catalog to determine how to answer the query. Strategies Strategy - encapsulates some application-wide policy or algorithm. For example, a strategy object for a network might encapsulate a buffering policy, a routing algorithm, or a congestion control algorithm. Strategy [Go4], [WWW 5] Other Names Policy Problem An application domain policy may affect the behavior of application objects belonging to many different classes. For example, a government taxation policy affects the behavior of consumers, investors, and firms. The policy may change often, or the policy may be different for different instances of the same class. Solution Encapsulate the policy in a separate meta-level object. Each application (i.e., base-level) object maintains a reference to a policy object. Changing the policy object changes the behaviors of all associated application objects. Contrast the strategy pattern with the decorator pattern: 119 Gamma, et. al. [Go4] express the comparison more colorfully: "A decorator lets you change the skin of an object; a strategy lets you change the guts." Static Structure Strategies are instances of classes derived from an abstract strategy base class, which specifies the meta interface. Java example: layout containers. class GUI extends JFrame { public GUI() { Container contentPane = getContentPane(); contentPane.add(new Button("STOP")): contentPane.add(new Button("GO")); // etc. } } How does the add() method know where to add the controls? Layout manager is associated with awt's Container class: setLayout(new FlowLayout()); Example - financial application value() computes the future value; depends on the risk and yield of the selected investment strategy: 120 Runtime Classes Refer to “CLASSES AND METACLASSES” section in the Smalltalk chapter of the course reader. Runtime class: Meta-level object that represents a class. class Class { ... } // instances are runtime classes Class rtc = new Class(...); or Class rtc = Class.forName(...); Ex. Appliance app = new Refrigerator(...) Class rtc = app.getClass(); rtc points to an instance of Class that represents the Refrigerator class. String name; System.out.println("Enter a class name: "); String name = System.in.readLine(); Class rtc = Class.forName(name); // allows for more dynamic // activities What is the runtime class of a runtime class? Class rtc2 = rtc.getClass(); // rtc2 = ??? rtc2 points to a runtime class that represents the Class class, as does rtc2.getClass()! Services of a runtime class include runtime type information. Class rtc = app.getClass(); if (rtc.getName() == "Refrigerator") ((Refrigerator)app).getTemperature(); Class rtc2 = rtc.getBaseClass(); System.out.println(rtc.getName()); // prints "Appliance" rtc2 = rtc.getClass(); System.out.println(rtc2.getName()); // prints "Class" Dynamic instantiation: Appliance app2 = rtc.newInstance(); This is useful for framework factory methods that must construct objects without knowing their identity in advance. class Method { ... }; class Field { ... }; 121 Method[] funs = rtc.getMethods(); Field[] vars = rtc.getFields(); This information, combined with dynamic creation, can be used to automate the process of reading and writing base-level objects to files and databases. Runtime Type Information in Java For example: Document x = new Report(); Class c = x.getClass(); System.out.println("class of x = " + c.getName()); c = c.getSuperclass(); System.out.println("base class of x = " + c.getName()); c = c.getSuperclass(); System.out.println("base of base class of x = " + c.getName()); x = new Memo(); c = x.getClass(); System.out.println("now class of x = " + c.getName()); produces the output: class of x = Report base class of x = Document 122 base of base class of x = java.lang.Object now class of x = Memo class Document { public int wordCount = 0; public void addWord(String s) { wordCount++; } public int getWordCount() { return wordCount; } } Document x = new Document(); Class c = x.getClass(); x.addWord("The"); x.addWord("End"); Method methods[] = c.getMethods(); for(int i = 0; i < methods.length; i++) System.out.println(methods[i].getName() + "()"); Field fields[] = c.getFields(); try { System.out.print(fields[0].getName() + " = "); System.out.println(fields[0].getInt(x)); } catch(Exception e) { System.out.println("fields[0] not an int"); } produces the output: getClass hashCode equals toString notify notifyAll wait wait wait addWord getWordCount wordCount = 2 Notice that the methods inherited from the Object base class were included in the array of Document methods. (There are three overloaded variants of the wait() function in the Object class.) If wordCount is declared private, as it normally would be, then its value doesn't appear in the last line: wordCount = Dynamic Instantiation Frequently occurs when we are developing frameworks that must create instances of classes that will be specified in customizations of the framework.: Factory Method [Go4] 123 Other Names Virtual constructor. Problem A "factory" class can't anticipate the class of "product" objects it must create. Solution Provide the factory class with an ordinary member function that creates product objects. This is called a factory method. The factory method can be a virtual function implemented in a derived class, or a template function parameterized by a product constructor. Dynamic Instantiation in Java String cName; // holds a class name Object x; Class c; try { cName= "Horn"; c = Class.forName(cName); // find & load a class x = c.newInstance(); c = x.getClass(); System.out.println("class of x = " + c.getName()); cName= "Drum"; c = Class.forName(cName); x = c.newInstance(); c = x.getClass(); System.out.println("now class of x = " + c.getName()); } catch(Exception e) { System.out.println("Error: " + e); } produces the output: class of x = Horn now class of x = Drum Calling Class.forName("Horn") locates the file containing the declaration of the Horn class, compiles it if necessary, then dynamically links it into the program. Dynamic Instantiation in C++ (The Prototype Pattern) Unfortunately, C++ doesn't have built-in support for dynamic instantiation, but the prototype pattern provides a standard way to add this feature to our C++ programs: Prototype [Go4] Problem A "factory" class can't anticipate the class of "product" objects it must create. 124 Solution Derive all product classes from an abstract product base class that provides a factory method that uses a type description parameter to determine the type of product to create. The type description parameter is used to locate a prototype, which is then copied, customized, and returned by the factory method. Prototype Table class Product { String getClass() const; abstract Product clone(); static Product makeProduct(String type); // factory function static Product addPrototype(Product p); private static Table protoTable; }; Product makeProduct(string type) { Product proto; if (find(type, proto, protoTable)) return proto->clone(); else return null; } 125 Persistence An object that can be saved to and restored from a file or database is called a persistent object. Objects that can't be saved and restored are called transient objects (e.g., windows and menus). Databases meta-data or the system catalog - information about its own structure. Database management system (DBMS), first determines if the user is authorized to access the database and if the request is consistent. If so, then the meta-data is used to devise a strategy for retrieving the required data, the data is retrieved and sent back to the user. Find all calculus students. Eliminate those who are not taking calculus from Professor Smith. Eliminate all information about these students except name and GPA. Relational Databases Relational databases - organized into tables or relations INGRES, ORACLE, Access, FoxPro, Informix, Sybase, and DB2 are examples of relational database management systems (RDBMS). Records (rows), represent objects Attributes (columns) Example Three tables represent Student, Teacher, and Course. Each student takes exactly three courses per term, and each teacher teaches exactly three courses per term: 126 Rows represent students; entry in a given row and column is called an attribute value: Candidate key - attribute that is unique for each record (e.g., ID) Primary key -a selected candidate key Foreign key - a candidate key in another table; used to express links between records, hence associations between classes. Instructor column - foreign keys (ID numbers of teachers in the Teacher table) SQL "Structured Query Language" - ISO standard language for defining and manipulating relational databases. SELECT INSERT UPDATE DELETE ... ... ... ... To To To To query data in a database insert rows into a table update rows in a table delete rows from a table Select is the most common command. Its basic format is: SELECT column, column, ... FROM table, table, ... WHERE condition 127 For example, the query: "What are the names of all students who have at least a 2.0 grade point average and who take calculus during first period?" can be expressed in SQL by: SELECT LastName, FirstName FROM Students WHERE Period1.Title = "Calculus" AND 2.0 <= GPA Result is a new table created from the tables listed in the FROM clause. Columns of the result table are those listed in the SELECT clause. The rows of the result table are those meeting the condition specified in the WHERE clause. If there is no WHERE clause, then no rows are filtered from the result table. Tables that are explicitly stored in the database are called base tables. Tables constructed from executing queries are called virtual tables or views. The tables listed in the FROM clause might be base tables or virtual tables. Interfaces to Relational Databases Open Database Connectivity (ODBC) is a standard RDBMS API being proposed by Microsoft. To implement ODBC, RDBMS vendors provide a driver in the form of a DLL (Dynamic Link Library) that interfaces with the RDBMS API and interprets ODBC function calls. Another way DBMS vendor-dependency can be eliminated is by using embedded SQL. Embedded SQL allows programmers to embed SQL statements directly into their source code. Subsequently, a preprocessor replaces them with calls to vendor-specific API functions that directly access the RDBMS. Object Oriented Databases and ODMG 2.0 Object databases are collections of objects organized into classes that are related by association and specialization. Object Database Management Group (ODMG) - define standards for object oriented database management systems (OODBMS). Version 2 of the standard (ODMG 2.0) appeared in 1997. Object fault - transparently locates the requested object using the object's unique object identifier number (OID), translates the object into a C++ (or Java or Smalltalk) object, then loads the object into main memory. If the program updates an object (and commits to the update), the procedure is reversed: the ODBMS translates the C++ (or Java or Smalltalk) object into an ODMG object, then writes the translated object back to the database. 128 Data Streams Output Streams There are many classes of output streams derived from the abstract OutputStream base class. A file output stream is associated with an output file. A filter output stream is associated with the output stream that it filters: System.out and System.err are instances of the PrintStream class. They should only be used for debugging. 129 Input Streams Object Streams Reading and writing objects presents three problems: 1. An object may contain references to other objects. When saving the object, these other objects must also be saved. Object digraph, where nodes represent objects and arrows represent references: 2. An object digraph may contain cycles or multiple paths to a single object. We want to avoid duplicating objects in the object stream and we want to avoid non terminating recursions. Don't want to have two copies of c or x in the object stream. 3. The reading program must know how much memory to allocate to hold the object digraph. Using its run time type identification features, Java automatically solves all three problems. 130 interface Serializable {} Most pre-defined classes implement this interface. Example Address Book class AddressBook implements Serializable { private Person[] people; private int size, maxSize; AddressBook() { maxSize = 50; size = 0; people = new Person[maxSize]; } public void addPerson(Person p) { if (size < maxSize) people[size++] = p; else Tools.error("Address book is full"); } 131 public Person findPerson(String name) { boolean found = false; Person result = null; int i = 0; while (!found && i < size) found = name.equals(people[i++].getName()); if (found) result = people[i - 1]; return result; } public void println() { for(int i = 0; i < size; i++) { people[i].println(); System.out.println("++++++++++++++++++"); } } } // AddressBook Person class Person implements Serializable { private String name; private Address address; private String phone; public Person(String nm, Address addr, String ph) { name = nm; address = addr; phone = ph; } public String getName() { return name; } public Address getAddress() { return address; } public String getPhone() { return phone; } public void println() { System.out.println("Name = " + name); address.println(); System.out.println(" " + phone); } } // Person Address class Address implements Serializable { private int number; private String street; private String city; private String state; public Address(int n, String st, String c, String s) 132 { number = n; street = st; city = c; state = s; } public void println() { System.out.println(" System.out.println(" } } // Address " + number + " " + street); " + city + ", " + state); An Object Writer class ObjectWriter { public static void test(String outFile) { Address addr1 = new Address(123, "Sesame St.", "New York", "N.Y."); Address addr2 = new Address(1600, "Pennsylvania Ave.", "Washington", "D.C."); String ph1 = "(212) 653-9875"; String ph2 = "(105) 953-5555"; Person george = new Person("George Jetson", addr1, ph1); Person spock = new Person("Spock", addr2, ph2); Person judy = new Person("Judy Jetson", addr1, ph1); // make an address book AddressBook book = new AddressBook(); book.addPerson(george); book.addPerson(spock); book.addPerson(judy); book.println(); // do judy and george have the same address? System.out.print("george.getAddress() == judy.getAddress() = "); System.out.println(george.getAddress() == judy.getAddress()); try { ObjectOutputStream bookStream = new ObjectOutputStream( new FileOutputStream(outFile)); bookStream.writeObject(book); } catch(IOException e) { Tools.error("Error: " + e); } } } // ObjectWriter Here's the object digraph created. Note that Judy and George both contain references to the same address. This will be proved by comparing their addresses using ==. 133 Object Reader class ObjectReader { public static void test(String inFile) { AddressBook book; // just a reference try { ObjectInputStream bookStream = new ObjectInputStream( new FileInputStream(inFile)); book = (AddressBook) bookStream.readObject(); book.println(); Person x = book.findPerson("George Jetson"); Person y = book.findPerson("Judy Jetson"); if (x != null && y != null) { // do judy and george have the same address? System.out.print("george.getAddress() == judy.getAddress() = "); System.out.println(x.getAddress() == y.getAddress()); } else Tools.cout.println("Find failed!"); } // try catch(IOException e) { Tools.error("Error: " + e); } catch(ClassNotFoundException e) { Tools.error("Error: " + e); } } // test } // ObjectReader Sample output 134 public class Demo { public static void main(String[] args) { if (args.length != 1) Tools.error("usage: java Demo File"); System.out.println("calling ObjectWriter.test()"); ObjectWriter.test(args[0]); System.out.println("\n\ncalling ObjectReader.test()"); ObjectReader.test(args[0]); System.out.println("done"); } } Here's the output produced: C:\Pearce\Java\Projects\streams>java Demo book1 calling ObjectWriter.test() Name = George Jetson 123 Sesame St. New York, N.Y. (212) 653-9875 ++++++++++++++++++ Name = Spock 1600 Pennsylvania Ave. Washington, D.C. (105) 953-5555 ++++++++++++++++++ Name = Judy Jetson 123 Sesame St. New York, N.Y. (212) 653-9875 ++++++++++++++++++ george.getAddress() == judy.getAddress() = true calling ObjectReader.test() Name = George Jetson 123 Sesame St. New York, N.Y. (212) 653-9875 ++++++++++++++++++ Name = Spock 1600 Pennsylvania Ave. Washington, D.C. (105) 953-5555 ++++++++++++++++++ Name = Judy Jetson 123 Sesame St. New York, N.Y. (212) 653-9875 ++++++++++++++++++ george.getAddress() == judy.getAddress() = true done Are Java Programs Clairvoyant? What if the object reader ran in a separate program from the object writer? Everything would work as above provided the class loader could find AddressBook.class, Person.class and 135 Address.class. This would happen if these files were in the current directory or in a directory listed in the CLASSPATH. Otherwise, a "class not found" exception would be thrown. Databases Java Database Connectivity (JDBC) is a package consisting of eight interfaces: java.sql.Driver java.sql.Connection java.sql.Statement java.sql.PreparedStatement java.sql.CallableStatement java.sql.ResultSet java.sql.ResultSetMetaData java.sql.DatabaseMetaData java.sql.DriverManager. - maintains a list of all available drivers driver knows how to create a connection to a particular database. JDBC drivers for most well known databases are available for a price. If a database has an ODBC driver, then one can use a JDBC-ODBC bridge, which is provided free on Windows NT, but must be purchased for Sun platforms. Consult the javasoft web site for a list of drivers and bridges. Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); // load driver manager String url = "jdbc:odbc:ODBC_Addresses"; String user = "smith"; String pswd = "foobar"; Connection myConn = DriverManager.getConnection(url, user, pswd); class connection - stores information about database session and provides statement (or prepared or callable statement) object to the application: 136 Statement myStmt = myConn.createStatement(); statement - template for an SQL statement (UPDATE, DELETE, INSERT, or SELECT); provides executeQuery() and executeUpdate() methods. The executeQuery() method returns a result set representing a list of rows: String sql = "SELECT columns FROM table WHERE column = xxx; ResultSet rs = stmt.executeQuery(sql); Connection import java.sql.*; class Database { private Connection myConn; private Statement myStmt; public Database(String dm, String url, String user, String pswd) { try { Class.forName(dm); // loads a DriverManager class myConn = DriverManager.getConnection(url, user, pswd); myStmt = myConn.getStatement(); } //try catch(SQLException sqlexp) { Tools.error("connection failed: " + sqlexp); } catch (ClassNotFoundException cnfe) { Tools.error("Failed to load driver; " + cnfe); } } // etc. } // Database Statements class Database { private Connection myConn; private Statement myStmt; public Database(String dm, String url, String user, String pswd) { ... } public String execute(String sql) { try { if (isSelect(sql)) { ResultSet rs = myStmt.executeQuery(sql); return formatResultSet(rs); } else if (isUpdate(sql) || isInsert(sql)) { int rs = myStmt.executeUpdate(sql); return "# of rows affected = " + rs; } else 137 Tools.error("unrecognized SQL: " + sql); } // try catch (SQLException sqle) { Tools.error("Dbase access failure; " + sqle); } // catch catch(IOException ioe) { Tools.error("Request read failure; " + ioe); } // catch } // execute // etc. private boolean isSelect(String stmt) { return (stmt.substring(0, 6).equalsIgnoreCase("SELECT")); } private boolean isUpdate(String stmt) { return stmt.substring(0, 6).equalsIgnoreCase("UPDATE"); } private boolean isInsert(String stmt) { return stmt.substring(0, 6).equals("INSERT"); } } // Database Result Set result set - list of rows extracted from a table. Each row is a list of columns. Each column is a String. Associated metadata object - tells us the number of columns and the name and type of each column. class Database { private Connection myConn; private Statement myStmt; public Database(String dm, String url, String user, String pswd) { ... } public String execute(String sql) { ... } private String formatResultSet(ResultSet rs) throws SQLException { ResultSetMetaData rsmd = rs.getMetaData(); int cc = rsmd.getColumnCount(); // = # of columns String result = " table: "; // iterate through each row while(rs.next()) { result = result + " row: "; // iterate through each column for(int i = 1; i <= cc; i++) { String colName = " name: " + rsmd.getColumnName(i); String colType = " type: " + rsmd.getColumnType(i); 138 String colVal = " value: " + rs.getString(i); result = result + colName + colType + colVal; } // for } // while return result; } // formatResultSet // etc. } // Database Example class DbaseClient { public static void main(String[] args) { String sql = "SELECT FNAME, LNAME, ADDRESS, CITY, STATE, ZIP, PHONE " + "FROM People " + "WHERE PHONE = " + "'" + args[0] + "'\n"; Database db = new Database("sun.jdbc.odbc.JdbcOdbcDriver", "jdbc:odbc:ODBC_Addresses", "smith", // owner "foobar"); // password Tools.cout.println(db.execute(sql)); } JDBC can also be used to create databases. 139 Programming and Design Principles Producing a large software system is the most challenging technical activity that the human race attempts to carry out. Darrel Ince Software is changed far more frequently than computers, bridges, and skyscrapers. Challenges in modern programming Coupling and variabilities Coupling - if there is a high probability that changing one piece will require changing another. Software is highly susceptible to coupling. Studies indicate that coupling correlates with the cost of maintenance. Reduce coupling by distributing knowledge on a "need-to-know" basis. That is, module A doesn't know about module B unless B has an immediate role in A's job. This is the main reason to avoid sprawling global namespaces. Furthermore, what A knows about B is limited to the role that B has to A. This is the motivation behind encapsulation and multiple interfaces.. Places of likely change are called variabilities. Designer should ensure that encapsulation and variabilities are preserved through changes. Encapsulation says "these modules should not interact", while a variability says "this module should be replaceable.". Patterns are designed to express policies, and so form a valuable addition to the designer's vocabulary. What is a good design? Loosely-coupled components with clearly demarcated variabilities. Meet the functional requirements within the allotted resources. Understandable - basing the design on a common pattern. Amenable to change. Documenting policy Use patterns as documentation, to ensure that components stay decoupled and variabilities remain flexible. 140 Object Oriented Analysis and Design Analysis - document the behavior, requirements constraints Develop use-case scenarios to ensure your understanding of the desired system and features Design - KISS (Keep It Simple Stupid); your designs should be as simple as possible Develop some artifacts to help understand the nature of your system: Class/package diagrams with associations/roles to help understand the relationships of the classes/packages The Open/Closed Principle (Bertrand Meyer) "Modules should be open for extension but closed for modification" This means: Use abstraction (interfaces) to minimize dependence on implementation details! Consider the following to help understand this principle: Class Shape { public static final Circle = 0; public static final Square = 1; private int myShape; void drawShapes(ShapeArray shapes[]) { for (int i = 0; i < shapes.size(); i++) { switch (shapes[i].getShape()) { case Circle: drawCircle(shapes[i]); break; case Square: drawSquare(shapes[i]); break; … default: "*****" } } … } The above code is open - if we want to add another shape we must change the switch (and perhaps many other switches). It's quite easy to miss one of the switches. Also note that this code will be modified - it's open; Contrast this with the code given below. interface Shape { public draw(); } class Square implements Shape { public draw() { ... } 141 } class Circle implements Shape { public draw() { ... } } class OtherClass { void drawShapes(ShapeArray shapes[]) { for (int i = 0; i < shapes.size(); i++) { shapes[i].draw(); } } } OtherClass is closed; it will not change if we add more shapes - there are no switch statements! Object Oriented Analysis: document the behavior, requirements and constraints of the system to be developed - the important artifact to use is Use Case Scenarios which describe how external actors (users) interact with the system. These scenarios will raise many issues that need answering prior to attempting any design efforts. Object Oriented Design: Aspects of the design process are described below. KISS - Keep It Simple Stupid 1. Determine Design Patterns (described later in this reader) 2. Packages: The relationships between packages can be depicted as a diagram where each package is drawn as a block and directed edges describe dependencies between packages. This diagram should be a Directed Acyclic Graph. It should not be too confusing to understand when considering edge crossovers. A simple diagram with few edges implies there is not too much strong coupling between packages. If the packages are separated into layers (preferable for large systems) then each layer should demonstrate cohesion and there should be loose coupling between layers (perhaps higher layers should depend on lower layers but not vice versa) 3. Inheritance Be careful when making decisions of when to inherit vs compose (containment). Strive to use the is-a relationship for inheritance. If classes relate via the has-a relationship then be sure to use composition. If the hierarchy becomes complex then attempt to refactor classes by using composition (refer to the Decorator design pattern). 4. Interfaces 142 These are used to specify contracts for services between the client desiring to use services and the server who will provide the services. The use of interfaces helps ensure the provision of closed modules (see Meyer's quote above). 5. Classes Minimize coupling and maximize cohesion! Be careful when constructing instances sometimes you might want to distinguish between construction and initialization (e.g. Applets separate these two issues) 6. Methods Keep method size small to increase understanding and minimize coupling. Some thought should go into assuring correctness. These thoughts can be implemented by providing preand post-conditions along with assertions for constraint checking. Along with these pieces of code you might throw exceptions if appropriate. Remember to design by contract 7. Variables Almost all variables should be private (use getters/setters, if necessary). Garbage collection issues should be dealt with (e.g. if you no longer need to refer to an object then be sure to reset the variable to null). Be careful with regards to finalizers. Finally, when serializing objects consider whether the variable should be transient - you might serialize private information or information only relevant to the current state of an object. 8. Robustness The importance of this final category for consideration cannot be minimized. Again, be sure to provide constraint checking within the code via pre- and post-condition checking. Exceptions should be used with many of these checks. Therefore, carefully consider the provision of appropriate exception hierarchies. Note that the use of exceptions will force consumers of your code to write robust code, too. 143 Event Notification (Appendix 3) Overview Broadcasters and receivers in many forms are common in event-driven programs. A broadcaster is an object that randomly broadcasts messages to unknown numbers and types of receivers. Receivers are objects that are either always on-perpetually listening or polling for broadcasts-or usually off-expecting to be awakened when a broadcast occurs. Because broadcast messages are un-addressed and occur randomly, they are often called events. In this case receivers are called event handlers, broadcasters are called event sources, and broadcasting is called event firing. To avoid polling, which consumes too much time and bandwidth, many platforms provide several types of event notification mechanisms. These mechanisms can be viewed as instances of various event notification design patterns e.g., Publisher-Subscriber pattern Design Patterns A design pattern is a recurring design problem together with a generic, reusable solution. A pattern catalog collects, classifies, and names design patterns mined from well designed structures. Traditional Neighborhood Development [KUN] Other Names TND, New Urbanism, Neo-traditional Planning, Low-density Urbanism, Transit Oriented Development (TOD), civic art. Problem Post World War II zoning laws and property taxation policies, the rise of nation-wide chain stores, and increased reliance on automobiles has led to suburban sprawl, urban decay, and the death of traditional neighborhoods and downtown districts. These problems contribute to social and environmental problems such as crime, racism, poverty, pollution, and alienation. Solution 1. Neighborhoods should be the basic unit of city planning. Networks of neighborhoods connected and bounded by corridors (rivers, parks, boulevards) form towns and cities. 2. A neighborhood has a well defined boundary, which is no more than a five minute walk from a focused center. Neighborhoods are mixed-use and mixed-income. 3. The primary function of the street is to define a public space. Buildings must be disciplined to honor and embellish this public space. Cars are not given precedence over people. etc. 144 Example: Monitoring Devices Assume we are in the early design phase of a project to provide software that will control and monitor a nuclear reactor. Here's a description of the application domain: The most important attribute of a nuclear reactor is the temperature of its core. Reactors provide computerized controls for decrementing and incrementing the temperature. These controls are manipulated from a remote console, which is manned by our most competent employee, Homer Simpson. Sensors are scattered throughout the power plant. These include thermometers, and various types of alarms and thermostats that go off when the reactor's temperature rises above the critical level: 1500 degrees. We need to have the ability to add more and new types of sensors and consoles to the system without modifying the reactor control code. Here's our initial model of the domain: class Reactor { private double temperature = 0; private final double critical = 1500; public boolean tooHot() { return critical <= temperature; } public double getTemperature() { return temperature; } public void inc(double amt) { temperature += amt; } public void dec(double amt) { temperature -= amt; } } // console and sensors: class Console { Reactor myReactor; ... } class Alarm { Reactor myReactor; ... } class Thermometer { Reactor myReactor; .. } class Thermostat { Reactor myReactor; ... } // etc. Use polling: 145 double lastTemp = myReactor.getTemperature(); while(true) { double temp = myReactor.getTemperature(); if (lastTemp != temp) { /* do something */ } lastTemp = temp; } Problem: Unacceptable level of network traffic (changes in the temperature of the reactor's core will probably be relatively infrequent compared to the polling frequency).. Solution: Add a sequence of statements to Reactor.inc() and Reactor.dec() that call specific methods of each sensor: void inc(double amt) { temperature += amt; // there are different types of thermometers: thermometer1.setTemp(temperature); thermometer2.adjust(temperature); thermometer3.increment(amt); // etc. if (critical <= temperature) // yikes! { // there are different types of alarms: alarm1.ring(); alarm2.flash(); alarm3.buzz(); // etc. } } - Each time a new sensor is added to the system, we will have to add statements to inc() and dec(). This is an example of tight coupling. It creates dependencies between the reactor and the sensors, and it is exactly what the last sentence in the domain description asks us not to do. Why? The program that actually controls the reactor is probably complicated, very specialized, and, more importantly, it must never fail! Each time we allow people to modify this program when a new type of sensor is added to the system, we open the door for introducing bugs (and melt downs). The Publisher-Subscriber Pattern It's time to reach for a pattern catalog. The Publisher-Subscriber pattern is listed in several: Publisher-Subscriber [Go4], [POSA], [LAR], [ROG] Other Names Publishers are also called senders, observables, subjects, broadcasters, and notifiers. Subscribers are also called receivers, listeners, observers and callbacks. 146 Problem Various monitors need to be notified when a device changes state, but the number and types of monitors can vary dynamically. We want to avoid polling, and we don't want to make assumptions in the device code about the numbers and types of monitors. Solution The device should maintain a list of references to registered monitors. Monitors can be different, but each must implement the same interface, which includes an update() function that the device will call when it changes state. The list of monitor references can be managed by a reusable Publisher base class. An abstract Subscriber base class defines the interface monitors must implement. Static Structure Note: update() is a pure virtual function that will be implemented differently in different derived classes. Alternatively, we could have stereotyped Subscriber as an interface. Dynamic Structure - Sequence diagram 147 The Publisher-Subscriber Pattern in Java Publisher and subscriber classes are provided in the Java utility package - Observable and Observer. Observers are subscribers: interface Observer { // from java.util public void update(Observable o, Object arg); } Observables are publishers: public class Observable { // from java.util private boolean changed = false; // = state changed? public void addObserver(Observer o) { ... } public void deleteObserver(Observer o) { ... } public void notifyObservers() { ... } // pull variant public void notifyObservers(Object arg) { ... } // push variant protected void setChanged() { changed = true; } protected void clearChanged() { changed = false; } public boolean hasChanged() { return changed; } // etc. } Example: Monitoring Devices (continued) 148 "inc" button - increments the temperature of the reactor by 500 degrees "dec" button - decrements the reactor's temperature by 50 degrees. thermometer displays the reactor's temperature - when the reactor's temperature exceeds 1500 degrees, several beeps can be heard and the message: Warning: reactor too hot! appears several times in the DOS/Unix console window. Reactor Revised Reactor class class Reactor extends Observable { private double temperature = 0; private final double critical = 1500; public boolean tooHot() { return critical <= temperature; } public double getTemperature() { return temperature; } public void inc(double amt) { temperature += amt; setChanged(); notifyObservers(); clearChanged(); } public void dec(double amt) { temperature -= amt; setChanged(); notifyObservers(); clearChanged(); } } Alarms class BeepingAlarm implements Observer { public void update(Observable o, Object arg) { if (o instanceof Reactor) { Reactor r = (Reactor) o; if (r.tooHot()) { System.out.println('\u0007'); // beep } } } } class PrintingAlarm implements Observer { public void update(Observable o, Object arg) { if (o instanceof Reactor) { Reactor r = (Reactor) o; if (r.tooHot()) { System.out.println("Warning: reactor too hot!"); } 149 } } } Thermometers class Thermometer extends JLabel implements Observer { private Reactor myReactor; public Thermometer(Reactor r) { super("" + r.getTemperature()); myReactor = r; myReactor.addObserver(this); } public void update(Observable o, Object arg) { setText("" + myReactor.getTemperature()); } } Reactor Console class ReactorConsole extends MainJFrame { private Reactor myReactor; // button listeners: class IncAction implements ActionListener { ... } class DecAction implements ActionListener { ... } // constructor: public ReactorConsole() { ... } // fire it up: public static void main(String[] args) { ReactorConsole console = new ReactorConsole(); console.setVisible(true); } } public ReactorConsole() { setTitle("Reactor Console"); myReactor = new Reactor(); BeepingAlarm ba1 = new BeepingAlarm(); BeepingAlarm ba2 = new BeepingAlarm(); PrintingAlarm pa1 = new PrintingAlarm(); PrintingAlarm pa2 = new PrintingAlarm(); myReactor.addObserver(ba1); myReactor.addObserver(ba2); myReactor.addObserver(pa1); myReactor.addObserver(pa2); // make & add inc button panel: JPanel incPanel = new JPanel(); JButton incButton = new JButton("inc"); incButton.addActionListener(new IncAction()); incPanel.add(incButton); // make & add dec button panel: JPanel decPanel = new JPanel(); JButton decButton = new JButton("dec"); decButton.addActionListener(new DecAction()); decPanel.add(decButton); // make & add thermometer panel: 150 JPanel thermPanel = new JPanel(); Thermometer thermometer = new Thermometer(myReactor); thermPanel.add(thermometer); // add panels: Container contentPane = getContentPane(); contentPane.add(incPanel, "West"); contentPane.add(thermPanel, "Center"); contentPane.add(decPanel, "East"); } Java Foundation Classes (JFC) Most GUI components fire events when manipulated by users. Here are a few JFC event classes: 151 For example, the reactor console indirectly extends JFrame and contains two JButtons and a JLabel: Event Delegation When the user presses either the "inc" or "dec" button, an action event is fired and the corresponding reactor method is invoked. , the event is routed to registered listeners which realize one or more listener interfaces: 152 incButton.addActionListener(new IncAction()); decButton.addActionListener(new DecAction()); IncAction and DecAction are declared as inner classes of the ReactorConsole class Commonly done because these classes are seldom reusable outside of a particular GUI, but also because inner class instances have access to the private variables of the outer class instance that creates them. Thus, our listeners can access the myReactor reference of the reactor console that creates them: class IncAction implements ActionListener { public void actionPerformed(ActionEvent a) { myReactor.inc(500); } } class DecAction implements ActionListener { public void actionPerformed(ActionEvent a) { myReactor.dec(50); } } Components are publishers. They publish events. Listeners are subscribers. They must implement methods prescribed by appropriate listener interfaces and they must register with the components they listen to. Constraint Networks A constraint network represents a mathematical relationship between several variables, and is able to compute the value of any one of these variables given the values of all the others. There are two types of nodes in a constraint network: cells and constraints. Cells represent variables (read-only cells represent constants) and constraints represent primitive mathematical relationships such as z = x + y and z = x * y. The neighbors of a constraint are the cells that it constrains. The neighbors of a cell are the constraints that constrain it. 153 A number can be saved to or erased from a cell. In either case, the cell's neighboring constraints will be notified. Upon notification that a neighboring cell has been erased, a constraint erases all of its neighbors. Upon notification that a number has been stored in a neighboring cell, a constraint attempts to compute new values for its remaining neighbors. In either case, cells updated by a constraint notify the other constraints they are connected to. In this way cell updates cascade through the network. For example, assume x, y, and z are cells connected to an addition constraint. If a number is stored in x, and if y already holds a number, then the adder constraint stores x + y in z. z = x + y; Consider the relationship: z = 3x + 4y We can represent this as a constraint network consisting of three constraints and seven cells: In this network the cells labeled 3 and 4 are constant or read-only cells. The cells labeled 3x and 4y hold 3 * x and 4 * y. The nodes labeled + and * are constraints. Assume x = 2 and y is undefined. In this case 3x = 6 and 4y is undefined. Assume 26 is stored in z. The adder constraint stores 26 - 6 = 20 in 4y. At this point the right multiplier constraint stores 20/4 = 5 in y. ConNet Our constraint network is called ConNet:: -> help help (displays this message) quit (terminates session) set NAME VALUE (update a variable cell) addVar NAME (add a variable cell) 154 addConst NAME VALUE (add a constant cell) display NAME (display a cell) undisplay NAME (undisplay a cell) addAdder ARG1 ARG2 RESULT (add an adder constraint) addMult ARG1 ARG2 RESULT (add a multiplier constraint) clear (get rid of all cells & constraints) forgetAll (forget all cell values) forget NAME (forget a cell value) Let's program ConNet to solve the equation: z = 3x + 4y We begin by creating three cells representing the variables x, y, and z: -> addVar x done -> addVar y done -> addVar z done Next, we create two cells that hold the constants 3 and 4, as well as two cells to hold the intermediate values of 3x and 4y: -> addConst 3 3 done -> addConst 4 4 done -> addVar 3x done -> addVar 4y done -> addMult y 4 4y done -> addMult x 3 3x done -> addAdder 3x 4y z done -> display x done -> display y done -> display z done -- displays x when it is updated Ex: -> set x 3.14 x = 3.14 done -> set z 9 z = 9 y = -0.105 done Next, let's compute z assuming x = 5 and y = 3. In this case: 155 z = 3x + 4y = 3 * 5 + 4 * 3 = 27 -> forgetAll forgetting x forgetting y forgetting z done -> set x 5 x = 5 done -> set y 3 y = 3 z = 27 done Design Each constraint maintains a collection of links to its neighboring cells and controls the values those cells may hold. However, a cell has no direct knowledge of its neighboring constraints. Instead, the Publisher-Subscriber pattern is used. Cells are publishers and neighboring constraints are subscribers. The user interface is provided through a command line console linked to the constraint network: For example, a constraint network representing the equation: z = x + y would look like this in memory: 156 The following sequence diagram shows the constraint network setting the value of x then the value of y: After the value of x is set, the adder is updated, but the adder does nothing because neither y nor z holds a value. After y is set, the adder is again updated. Now two of its three neighbors holds values, so the adder sets the value of z to x + y. Programming Note Programming Note 3.1 package.util; import java.awt.*; import java.awt.event.*; import javax.swing.*; public class MainJFrame extends JFrame { protected int screenHeight; 157 protected int screenWidth; protected String title = "Main Window"; private class Terminator extends WindowAdapter { public void WindowClosing(WindowEvent e) { System.exit(0); } } public MainJFrame() { addWindowListener(new Terminator()); setTitle(title); Toolkit tk = Toolkit.getDefaultToolkit(); Dimension d = tk.getScreenSize(); screenHeight = d.height; screenWidth = d.width; setSize(screenWidth/2, screenHeight/2); setLocation(screenWidth/4, screenHeight/4); } } Event Managers The Publisher-Subscriber pattern is an instance of a general class of architectures called Event Notification Systems Event managers are used when multiple devices need to be monitored. Event Manager [LAR] Other Names Event managers are closely related to view managers, application coordinators, and message dispatchers. Problem Various listeners need to be notified when a certain type of event occurs. These events are "fired" by various event sources. We want to avoid polling or tight coupling between listeners and sources. Solution The event manager maintains a table of associations between event types and lists of registered listeners interested in occurrences of the corresponding event type. Usually there is just one event manager. When an event of type t occurs, the event manager passes the event to the handleEvent() function of each listener registered for type t events. Each event source maintains a reference to the event manager, and calls its notify() function when it fires an event. Static Structure 158 159 Some Important Design Patterns Model-View-Controller Model - main object/data structure View - visual presentation of the model Controller - (visual) components (e.g. buttons, text fields) that control interactions with user -event occurs and controller dispatches event to view and/or model. Swing components, Excel spreadsheets Adaptor Pattern Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces. Distributed object databases like CORBA use Adaptors to integrate native language objects into the database. Adaptors are also used to make non-objects, like text files, look like objects Chain of Responsibility Pattern Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it. (MFC) The X Window System, MacApp, Windows, and ET++ use the Chain of Responsibility pattern to handle input, redraw, and modification events. It can also be used for contextsensitive help events. A window which isn't interested in the event passes it up to its enclosing window. Decorator Pattern Attach (wrap) additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extended functionality. X Window and ET++ use Decorators to add a title bar, border, and scroll bars to a window (where the name "decorator" comes from). Java’s streams use decorators extensively. Observer Pattern Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. 160 Proxy Pattern Provide a surrogate or placeholder for another object which cannot be accessed by normal means. Proxies are commonly used to make objects appear local in distributed systems. For example, the client stub involved in CORBA/RMI is a Proxy for the server function. Strategy Pattern Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithms vary independently from clients that use it. A Decorator lets you change the skin of an object; a Strategy lets you change the guts. Java uses the Strategy pattern to encapsulate layout algorithms for text viewers. Streams Pattern (Pipes and Filters, Dataflow) Process a stream of information by feeding it through a network of independent and reusable processing units. The Unix shell uses eager parallel processes to evaluate a pipeline command. The interface between processes is exceedingly simple: characters go from stdin to stdout. Processes are so independent that they can be written in any programming language. Unix manages the buffers and suspends processes automatically, making the buffering transparent. So it is equally fair to call this a push or a pull design. References Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, Reading, MA, 1994. "Software Patterns" in Communications of the ACM, October, 1996. 161 Refactoring to Patterns (from Refactoring to Patterns by Joshua Kertevsky, Addison-Wesley 2005) 1. Compose Method Refactoring Transform the logic into a small number of intention-revealing steps at the same level of detail. Example /** * @author Dr. Barry Levine * * Compose Method Refactoring Example */ public class MyList { private final static int initSize = 10; private Object elements[] = new Object[initSize]; private int currentSize = 0, maxSize = initSize, incrAmount = 10; private boolean readOnly = false; public static void main(String[] args) { new MyList().addElement("First"); } public MyList() {} public Object deleteElement() { return "bogus";} public MyList addElement(Object newElement) { if (!readOnly) { if (currentSize == maxSize) { Object tempElmts [] = new Object[maxSize + incrAmount]; for (int i = 0; i< maxSize; i++) { tempElmts[i] = elements[i]; } elements = tempElmts; maxSize += incrAmount; } elements[currentSize++] = newElement; } return this; } } 162 1. Restructure if public MyList addElement(Object newElement) { if (readOnly) { return this; } if (currentSize == maxSize) { Object tempElmts[] = new Object[maxSize + incrAmount]; for (int i = 0; i < maxSize; i++) { tempElmts[i] = elements[i]; } elements = tempElmts; maxSize += incrAmount; } elements[currentSize++] = newElement; return this; } 2. Extract Methods – highlight code to extract to method and click on extract method menu item. public MyList addElement(Object newElement) { if (readOnly) { return this; } if (currentSize == maxSize) { grow(); } addAnElement(newElement); return this; } /** * @param newElement */ private void addAnElement(Object newElement) { elements[currentSize++] = newElement; } /** * */ private void grow() { 163 Object tempElmts[] = new Object[maxSize + incrAmount]; for (int i = 0; i < maxSize; i++) { tempElmts[i] = elements[i]; } elements = tempElmts; maxSize += incrAmount; } Benefits and Liabilities Efficiently communicates what a method does and how it does what it does Simplifies a method by breaking it up into well-named chunks of behavior at the same level of detail Can lead to an overabundance of small methods Can make debugging difficult because logic is spread out across many small methods 164 2. Replace Conditional Logic with Strategy Conditional logic in a method controls which of several variants of a calculation are executed. Create a Strategy for each variant and make the method delegate the calculation to a Strategy instance. Example import java.util.*; public class Checkout { public final static String CashPayment = "Cash"; public final static String CreditPayment = "Credit"; public final static String CheckPayment = "Check"; private final static int badCreditPurchase = 4; // 40 percent of credit purchases will fail private final static int badCheckPurchase = 2; // 20 percent of credit purchases will fail private String typeOfPurchase; private double amountOfPurchase; public Payment(double amountOfPurchase, String typeOfPurchase) { this.amountOfPurchase = amountOfPurchase; this.typeOfPurchase = typeOfPurchase; } // make payment and return change public double makePayment(double amountOfPayment) throws Exception { // Note that the logic for each payment type can get much more // complicated if (typeOfPurchase.equals(CashPayment)) {; return computeChange(amountOfPayment); } else if (typeOfPurchase.equals(CreditPayment)) { int randInt = (new Random()).nextInt(10); if (randInt < badCreditPurchase) { throw new Exception("bad credit report - payment failed"); } return 0.0; // no change with credit purchases } // check purchase int randInt = (new Random()).nextInt(10); if (randInt < badCheckPurchase) { throw new Exception("bad check - payment failed"); 165 } return 0.0; // no change with check purchases } double computeChange(double pay) { return 0.0; } } 1. Create a concrete class for each strategy. Move the method that implements the strategy to each subclass, as appropriate. import java.util.*; public abstract class PaymentStrategy { protected double amountOfPurchase; public PaymentStrategy(double amountOfPurchase) { this.amountOfPurchase = amountOfPurchase; }; // make payment and return change public abstract double makePayment(double amountOfPayment) throws Exception; double computeChange(double pay) { return 0.0; } } class CashPaymentStrategy extends PaymentStrategy { public CashPaymentStrategy(double amountOfPurchase) { super(amountOfPurchase); } public double makePayment(double amountOfPayment) throws Exception { return computeChange(amountOfPayment); } } 166 class CheckPaymentStrategy extends PaymentStrategy { private final static int badCheckPurchase = 2; // 20 percent of credit // purchases will fail public CheckPaymentStrategy(double amountOfPurchase) { super(amountOfPurchase); } public double makePayment(double amountOfPayment) throws Exception { int randInt = (new Random()).nextInt(10); if (randInt < badCheckPurchase) { throw new Exception("bad check - payment failed"); } return 0.0; // no change with check purchases } } class CreditPaymentStrategy extends PaymentStrategy { private final static int badCreditPurchase = 4; // 40 percent of credit // purchases will fail public CreditPaymentStrategy (double amountOfPurchase) { super(amountOfPurchase); } public double makePayment(double amountOfPayment) throws Exception { int randInt = (new Random()).nextInt(10); if (randInt < badCreditPurchase) { throw new Exception("bad credit report - payment failed"); } return 0.0; // no change with credit purchases } } public class Checkout { private PaymentStrategy paymentStrategy; public CheckoutCash(double amountOfPurchase) { paymentStrategy = new CashPaymentStrategy(amountOfPurchase); } public CheckoutCheck(double amountOfPurchase) { 167 paymentStrategy = new CheckPaymentStrategy(amountOfPurchase); } public CheckoutCredit(double amountOfPurchase) { paymentStrategy = new CreditPaymentStrategy(amountOfPurchase); } // make payment and return change public double makePayment(double amountOfPayment) throws Exception { return paymentStrategy.makePayment(amountOfPayment); } } Benefits and Liabilities Clarifies algorithms by decreasing or removing conditional logic Simplifies a class by moving variations on an algorithm to a hierarchy Enables one algorithm to be swapped for another at runtime Complicates how algorithms obtain or receive data from their context class 168 3. Replace Conditional Dispatcher with Command Create a Command for each action. Store the Commands in a collection and replace the conditional logic with code to fetch and execute Commands. Example package before; import java.util.*; public class Program { private Vector program = new Vector(); private java.util.Dictionary labeltable = new java.util.Hashtable();; public void addCode(String code) { program.addElement(code); } public String getCode(int i) { return (String)program.elementAt(i); } } package before; import java.util.*; /** * The VirtualMachine is the virtual hardware that will execute * the bytecodes; its operation is emulated by the vm object created; * It contains a runtime stack, program counter, function return * address stack, the bytecode program and an indication whether it's * running */ public class VirtualMachine { private Stack runStack = new Stack(); private int pc; // program counter private boolean isRunning; // is the vm running? private Program program; // the bytecode program /** * Construct a new virtual machine 169 * @param program - the bytecode program to execute */ public VirtualMachine(Program program) { this.program = program; } /** * begin execution of the vm; init the pc to 0; create new stacks; * set the machine running and include the main execution loop */ public void executeProgram() { pc = 0; isRunning = true; while (isRunning) { String code = program.getCode(pc); if (code.equals("pop")) { runStack.pop(); } else if (code.equals("Halt")) { isRunning = false; } else if (code.equals("add")) { int op1 = pop().intValue(); int op2 = pop().intValue(); runStack.push(new Integer(op1+op2)); } // and so on for the other operators pc++; } } public Integer pop() {return (Integer)runStack.pop();} } 1. Create Bytecode abstract class to describe behavior of commands. Create concrete classes for each Bytecode; store Bytecodes in Program. Include an execute method in each concrete Bytecode class. /** * ByteCode class is the abstract class describing the behaviour for * all bytecodes; used for typing information when processing bytecodes */ public abstract class ByteCode { public abstract void execute(VirtualMachine vm); } 170 public class PopCode extends ByteCode { private int value; public PopCode() {} /** * POP <i>n</i> <br> * Pop the top <i>n</i> items from the runtime stack */ public void execute(VirtualMachine vm) { for (int i = 0; i < value; i++) { vm.pop(); } } } 2. Now, Program stores Bytecodes rather than Strings import java.util.*; public class Program { private Vector program = new Vector(); private java.util.Dictionary labeltable = new java.util.Hashtable();; public void addCode(ByteCode code) { program.addElement(code); } public ByteCode getCode(int i) { return (ByteCode)program.elementAt(i); } } 3. Sequence through the collection of objects in a generic fashion. import java.util.*; /** * The VirtualMachine is the virtual hardware that will execute * the bytecodes; its operation is emulated by the vm object created; * It contains a runtime stack, program counter, function return * address stack, the bytecode program and an indication whether it's 171 * running */ public class VirtualMachine { private Stack runStack = new Stack(); private int pc; // program counter private boolean isRunning; // is the vm running? private Program program; // the bytecode program /** * Construct a new virtual machine * @param program - the bytecode program to execute */ public VirtualMachine(Program program) { this.program = program; } /** * begin execution of the vm; init the pc to 0; create new stacks; * set the machine running and include the main execution loop */ public void executeProgram() { pc = 0; isRunning = true; while (isRunning) { ByteCode code = program.getCode(pc); code.execute(this); pc++; } } public Integer pop() {return (Integer)runStack.pop();} } Note that the Bytecode variabilities have been factored into a hierarchy of Bytecodes to encapsulate the Bytecode abstraction. Benefits and Liabilities Provides a simple mechanism for executing diverse behavior in a uniform way Enables runtime changes regarding which requests are handled and how Requires trivial code to implement 172 Complicates a design when a conditional dispatcher is sufficient Note that in the Strategy and Command refactorings we made improvements based on recognizing and making explicit the variabilities found in the problems. 4. Replace Type Code with Class Constrain the assignments and equality comparisons by making the type of the field a class. Example Recall the example used in Replace Conditional Logic with Strategy import java.util.*; public class Checkout { public final static String CashPayment = "Cash"; public final static String CreditPayment = "Credit"; public final static String CheckPayment = "Check"; private final static int badCreditPurchase = 4; private final static int badCheckPurchase = 2; private String typeOfPurchase; private double amountOfPurchase; public Checkout(double amountOfPurchase, String typeOfPurchase) { this.amountOfPurchase = amountOfPurchase; this.typeOfPurchase = typeOfPurchase; } // make payment and return change public double makePayment(double amountOfPayment) throws Exception { // Note that the logic for each payment type can get much more // complicated if (typeOfPurchase.equals(CashPayment)) {; return computeChange(amountOfPayment); } else if (typeOfPurchase.equals(CreditPayment)) { int randInt = (new Random()).nextInt(10); if (randInt < badCreditPurchase) { throw new Exception("bad credit report - payment failed"); } return 0.0; // no change with credit purchases } 173 // check purchase int randInt = (new Random()).nextInt(10); if (randInt < badCheckPurchase) { throw new Exception("bad check - payment failed"); } return 0.0; // no change with check purchases } double computeChange(double pay) { return 0.0; } } What if we used new Checkout(20.03,” Cedit”) rather than new Checkout(20.03,” Credit”) This logic error might be difficult to determine. We might also consider using an enum type which is available in JDK 1.5. Benefits and Liabilities Provides better protection from invalid assignments and comparisons Requires more code than using unsafe type does 174 Frameworks. Toolkits, and Polymorphism (Appendix 4) Overview Polymorphism allows programmers to declare partially complete classes and functions. The idea being to complete the declaration with a subclass or a template instantiation when more application-specific information is available. Of course this is also the idea behind frameworks, toolkits, and many design patterns. In each case application-independent logic is captured by one part of the program, while application-dependent logic is concentrated in another. Frameworks A framework is a library of collaborating base classes that capture the logic, architecture, and interfaces common to a family of similar applications. Frameworks are customized into specific applications by deriving classes from framework classes. Consider MFC - provides generic graphical user interfaces with windows, menus, toolbars, dialog boxes, and other common user interface components. JFC provides generic graphical user interfaces with windows, menus, toolbars, dialog boxes, and other common user interface components. 175 Example: Application Frameworks Example: Client-Server Frameworks Data is maintained by the server Presented and manipulated by clients. Often machine or network boundaries separate clients and servers. The World Wide Web (WWW) is a typical client server application. Web servers maintain collections of web pages that are delivered upon request to web clients (i.e., web browsers). Java – applets server as clients in this framework 176 Object-Oriented Concepts Polymorphism A type system for a language L is a set of primitive types together with a set of rules for constructing, comparing, and naming types, as well as rules for binding types to the expressions and values of L. For example, the primitive types of Java include int, float, char, and boolean. Arrays and classes are examples of constructed types. Instances of a monomorphic or uniform type all have the same representation; e.g., Real type using IEEE floating point standard representation, Polymorphic or multi-form type can have a variety of representations; e.g., Java classes because instances of an extension can be treated as instances of the super class Ex: class Shape { public void draw(Graphics g) { /* no op */ } // etc. } class Circle extends Shape { public void draw(Graphics g) { g.drawOval(...); } // etc. } 177 Reference to a Shape can actually refer to any instance of a Shape extension Shape[] shapes = new Shape[3]; shapes[0] = new Circle(); shapes[1] = new Rectangle(); shapes[2] = new Triangle(); The following statement draws a circle, rectangle, and triangle in the graphical context g: Graphics g = myShapeWindow.getGraphics(); for(int i = 0; i < 3; i++) shape[i].draw(g); Abstraction Interfaces An interface is a collection of operator specifications. An operator specification may include a name, return type, parameter list, exception list, pre-conditions, and post-conditions. A class implements an interface if it implements the specified operators. A software component is an object that is known to its clients only through the interfaces it implements. Often, the client of a component is called a container. Interfaces and Components in UML Modelers can represent interfaces in UML class diagrams using class icons stereotyped as interfaces. The relationship between an interface and a class that realizes or implements it is indicated by a dashed generalization arrow: Notice that the container doesn't know the type of components it uses. It only knows that its components realize or implement the IComponent interface. For example, imagine that a pilot flies an aircraft by remote control from inside of a windowless hangar. The pilot holds a controller with three controls labeled: TAKEOFF, FLY, and LAND, but he has no idea what type of aircraft the controller controls 178 The pilot only knows about the operations that are specifically declared in the Aircraft interface. Can create new interfaces from existing interfaces using generalization. Interfaces and Components in Java Java: interface Aircraft { public void takeoff(); public void fly(); 179 public void land(); } Notice that the interface declaration lacks private and protected members. There are no attributes, and no implementation information is provided. class Pilot { private Aircraft myAircraft; public void fly() { myAircraft.takeoff(); myAircraft.fly(); myAircraft.land(); } public void setAircraft(Aircraft a) { myAircraft = a; } // etc. } Java: class Airplane public void public void public void public void // etc. } implements Aircraft { takeoff() { /* Airplane takeoff algorithm */ } fly() { /* Airplane fly algorithm */ } land() { /* Airplane land algorithm */ } bank(int degrees) { /* only airplanes can do this */ } Ex. Pilot p = new Pilot("Charlie"); p.setAircraft(new Blimp()); p.fly(); // Charlie flies a blimp! p.setAircraft(new Helicopter()); p.fly(); // now Charlie flies a helicopter! p.setAircraft(new Aircraft()); // error! Java interface extension: interface Airliner extends Aircraft, Carrier { public void serveCocktails(); } class PassengerPlane extends Airplane public void add(Passenger p) { ... public void rem(Passenger p) { ... public void serveCocktails() { ... // etc. } implements Airliner { } } } Abstract Classes Classes only inherit obligations from the interfaces they implement-obligations to implement the specified member functions. 180 Abstract class is a partially defined class. Classes derived from an abstract class inherit both features and obligations.: abstract public class Aircraft { protected double altitude = 0, speed = 0; public Aircraft(double a, double s) { altitude = a; speed = s; } public Aircraft() { altitude = speed = 0; } public double getSpeed() { return speed; } public double getAltitude() { return altitude; } public void setSpeed(double s) { speed = s; } public double setAltitude(double a) { altitude = a; } abstract public void takeoff(); abstract public void fly(); abstract public void land(); } Names of abstract classes and virtual functions are italicized in UML: Working with Unknown Classes A framework is a polymorphic application. A framework is an application that can take on many forms, depending on how it is customized. As such, framework programmers often need to refer to classes that won't be defined until the framework is customized, and of course different customizations may define these classes in different ways. Polymorphism allows framework programmers to work around these critical pieces of missing information. 181 MFW: A Framework for Music Applications MFW is a music framework that can be customized into various musical applications such as score editors, virtual recording studios, virtual instruments, expert systems for composers, and interfaces to computer controlled instruments such as synthesizers. One part of MFW defines various ensembles of musical instruments: bands, orchestras, trios, quartets, etc. Unfortunately, the types of musical instruments will be defined much later in the various customizations of the framework. How can MFW form ensembles without knowing the identity of their constituent instruments? There are two solutions: make Instrument an abstract class in MFW, or make Instrument an MFW interface. interface Instrument { public void play(); } class Trio { Instrument first, second, third; Trio(Instrument a, Instrument b, Instrument c) { first = a; second = b; third = c; } public void play() { if (first != null) first.play(); if (second != null) second.play(); if (third != null) third.play(); } } Problem: create and play trios consisting of different combinations of horns, harps, and drums. class Harp implements Instrument { public void play() { System.out.println("plink, plink, plink"); } } Trio t1 = new Trio(new Harp(), new Horn(), new Drum()); Trio t2 = new Trio(new Drum(), new Drum(), new Harp()); t1.play(); t2.play(); Heterogeneous Collections Orchestra can be regarded as a container of different types of instruments: class Orchestra { private Instrument[] instruments = new Instrument[100]; private int size = 0; public void add(Instrument i) throws MFWError { if (100 <= size) throw new MFWError("orchestra full"); instruments[size++] = i; 182 } public void play() { for(int i = 0; i < size; i++) instruments[i].play(); } } Clients of the orchestra class can add a variety of instruments to orchestra instances: Orchestra orch = new Orchestra(); orch.add(new Horn()); orch.add(new Harp()); orch.add(new Horn()); orch.add(new Harp()); orch.add(new Drum()); orch.play(); Virtual Factory Methods Each note encapsulates a frequency and duration: abstract class Note { protected double freq; // in Hz protected double duration; // in mSec public Note(double f, double d) { freq = f; duration = d; } public abstract void play(); // quality? timbre? } class HornNote extends Note { public HornNote(double f, double d) { super(f, d); } public void play() { System.out.println("honk"); } } Replace the Instrument interface by a concrete class: class Instrument { public void play() { for(int i = 0; i < 3; i++) { Note note = makeNote(); // virtual factory method note.play(); } } } But how can the Instrument class know what type of Notes to create? Factory Method [Go4] Other Names Virtual constructor. Problem A "factory" class can't anticipate the type of "product" objects it must create. 183 Solution Provide the factory class with an ordinary member function that creates product objects. This is called a factory method. The factory method can be a virtual function implemented in a derived class, or a template function parameterized by a product constructor. An ordinary member function that creates and returns new objects is called a factory method. abstract class Instrument { public void play() { for(int i = 0; i < 3; i++) { Note note = makeNote(); note.play(); } } abstract public Note makeNote(); } The obligation to implement the virtual factory method falls on the shoulders of extension classes: class Horn extends Instrument { public Note makeNote() { return new HornNote(100, 100); } } Problem: programmers must maintain two parallel inheritance hierarchies, one for factories, and one for products: If a programmer creates a new type of note, he must remember to create the corresponding factory instrument that creates the note. 184 Factories in Java Inner Classes abstract class Instrument { abstract class Note { protected double freq; // in Hz protected double duration; // in mSec public Note(double f, double d) { freq = f; duration = d; } public abstract void play(); // quality? timbre? } // virtual factory method: abstract public Note makeNote(); public void play() { for(int i = 0; i < 3; i++) { Note note = makeNote(); note.play(); } } } class Horn extends Instrument { class HornNote extends Note { public HornNote(double f, double d) { super(f, d); } public void play() { System.out.println("honk"); } } public Note makeNote() { return new HornNote(100, 100); } } Horn factory = new Horn(); Instrument.Note product = factory.new HornNote(100, 100); product.play(); This technique for constructing notes fails if HornNote is declared as a private inner class. In this case clients are forced to use our makeNote() factory method. Inner class objects can access the public, protected, and private fields and methods of their factories, but the syntax is weird: class Horn extends Instrument { class HornNote extends Note { public HornNote(double f, double d) { super(f, d); } public void play() { System.out.print("my instrument = "); Horn.this.show(); System.out.println("honk"); } } public Note makeNote() { 185 return new HornNote(100, 100); } private void show() { System.out.println("Horn"); } } Notes: 1. The inner class instance only exists when an instance of the enclosing class is created. 2. If the inner class is static then it cannot reference any instance variables/methods of an enclosing class since an instance does not exist. UML doesn't have a notation for inner classes; we can use a stereotyped association: Local Classes Classes can even be defined inside of a method: class Horn extends Instrument { public Note makeNote() { class HornNote extends Note { public HornNote(double f, double d) { super(f, d); } public void play() { System.out.println("honk"); } } return new HornNote(100, 100); } } Anonymous Classes class Horn extends Instrument { public Note makeNote() { return new Note(100, 100) { public void play() { System.out.println("honk"); } }; } } The general syntax is: new SuperType(constructor parameters) { inner class methods and data } 186 Closures, Functors, and Thunks A functor (also called functional or function object) is an object that behaves like a method. A thunk (also called a promise) is a special type of functor. It's special because a thunk invocation can be frozen (delayed). Later, the frozen thunk can be thawed (forced), causing it to compute and return a value. The interesting thing is that a thunk remembers its freezing environment, which it uses to produce its value when thawed, even if the thawing environment is different. (Thunks are closely related to closures. A closure is a method that remembers its defining environment.) Every thunk must implement a thaw() method that returns the computed value as an Object: interface Thunk { public Object thaw(); } In this demo we want to convert the method ThunkDemo.foo() into a thunk. The foo() method simply computes the sum of two parameters and a local variable, but in reality, when called, foo() creates and returns an anonymous thunk that computes this value when thawed: class ThunkDemo { public Thunk foo(final int x, final int y) { final int z = 10; return new Thunk() { public Object thaw() { return new Integer(x + y + z); } }; } public static void main(String[] args) { ThunkDemo demo = new ThunkDemo(); int a = 3, b = 4; Thunk thunk = demo.foo(a, b); // freeze demo.foo(a, b) call a = 12; b = 15; // a and b change System.out.println("tic toc tic toc ..."); // much time passes System.out.println("result = " + (Integer)thunk.thaw()); // = 17 } } A memoization thunk caches its computed result so subsequent thaws don't need to recompute anything. In this case Thunk becomes an abstract base class that provides the cache: abstract class Thunk { protected Object cache = null; abstract public Object thaw(); } public class ThunkDemo { public Thunk foo(final int x, final int y) { final int z = 10; return new Thunk() { public Object thaw() { if (cache == null) // only computed once: 187 cache = new Integer(x + y + z); return cache; } }; } public static void main(String[] args) { ThunkDemo demo = new ThunkDemo(); int a = 3, b = 4; Thunk thunk = demo.foo(a, b); // freeze demo.foo(a, b) call a = 12; b = 15; // a and b change System.out.println("tic toc tic toc ..."); // much time passes System.out.println("result = " + (Integer)thunk.thaw()); // = 17 a = 100; b = -40; // a and b change again System.out.println("tic toc tic toc ..."); // more time passes System.out.println("result = " + (Integer)thunk.thaw()); // = 17 } } Clearly this is an advantage if computing the value requires a lot of time. Thunks are useful for representing streams. A stream is a potentially infinite sequence: (2 3 5 7 11 13 17 19 ... ) A stream is represented as a pair: class Stream { private Object car; private Thunk cdr; public Stream(Object a, Thunk b) { car = a; cdr = b; } public Object head() { return car; } public Stream tail() { return new Stream(cdr.thaw(), cdr); } } In the following demo, the tail of a stream representing the infinite sequence of integers always produces the next integer by incrementing the cache: public class StreamDemo { public Thunk next() { final int x = 0; return new Thunk() { public Object thaw() { if (cache == null) cache = new Integer(0); else cache = new Integer(((Integer)cache).intValue() + 1); return cache; } }; } Here is a test harness that prints out the first 20 values of a potentially infinite stream, s: 188 public static void main(String[] args) { StreamDemo demo = new StreamDemo(); Stream s = new Stream(new Integer(0), demo.next()); System.out.print('('); for(int i = 0; i < 20; i++) { System.out.print("" + (Integer)s.head() + ' '); s = s.tail(); } System.out.println(')'); } } Here's the program output: (0 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18) Note: I'm not sure this example is very interesting, because one could more easily accomplish the same thing in Java without using thunks. However, we note that the use of inner classes provides the capability of easily constructing infinite streams. Consider using thunks/memoization for providing more interesting infinite streams, such as a stream of primes. Also, consider combining streams, such as the stream of every other prime. Power Types as Factories Meta-classes and power-classes allow modelers to separate class from type. Instead of representing types implicitly as classes with no runtime existence, we can represent types as objects that instantiate meta or power classes. For example: Alternatively, we could define a single Aircraft class and represent the different types of aircraft by different instances of an AircraftType power class with a factory method: Insure that users won't bypass the factory and directly create aircraft? interface IAircraft { public AircraftType getType(); public void takeoff(); 189 public void fly(); public void land(); } class AircraftType { private String name; public AircraftType(String n) { name = n; } public String toString() { return name; } // factory method: public IAircraft makeAircraft() { return new Aircraft(); } private class Aircraft implements IAircraft { public void takeoff() { System.out.println("An aircraft is taking off"); } public void fly() { System.out.println("An aircraft is flying"); } public void land() { System.out.println("An aircraft is landing"); } public AircraftType getType() { return AircraftType.this; } } // Aircraft } // AircraftType public class TheApp { public static void main(String[] args) { AircraftType blimp = new AircraftType("Blimp"); AircraftType helicopter = new AircraftType("Helicopter"); //IAircraft a = blimp.new Aircraft(); // ok if inner class is public IAircraft a = blimp.makeAircraft(); a.takeoff(); a.fly(); a.land(); System.out.println("a.getType() = "+ a.getType()); //prints Blimp } } Reflection in Java: interface IAircraft { // public AircraftType getType(); public void takeoff(); public void fly(); public void land(); } class SpaceShip implements IAircraft { public void takeoff() { System.out.println("A spaceship is taking off"); } public void fly() { System.out.println("A spaceship is flying"); 190 } public void land() { System.out.println("A spaceship is landing"); } } import java.lang.reflect.*; public class Test { public static void main(String[] args) { try { String className = "SpaceShip"; Class c = Class.forName(className); IntfAircraft a = (IntfAircraft)c.newInstance(); Method[] meths = c.getMethods(); for(int i = 0; i < meths.length; i++) if (meths[i].getName().equals("takeoff")) meths[i].invoke(a, null); } catch(Exception e) { System.err.println("error = " + e); } } } Toolkits A toolkit (also called an abstract factory or simply a kit) is a class or object that provides factory methods that produce the components needed to construct all or part of an application. In a sense, toolkits are precursors to frameworks. While a framework is already assembled and only requires customization, a toolkit only provides the components programmers will need to assemble an application. The advantage of a toolkit is that the entire assembly process can be described relative to the toolkit. Thus, the toolkit decouples the assembly process from the details of how the components are created (see Java’s AWT). Toolkits are formalized by the abstract factory design pattern: Abstract Factory [Go4] Other Names Kit, toolkit Problem A system should be independent of how its components are implemented. Solution Provide the system with an abstract factory parameter. An abstract factory is a class consisting of virtual or template factory methods for making the system's components. Various derived classes or template instances provide implementations of the factory methods. As an example, let's consider building graphical user interfaces (GUIs). Unfortunately, the standard C++ library doesn't provide GUI components such as windows, menus, and buttons. 191 GUI components are highly platform-dependent - these components are supplied by platformspecific libraries such as the X-Windows library. We can partially relieve the pain of a port by using a toolkit to decouple the construction of a GUI from the details of how the GUI components are constructed. We begin by defining an abstract user interface toolkit: interface UIToolkit { Window makeWindow(); Button makeButton(); MenuItem makeMenuItem(); EditBox makeEditBox(); ListBox makeListBox(); // etc. }; In addition, the toolkit includes a hierarchy of abstract component classes or interfaces: abstract class UIComponent { // draw this component in parent window: public abstract virtual void draw(); // handle keyboard & mouse messages: public void handle(Message msg) {} // etc. protected protected Point corner; // upper left corner protected int height, width; // size protected Window parent; // = null for desktop window }; class MyApp { static Window makeMyGUI(UIToolkit tk) { Window w = tk.makeWindow(); Button b = tk.makeButton(); w.adopt(b); EditBox e = tk.makeEditBox(); w.adopt(e); // etc. return w; } // etc. } 192 For example, assume Z Windows is a library of GUI components for a particular platform (e.g., Z = X, MFC, MacApp2, etc.): Use adapters (which will be discussed in Chapter 4). For example: class ZButtonAdapter extends Button { private ZButton peer; // the corresponding Z component public ZButtonAdapter() { peer = new ZButton(); } public void draw() { peer.paint(); } public void handle(Message msg) { peer.onMsg(msg); } } We must also provide a Z Windows implementation of the abstract toolkit class: class ZUIToolkit extends UIToolkit { public Window makeWindow() { return new ZWindowAdapter(); } public Button makeButton() { return new ZButtonAdapter(); } public MenuItem makeMenuItem() { return new ZMenuItemAdapter(); } public EditBox makeEditBox() { return new ZEditBoxAdapter(); } public ListBox makeListBox() { return new ZListBoxAdapter(); } // etc. } Here is how the programmer might start an application using the ZUIToolkit: class MyApp { public static void main(String[] args) { Window appWindow = makeMyGUI(new ZUIToolkit()); appWindow.draw(); // draw app window & start msg loop } // etc. } Generic Methods Generic Algorithm [Go4], [ROG] Other Names Template method Problem 193 Parts of an algorithm are invariant across a family of framework customizations, while other parts are not. Solution Move the algorithm into the framework. Replace the non-invariant parts by calls to virtual functions that can be implemented differently in different customizations. Ex. Add a symphony class to MFW abstract class Symphony { public void play() { // a generic algorithm doFirstMovement(); doSecondMovement(); doThirdMovement(); doFourthMovement(); } protected abstract virtual void doFirstMovement() = 0; protected abstract virtual void doSecondMovement() = 0; protected abstract virtual void doThirdMovement() = 0; protected abstract virtual void doFourthMovement() = 0; } Framework customizers will have to subclass Symphony and implement the do-functions. For example: class TheFifth extends Symphony { protected void doFirstMovement() System.out.println("dah, dah, } void doSecondMovement() { System.out.println("duh, duh, } void doThirdMovement() { System.out.println("dah, duh, } void doFourthMovement() { System.out.println("duh, dah, } } { dah, duh ..."); duh, dah ..."); duh, dah ..."); dah, duh ..."); Singletons In some situations, multiple instances might be illogical: TheFifth s1, s2, s3; // how many Fifth Symphonies did he write? The Singleton pattern solves this problem by making all constructors private. A public factory method is provided that allows users to create instances, but the factory method always returns pointers to the same hidden instance: Singleton [Go4] Problem There must be at most one instance of a class. Solution 194 Make all constructors private, including the default and copy constructors. Provide a static function that initializes and returns a private, static pointer to the sole instance of the class. For example, let's apply the singleton pattern to the TheFifth class from the previous example: class TheFifth extends Symphony { public static TheFifth makeTheFifth() { if (theFifth == null) theFifth = new TheFifth(); // constructor access ok here return theFifth; } // etc. private static TheFifth theFifth; // the one and only private TheFifth() {} // hide default constructor } Only one instance is ever created: TheFifth s1 = TheFifth.makeTheFifth(); TheFifth s2 = TheFifth.makeTheFifth(); TheFifth s3 = TheFifth.makeTheFifth(); s1.play(); s2.play(); s3.play(); Example: A Simple Application Framework Java's platform independence means, among other things, that the Java VM doesn't care how characters and numbers are represented on the host platform. This makes writing a simple interactive program with a console user interface (CUI) a bit of a challenge. Since this is a common task, we now present the first of two CUI-based application frameworks. The entire framework consists of two classes: Console and AppError. Note: This demonstrates the Generic Pattern. Calculator C:\Pearce\JPOP\console>java Calculator type "help" for commands -> help Console Help Menu: about: displays application information help: displays this message quit: terminate application ARG1 + ARG2 = sum of ARG1 and ARG2 ARG1 * ARG2 = product of ARG1 and ARG2 ARG1 - ARG2 = difference of ARG1 and ARG2 ARG1 / ARG2 = quotient of ARG1 and ARG2 Note 1: Spaces between tokens are required Note 2: ARG1 & ARG2 are numbers. The about command displays information about the framework and the customization: -> about Console Framework copyright (c) 2001, all rights reserved 195 A Simple Calculator Copyright (c) 2001, all rights reserved Of course we can do arithmetic and small errors don't break the calculator: -> -13 + 51 result = 38.0 -> 12 / 0 Application error, can't divide by 0 -> 13.8 * 3.1416 result = 43.35408 -> x + 2 Application error, ARG1 & ARG2 must be numbers -> quit bye The Console Framework Design Implementation abstract public class Console { protected BufferedReader stdin; protected PrintWriter stdout; protected PrintWriter stderr; protected String prompt = "-> "; abstract protected String execute(String cmmd) throws AppError; public Console() { stdout = new PrintWriter( new BufferedWriter( new OutputStreamWriter(System.out)), true); stderr = new PrintWriter( new BufferedWriter( 196 new OutputStreamWriter(System.err)), true); stdin = new BufferedReader( new InputStreamReader(System.in)); } public void controlLoop() { ... } // overidables: protected void help() { stdout.println("Console Help Menu:"); stdout.println(" about: displays application information"); stdout.println(" help: displays this message"); stdout.println(" quit: terminate application"); } protected void about() { stdout.println("Console Framework"); stdout.println("copyright (c) 2001, all rights reserved\n"); } protected boolean handle(AppError exp) { stderr.println("Application error, " + exp); return true; } } The Control Loop public void controlLoop() { boolean more = true; String cmmd = " "; String result = " "; String done = "done"; stdout.println("type \"help\" for commands"); while(more) { try { stdout.print(prompt); stdout.flush(); // force the write cmmd = stdin.readLine(); if (cmmd.equals("quit")) { more = false; } else if (cmmd.equals("help")) { help(); } else if (cmmd.equals("about")) { about(); } else { // an application-specific command? result = execute(cmmd); stdout.println(result); } } catch(AppError exp) { more = handle(exp); } catch (IOException exp) { stderr.println("IO error, " + exp); } catch (Exception exp) { stderr.println("Serious error, " + exp); more = false; } } // while 197 stdout.println("bye"); } // controlLoop Application Errors public class AppError extends Exception { private String gripe; public String toString() { return gripe; } public AppError(String g) { super(g); gripe = g; } public AppError() { super("unknown"); gripe = "unknown"; } } The Calculator Extension (just extend the CUI framework where needed) class Calculator extends Console { protected String execute(String cmmd) throws AppError { ... } protected void help() { super.help(); stdout.println(" ARG1 + stdout.println(" ARG1 * stdout.println(" ARG1 stdout.println(" ARG1 / stdout.println("Note 1: stdout.println("Note 2: } ARG2 = ARG2 = ARG2 = ARG2 = Spaces ARG1 & sum of ARG1 and ARG2"); product of ARG1 and ARG2"); diff of ARG1 and ARG2"); ratio of ARG1 and ARG2"); between tokens are required"); ARG2 are numbers."); protected void about() { super.about(); stdout.println("A Simple Calculator"); stdout.println("Copyright (c) 2001, all rights reserved"); } public static void main(String[] args) { Calculator calc = new Calculator(); calc.controlLoop(); } } // Calculator The execute() Method protected String execute(String cmmd) throws AppError { double arg1, arg2; String op; StringTokenizer tokens = new StringTokenizer(cmmd); 198 try { String digits = tokens.nextToken(); arg1 = Double.valueOf(digits).doubleValue(); op = tokens.nextToken(); digits = tokens.nextToken(); arg2 = Double.valueOf(digits).doubleValue(); if (op.equals("+")) { return "result = " + (arg1 + arg2); } else if (op.equals("*")) { return "result = " + (arg1 * arg2); } else if (op.equals("-")) { return "result = " + (arg1 - arg2); } else if (op.equals("/")) { if (arg2 == 0) throw new AppError("can\'t divide by 0"); return "result = " + (arg1/arg2); } else { throw new AppError("operator must be +, -, *. or /"); } } catch (NumberFormatException e) { throw new AppError("ARG1 & ARG2 must be numbers"); } catch (NoSuchElementException e) { throw new AppError("usage: ARG1 OP ARG2"); } } Example: Pipelines A pipe is a message queue. A message can be anything. A filter is a process, thread, or other component that perpetually reads messages from an input pipe, one at a time, processes each message, then writes the result to an output pipe. Thus, it is possible to form pipelines of filters connected by pipes: UNIX: % cat inFile | grep pattern | sort > outFile In this case pipes (i.e., "|") are inter process communication channels provided by the operating system, and filters are any programs that read messages from standard input, and write their results to standard output. Pipes and Filters [POSA] Other Names Pipelines 199 Problem The steps of a system that processes streams of data must be reusable, re orderable, replaceable, and/or independently developed. Solution Implement the system as a pipeline. Steps are implemented as objects called filters. Filters receive inputs from, and write outputs to streams called pipes. A filter knows the identity of its input and output pipes, but not its neighboring filters. Filter Classification Producers - producer of messages (no input pipe) Generates a message into its output pipe Consumers - consumer of messages. It has no output pipe. It eats messages taken from its input pipe Transformers - reads a message from its input pipe, modulates it, and then writes the result to its output pipe. (This is what DOS and UNIX programmers call filters.) Testers - reads a message from its input pipe, and then tests it. If the message passes the test, it is written, unaltered, to the output pipe; otherwise, it is discarded. (This is what signal processing engineers call filters). Filters can also be classified as active or passive. An active filter has a control loop that runs in its own process or thread. It perpetually reads messages from its input pipe, processes them, then writes the results to its output pipe. An active filter needs to be derived from a thread class provided by the operating system: class Filter extends Thread { ... } void controlLoop() // for active filter { while(true) { Message val = inPipe.read(); val = transform(val); // do something to val outPipe.write(val); } } When activated, a passive filter reads a single message from its input pipe, processes it, and then writes the result to its output pipe: void activate() { Message val = inPipe.read(); val = transform(val); // do something to val outPipe.write(val); } 200 There are two types of passive filters. A data-driven filter is activated when another filter writes a message into its input pipe. A demand-driven filter is activated when another filter attempts to read a message from its empty output pipe. Dynamic Structure: Data-Driven Dynamic Structure: Demand-Driven A Problem How does the transformer know when to call pipe1.read()? How does the data-driven consumer know when to call pipe2.read()? How does the demand-driven producer know when to produce a message? Active filters solve this problem by polling their input pipes or blocking when they read from an empty input pipe, but this is only feasible if each filter is running in its own thread or process. We could have the producer in the data-driven model signal the transformer after it writes a message into pipe 1. The transformer could then signal the consumer after it writes a message into pipe 2. In the demand-driven model the consumer could signal the transformer when it 201 needs data, and the transformer could signal the producer when it needs data. But this solution creates dependencies between neighboring filters. The same transformer couldn't be used in a different pipeline with different neighbors. Pipes are publishers and filters are subscribers. In the data-driven model filters subscribe to their input pipes. In the demand-driven model filters subscribe to their output pipes. PIPES: A Pipeline Toolkit PIPES is a package containing reusable declarations of pipes and filters. Programmers create filters (transformers, testers, producers, and consumers) by creating extensions of PIPES classes. Pipelines are constructed by instantiating these extension classes, then connecting them with pipes. (Thus, PIPES is a toolkit, not a framework.) For example, pipeline that sums the squares of odd integers read from the keyboard. import pipes.*; public class SOS { class class class class Square extends Transformer { ... } // inner classes OddTester extends Tester { ... } Accum extends Consumer { ... } NumReader extends Producer { ... } public SOS() { Pipe p1 = new Pipe(), p2 = new Pipe(), p3 = new Pipe(); NumReader f1 = new NumReader(p1); OddTester f2 = new OddTester(p1, p2); Square f3 = new Square(p2, p3); Accum f4 = new Accum(p3); f1.start(); } public static void main(String[] args) { SOS pipeline = new SOS(); } } Program Output enter a number: 2 enter a number: 3 accum = 9 enter a number: 4 enter a number: 5 accum = 34 enter a number: 6 enter a number: 7 accum = 83 enter a number: q PipelineError: java.lang.NumberFormatException: q class Square extends Transformer { public Square(Pipe ip, Pipe op) { super(ip, op); 202 } public Object transform(Object num) throws PipelineError { if (!(num instanceof Integer)) throw new PipelineError("message must be an integer"); int n = ((Integer)num).intValue(); return new Integer(n * n); } } class OddTester extends Tester { public OddTester(Pipe ip, Pipe op) { super(ip, op); } public boolean test(Object num) throws PipelineError { if (!(num instanceof Integer)) throw new PipelineError("message must be an integer"); int n = ((Integer)num).intValue(); return (n%2 != 0); } } class Accum extends Consumer { private int accum = 0; public Accum(Pipe ip) { super(ip); } public void consume(Object num) throws PipelineError { if (!(num instanceof Integer)) throw new PipelineError("message must be an integer"); int n = ((Integer)num).intValue(); accum += n; System.out.println("accum = " + accum); } } class NumReader extends Producer { BufferedReader stdin; public NumReader(Pipe op) { super(op); stdin = new BufferedReader(new InputStreamReader(System.in)); } public Object produce() throws PipelineError { try { System.out.print("enter a number: "); String s = stdin.readLine(); return Integer.valueOf(s); } catch (IOException e) { throw new PipelineError("" + e); } catch (NumberFormatException e) { throw new PipelineError("" + e); } } } Design of PIPES Add and remove filters from pipelines without too much difficulty. Therefore, filters in a pipeline should be independent of each other. In other words, a filter should only know the identity of the pipes that connect it to its neighboring filters, not the neighbors themselves. 203 A pipe should be able to connect any filters. Therefore, although a filter may know the identity of its input and output pipes, a pipe is loosely coupled to the filters it connects. Pipes are publishers, and filters are subscribers that subscribe to their input pipes in datadrive pipelines. Implementation of PIPES Filters abstract class Filter implements Observer { protected Pipe inPipe = null, outPipe = null; // redefine in Producer: public void start() throws PipelineError { throw new PipelineError("This is not a producer filter."); } } Pipes public class Pipe extends Observable { private Object message; // the stored message public Object read() { return message; } 204 public void write(Object val) { message = val; setChanged(); notifyObservers(); // data driven clearChanged(); } } Error Handling Exceptions should be handled by the pipeline driver (the producer in the case of a data-drive pipeline), which can shut the pipeline down if necessary. Our hack for solving this problem is to provide a global error flag that will be checked by every update method. If the flag is not null, then an update method does nothing. If not null, and if an exception is thrown while the update method is processing a message, then the update method catches the exception and quietly assigns it to the global error flag. public class PipelineError extends Exception { public PipelineError(String gripe) { super(gripe); } static PipelineError flag = null; // global error flag } Consumer public class Consumer extends Filter { public Consumer(Pipe ip) { inPipe = ip; ip.addObserver(this); } public void update(Observable p, Object what) { if (PipelineError.flag == null) { try { Object val = inPipe.read(); consume(val); } catch (PipelineError e) { PipelineError.flag = e; } } } protected void consume(Object msg) throws PipelineError {} } Transformers public class Transformer extends Filter { public Transformer(Pipe ip, Pipe op) { inPipe = ip; outPipe = op; ip.addObserver(this); } public void update(Observable p, Object what) { if (PipelineError.flag == null) { try { Object val = inPipe.read(); val = transform(val); 205 outPipe.write(val); } catch (PipelineError e) { PipelineError.flag = e; } } } protected Object transform(Object msg) throws PipelineError { return msg; } } Testers public class Tester extends Filter { Tester(Pipe ip, Pipe op) { inPipe = ip; outPipe = op; ip.addObserver(this); } public void update(Observable p, Object what) { if (PipelineError.flag == null) { try { Object val = inPipe.read(); if (test(val)) outPipe.write(val); } catch (PipelineError e) { PipelineError.flag = e; } } } protected boolean test(Object msg) throws PipelineError { return true; } } Producer public class Producer extends Filter { public Producer(Pipe op) { outPipe = op; } public void update(Observable p, Object what) {} // no op public void start() { boolean more = true; Object msg = null; while(more) { try { if (PipelineError.flag != null) throw PipelineError.flag; msg = produce(); outPipe.write(msg); } catch(PipelineError e) { System.err.println( "" + e ); more = false; } catch(Exception e) { System.err.println( "Unknown error! Shutting down" ); more = false; } 206 } } protected Object produce() throws PipelineError { return null; } } Programming Notes Stereotypes UML can be extended using stereotypes. A stereotype is an existing UML icon with a stereotype label of the form: <<stereotype>> Stereotypes can be used to indicate the role a class plays within the collaboration. For example: Report* Secretary::type(...) { return new Report(...); } Note that type is a factory method. The class containing the factory method plays the role of a "factory", the return type of the factory method plays the role of a "product", and the association between the factory and product classes is that the factory class creates instances of the product class. Commonly used UML class stereotypes include: <<powertype>> = Instances represent subclasses of another class <<metatype>> = Instances represent other classes <<active>> = Instances own their own thread of control <<persistent>> = Instances can be saved to secondary memory <<control>> = Instance represent system control objects <<boundary>> = Instance represent system interface objects <<entity>> = Instances represent application domain entities <<actor>> = Instances represent external systems or users In some cases a stereotype is so common that it earns its own icon. For example, in UML actors are sometimes represented by stick figures: 207 The Java Collections Framework Example: class Fleet { private Set members = new HashSet(); public void add(Airplane a) { members.add(a); } public void rem(Airplane a) { members.remove(a); } public Iterator iterator() { return members.iterator(); } } Fleet united = new Fleet(); united.add(new Airplane()); united.add(new Airplane()); united.add(new Airplane()); Iterator it = united.iterator(); while(it.hasNext()) { Airplane a = (Airplane)it.next(); a.takeoff(); } 208 More on Design Patterns (ref. Design Patterns Explained: A New Perspective on Object-Oriented Design by A. Shalloway & J. Trott, Addison-Wesley, 2002) Suppose we are confronted with the task of designing a program that works with graphical widgets such as points, lines and squares. Furthermore, we have a drawing program (DP1) and anticipate purchasing a second drawing program (or, we must modify the program to work with a client’s drawing program). We’ll refer to these drawing programs as DP1 and DP2. We must follow “best practices” in our software engineering – e.g. consider the “Open-Closed Principle” (code that has been developed should be closed for modification and open for extension). We conclude it’s necessary to abstract on what is common amongst the various widgets – that is Shape Client Shape Point Line Square We can add new types of Shapes without affecting Client’s code (it’s closed for modification) Shape behavior: get/set location show/hide Shape fill Shape set color of Shape 209 Adaptor Pattern Shape +setLocation +getLocation +display +unDisplay +fill +setColor Point +display +unDisplay +fill Line +display +unDisplay +fill Square +display +unDisplay +fill What if client wants to add a circle Shape? We note that a colleague has already developed a circle class XCircle +setLocation +getLocation +displayIt +unDisplayIt +fillIt +setItsColor We need to Adapt it to fit our hierarchy – we want to preserve polymorphism (that is, clients still want to deal with Shapes so they must be able to use the circle wherever they want to deal with generic shapes). The Adaptor is actually the same as a wrapper. 210 Client Shape +setLocation +getLocation +display +unDisplay +fill +setColor Point +display +unDisplay +fill Line +display +unDisplay +fill Square +display +unDisplay +fill Circle +display +unDisplay +fill +setLocation +getLocation +setColor XCircle +displayIt +unDisplayIt +fillIt +setLocation +getLocation +setItsColor 211 Façade Pattern Suppose we have a Database used by a client Operations: 1. Open Database and get a Model 2. Query the Model to get an Element 3. Request information from the Element Client A Database Client B Model Element Using this schema it’s difficult to deal with timing and coordination issues (sequencing requests, etc.). We need to simplify the clients’ tasks and shield them from these difficult to manage details. Client A Client B Database facade Database Model Element This pattern greatly reduces the number of objects that clients must communicate with. Clients will provide the façade with the necessary information and the façade will then deal with timing/coordination issues. This pattern is used extensively in e-commerce applications utilizing Enterprise JavaBeans. 212 Comparison between Façade and Adaptor Patterns On the surface they seem to be very similar Are there preexisting classes? Is there an interface we must design to? Does an object need to behave polymorphically? Is a simpler interface needed? 213 Facade Yes No No Yes Adaptor Yes Yes Probably No Encapsulation Encapsulation really refers to ANY kind of hiding: data members method members objects subclasses The following exhibits object encapsulation: Circle +display +unDisplay +fill +setLocation +getLocation +setColor XCircle +displayIt +unDisplayIt +fillIt +setLocation +getLocation +setItsColor The following quote is taken from the GOF text: Consider what should be variable in your design. This approach is the opposite of focusing on the cause of redesign. Instead of considering what might force a change to a design, consider what you want to be able to change without redesign. The focus here is on encapsulating the concept that varies, a theme of many design patterns. Note that a user program using Shapes only sees Shapes her/his code doesn’t care about the kinds of Shapes this abstract Shape class served to encapsulate the hierarchy of Shape subclasses. The client code is closed to modification and open to extension (we can easily add new Shapes – subclass Shape – without requiring changes to the client code) 214 Abstract classes represent Concepts. Their operations represent an API, a specification. The Concrete classes (subclasses of the Abstract classes) represent the implementation. Consider the following simple diagram depicting the relationship between Commonality and Variability analysis: By looking at what these objects must do (conceptual perspective), we determine how to call them (specification perspective) Commonality analysis Conceptual perspective AbstractClass +Operations Specification perspective Variability analysis ConcreteClass +Operations Implementation perspective ConcreteClass +Operations When implementing these classes, ensure that the API provides sufficient information to enable proper implementation and decoupling 215 Bridge Pattern Let’s extend the prior hierarchy to allow for 2 drawing programs, DP1 and DP2 Shape +draw Client Rectangle +draw #drawLine V1Rectangle #drawLine Circle +draw #drawCircle V2Rectangle #drawLine V1Circle #drawCircle DP1 +draw_a_line +draw_a_circle V2Circle #drawCircle DP2 +drawline +drawcircle Note #drawLine is a protected method while +drawLine is a public method Note that we have 4 kinds of Shapes: V1Rectangle, V2Rectangle, V1Circle and V2Circle What if we include a third drawing program? We’ll need two more Shapes, yielding 6 kinds of Shapes. What if we add a new Shape (e.g. a pentagon)? With the 3 drawing programs will need 3 more kinds of Shapes, yielding 9 kinds of shapes. The size of the hierarchy is dramatically increasing! The problem is that the abstraction (Shapes) is tightly coupled to the implementation (drawing programs). 216 We perform Commonality Analysis amongst the concepts and note that we have two major concepts – Shapes and Drawing Programs – with several variations in each category. The Commonality Analysis identifies structures that are unlikely to change over time. Given the commonality structures we can speak about what varies. Strategy: Find what varies and encapsulate it Favor composition over inheritance (e.g. refer to the Adaptor Pattern example described above) Drawing +drawLine +drawCircle Shape +draw #drawLine #drawCircle Rectangle +draw Circle +draw V1Drawing +drawLine +drawCircle V2Drawing +drawLine +drawCircle This design is much simpler. The cohesion is better – the shapes don’t deal with implementation issues, in addition to the basic drawing functions; drawings don’t know about shapes (low coupling). The hierarchy grows linearly as we add new shapes or drawing programs. 217 Composite Patterns Drawing +drawLine +drawCircle Adaptors: V1Drawing +drawLine +drawCircle V2Drawing +drawLine +drawCircle DP1 +draw_a_line +draw_a_circle DP2 +drawline +drawcircle We want to provide a common drawing interface to all of the drawing programs. Thus, we are using V1Drawing and V2Drawing as Adaptors. In this case, we are using the Adaptor Pattern along with the Bridge Pattern. This is a Composite Pattern. These Composite Patterns are found frequently in practice. 218 Delegation (Appendix 6) Consider using specialization: class Handyman extends Carpenter { ... } class Houskeeper extends Cook { ... } What if same handyman gets a job as a plumber or electrician, Cannot dynamically change the base class of a handyman: class Handyman extends Plumber {... } Multiple inheritance is allowed in C++ (but not Java): class Handyman: public Carpenter, public Plumber, public Electrician { ... }; may be inefficient because every handyman object now carries the attributes of all three base classes: . But some handymen may never get jobs as plumbers or electricians, while others may find work at jobs we haven't anticipated, such as gardening or painting. Objects as States State [Go4] Other Names Objects as States Problem 219 The attributes and behavior of an object, x, may depend on its state, but its state may change dynamically. This can lead to member function implementations based on awkward multi-way conditionals that are difficult to maintain: switch (state) { case state1: ... case state2: ... // etc. } Solution Implement each branch of the multi-way conditional as a member function in a separate class derived from an abstract state base class. The member functions of x simply forward client requests to the corresponding member functions of the associated state object, which may be changed dynamically. Static Structure Here's how the State class might be declared: abstract class State { abstract void serviceA(Context c); abstract void serviceB(Context c); abstract void serviceC(Context c); } class State1 extends State { ... } class State2 extends State { ... } class State3 extends State { ... } class Context { void serviceA() { if (state != void serviceB() { if (state != void serviceC() { if (state != void setState(State s) { state // etc. private State state = null; // // etc. } null) state.serviceA(this); } null) state.serviceB(this); } null) state.serviceC(this); } = s; } current state Instantly change the behavior of the context's services. 220 class State1 extends State { void serviceC(Context c) { if (condition2) c.setState(new State2()); else if (condition3) c.setState(new State3()); // etc. } // etc. } class Handyman { void getToWork() { if (job != null) job.getToWork(this); } void setJob(Job j) { job = j; } private Job job = null; } abstract class Job { abstract void getToWork(Handyman h); } class Electrician extends Job { void getToWork(Handyman h) { /* change light bulbs, etc. */ } } class Plumber extends Job { void getToWork(Handyman h) { /* plunge toilets, etc. */ } } class Carpenter extends Job { void getToWork(Handyman h) { /* mend fences, etc. */ } } State pattern - only use when the nature of a state is too complex to be characterized by a single variable containing a simple value such as an int, string, or float. Also, frequent state construction and destruction can be inefficient if they are complex. Delegation Defined State pattern - one of many design patterns that use delegation. In the case of the state pattern, the context object is entrusting the power and responsibility of implementing its services to its agent or delegate, the associated state object. More concretely, delegation means that an object transparently forwards client requests to a hidden delegate object, which may be dynamically changed: class Context { void serviceA() { if (state != null) state.serviceA(this); } // etc. } Delegation is an inheritance mechanism at the level of individual objects, not of classes. Adapters Adapter [Go4] 221 Other Names Wrapper Problem An existing server class implements the functions required by a client, but the interface doesn't match the required interface. Solution Insert an adapter between the client and the server. The adapter implements the interface required by the client by calling the corresponding server functions. The adapter and server can be related by specialization or delegation. Static Structures Alternatively, the adapter can access the adaptee services by delegation: Implementations interface Server { void serviceA(); void serviceB(); // etc. } 222 class Adaptee { void serviceX() { ... } void serviceY() { ... } // etc. } class Client { void doSomething() { myServer.serviceA(); myServer.serviceB(); // etc. } // etc. private Server myServer; } class Adapter extends Adaptee implements Server { void serviceA() { serviceX(); } void serviceB() { serviceY(); } // etc. } OR class Adapter extends implements Server { void serviceA() { adaptee.serviceX(); } void serviceB() { adaptee.serviceY(); } // etc. private Server adaptee; } Handle-Body Idioms Generally speaking, a handle-body pair is a pair of objects collaborating to appear to the client as a single object. One object, called the handle, manages the interface with the client, while another object, called the body, provides the application logic. class Body { // no client access, handles only! void serviceA(); void serviceB(); // etc. } class Handle { void serviceA() { if (myBody != null) myBody.serviceA(); } void serviceB() { if (myBody != null) myBody.serviceB(); } // etc. private Body* myBody; } Applications 223 Adapter-Adaptee and Context-State are two examples of Handle-Body pairs. Also, RMI - The Body is a Remote Object. Client-side proxy (the handle) encapsulates the low-level communication details. Shared Bodies Decorators Specialization allows us to add features to a class, but in some situations we need to add different combinations of features to individual objects. For example, a grid encapsulates and manages a dynamic two dimensional array of characters that provides "poor man's" graphics: Suppose we will also need a shaded grid (i.e., a grid with a shaded background), a bordered grid (i.e., a grid with a border around it), a read-only grid (i.e., a grid with disabled plot functions), a shaded-read-only grid, a bordered-read-only grid, a shaded-bordered grid, and a shaded-bordered-read-only grid. If we create a class for each combination of these features we will end up with a complicated hierarchy of seven classes: 224 Adding a new feature, such as resizable grid, will add six new derived classes. In general, if we are contemplating n features, there will be 2n-1 combinations of these features. Decorator - implements a feature, then delegates to its body. The body may be the original component or another decorator. Decorator [Go4] Other Names Wrapper Problem In some situations we may want to add features to an individual object rather than an entire class. Solution For each additional feature create a new "decorator" class that implements the feature. The decorator class also implements the interface of the original object by delegating to the original object or perhaps to an instance of another decorator class. In this way decorators (i.e. instances of decorator classes) can be chained together. Static Structure 225 interface IComponent { void serviceA(); void serviceB(); // etc. } class Component implements IComponent { void serviceA() { ... } void serviceB() { ... } // etc. } class Decorator implements IComponent { Decorator(IComponent c) { myBody = c; } void serviceA() { myBody.serviceA(); } // default implementation void serviceB() { myBody.serviceB(); } // etc. private IComponent myBody; } class Decorator1 extends Decorator { Decorator1(IComponent c) { super(c); } void serviceA() { // only service with more functionality // added behavior goes here super.serviceA(); // delegates to body } } IComponent x = new Component(); IComponent y = new Decorator1(x); IComponent z = new Decorator2(y); OR 226 IComponent z = new Decorator2(new Decorator1(new Component())); Streams Ex. static public PrintWriter makeWriter(OutputStream os) { return new PrintWriter( new BufferedWriter( new OutputStreamWriter(os)), true); } A call to this method constructs the decorator chain: 227 Presentation and Control (Appendix 7) The Model-View-Controller Architecture Instances of the Model class often represent the application domain itself. Model aggregates or is composed of application domain objects (proxies). The model controls how its proxies are created, manipulated, and destroyed. The model is also responsible for saving and restoring its proxies. Controller and View classes - application's user interface. Controller class - responsible for handling messages sent by the user or by other objects. Typically, a message is a command to update the model in some way. For example, a flight simulator might provide users with a controller associated with the mouse and a controller associated with an on-screen control panel filled with buttons. These controllers translate mouse movements and button clicks into commands that control the altitude, attitude, and speed of the airplane model: View class - responsible for displaying the model or its components. 228 Model - relatively stable component User interface - very volatile (changes to the user interface are more common than changes in the application data and logic) Keep the coupling between the model and the user interface as loose as possible. This prevents changes in the user interface from propagating to the model. Model-View-Controller [POSE] Other Names MVC, Model-View-Controller architecture, Model-View architecture, Model-View Separation pattern, Document-View architecture. Problem The user interface is the component most susceptible to change. These changes shouldn't propagate to application logic and data. Solution Encapsulate application logic and data in a model component. View components are responsible for displaying application data, while controller components are responsible for updating application data. There are no direct links from the model to the view-controllers. Static Structure Ex. 229 Spreadsheet for Budget Views Model Controllers To take another example, a model component for a word processor might be a document containing a chapter of a book. The user can view the document as an outline (outline view), as separate pages (page layout view), or as a continuous page (normal view): Word processor Views Model Controllers GUI Toolkits Library of components for building GUIs: JFC, AWT Views and View Notification public class MyView extends AppView { public void update(Observable o, Object m) { repaint(); 230 } public void drawComponent(Graphics g) { ... } // etc. } Scenario Example: MFC's Document-View Architecture (Develop word processor) Microsoft Foundation Classes (MFC) is an application framework for building desktop applications for the MS Windows platforms (Windows NT, Windows 9x, Windows CE, Windows 2000). All MFC applications are based on a variant of the Model-View-Controller architecture called the Document-View architecture. (The document is the model.) The skeleton of an MFC application is created by a code generator called the App Wizard. App Wizard asks questions about the application to be built then generates several header and source files. class CWPDoc : public CDocument { ... }; // Model in WPDoc.h class CWPView : public CView { ... }; // View in WPView.h 231 Key member functions in the derived classes are stubs that must be filled in by the programmer. (Code entered by the programmer will be shown in boldface type.) class CWPDoc : public CDocument { private: CString m_text; public: CString GetText() { return m_text; } void Erase(int start, int end); void Insert(int pos, CString text); void Append(CString text); // etc. };, void CWPDoc::Append(CString text) { m_text += text; // append text SetModifiedFlag(true); // used to save info when quitting UpdateAllViews(0); } Serialize automatically called when "Open" or "Save" is selected from the application's File menu.: void CWPDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { ar << m_text; } else { ar >> m_text; } } 232 CView is an abstract class containing virtual member functions called OnUpdate() and OnDraw(). These functions are analogous to our update() and draw() functions. void CWPView::OnDraw(CDC* pDC) { CWPDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); CRect region; // = some rectangular region GetClientRect(&region); // region = client rectangle CString text = pDoc->GetText(); // = text to draw pDC->DrawText(text, &region, DT_WORDBREAK); } Document UpdateAllViews() - calls each view's OnUpdate() function. void CWPView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { Invalidate(); // calls OnDraw(new CDC()) } Controller - keyboard handler when user presses a key void CWPView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { CWPDoc* pDoc = GetDocument(); pDoc->Append(nChar); } ClassWizard used to generate handler stubs 233 Building an Application Framework (AFW) Can be customized into any desktop application that employs the services of the underlying platform. Version 1.0 provides a console user interfaces. Subsequent versions provide graphical user interfaces. AFW 1.0: A CUI Application Framework Separate user interface from application data and logic. Design Console class - combines control and view responsibilities 234 Implementation public class protected protected protected protected protected Console { BufferedReader stdin; PrintWriter stdout; PrintWriter stderr; String prompt = "-> "; ConsoleModel model; public Console(ConsoleModel m) { ... } public void controlLoop() { ... } protected void help() { ... } protected void about() { ... } protected boolean handle(AppError exp) { ... } private void save() throws IOException { ... } private void saveChanges() throws IOException { ... } private void newModel() throws Exception { ... } private void load() throws IOException, ClassNotFoundException { ... } } public Console(ConsoleModel m) { stdout = new PrintWriter( new BufferedWriter( new OutputStreamWriter(System.out)), true); stderr = new PrintWriter( new BufferedWriter( new OutputStreamWriter(System.out)), true); stdin = new BufferedReader( new InputStreamReader(System.in)); model = m; } 235 public void controlLoop() { boolean more = true; String cmmd = " "; String result = " "; String done = "done"; stdout.println("type \"help\" for commands"); while(more) { try { stdout.print(prompt); stdout.flush(); // force the write cmmd = stdin.readLine(); if (cmmd.equals("quit")) { saveChanges(); more = false; } else if (cmmd.equals("help")) { help(); // override in subclass for app specific help } else if (cmmd.equals("about")) { about(); } else if (cmmd.equals("save")) { save(); stdout.println(done); } else if (cmmd.equals("load")) { load(); stdout.println(done); } else if (cmmd.equals("new")) { newModel(); stdout.println(done); } else if (cmmd.equals("save as")) { saveChanges(); model.setUnsavedChanges(true); stdout.println(done); } else { // an application-specific command? result = model.execute(cmmd); stdout.println(result); } } catch(AppError exp) { more = handle(exp); } catch (IOException exp) { stderr.println("IO error, " + exp); } catch (Exception exp) { stderr.println("Serious error, " + exp); more = false; } } // while stdout.println("bye"); } // controlLoop protected void help() { // app independent help – override if needed stdout.println("Console Help Menu:"); stdout.println(" about: displays application information"); stdout.println(" help: displays this message"); stdout.println(" load: load a saved model"); stdout.println(" new: create a new model"); stdout.println(" quit: terminate application"); stdout.println(" save: save model"); stdout.println(" save as: save model with a new name"); stdout.println("Application-specific Help Menu:"); model.help(stdout); } 236 protected void about() { stdout.println("Console Framework"); stdout.println("copyright (c) 2001, all rights reserved\n"); model.about(stdout); } protected boolean handle(AppError exp) { // override if needed stderr.println("Application error, " + exp); return true; } AppError.java AppError - base class for all application-specific errors. public class AppError extends Exception { private String gripe; public String toString() { return gripe; } public AppError(String g) { super(g); gripe = g; } public AppError() { super("unknown"); gripe = "unknown"; } } private void save() throws IOException { String fname = model.getFname(); if (fname == null) { // first save stdout.print("enter file name: "); stdout.flush(); // force the write fname = stdin.readLine(); model.setFname(fname); } if (model.getUnsavedChanges()) { ObjectOutputStream obstream = new ObjectOutputStream( new FileOutputStream(fname)); model.setUnsavedChanges(false); obstream.writeObject(model); obstream.flush(); obstream.close(); } } private void saveChanges() throws IOException { if (model.getUnsavedChanges()) { stdout.print("Save changes?(y/n): "); stdout.flush(); // force the write String response = stdin.readLine(); if (response.equals("y")) save(); else stdout.println("changes discarded"); } } 237 private void load() throws IOException, ClassNotFoundException { saveChanges(); stdout.print("enter file name: "); stdout.flush(); // force the write String fname = stdin.readLine(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fname)); model = (ConsoleModel) ois.readObject(); ois.close(); model.setUnsavedChanges(false); // necessary? model.setFname(fname); // necessary? } private void newModel() throws Exception { saveChanges(); stdout.print("enter class name: "); stdout.flush(); // force the write String cname = stdin.readLine(); Class c = Class.forName(cname); model = (ConsoleModel)c.newInstance(); } Developer must extend the generic ConsoleModel class to provide intended behavior. public class ConsoleModel implements Serializable { protected String unrecognized = "Unrecognized command: "; private String fname = null; private boolean unsavedChanges = false; public boolean getUnsavedChanges() { return unsavedChanges; } public void setUnsavedChanges(boolean flag) { unsavedChanges = flag; } public String getFname() { return fname; } public void setFname(String name) { fname = name; } public String execute(String cmmd) throws AppError { ... } public void help(PrintWriter str) { str.println( " Sorry, no application-specific help available"); } public void about(PrintWriter str) { str.println( "Sorry, no application-specific info available"); } // a test harness public static void main(String[] args) { Console cui = new Console(new ConsoleModel()); cui.controlLoop(); } } Override the execute() method to extend framework with app specific commands public String execute(String cmmd) throws AppError { if (cmmd.equals("throw")) { throw new AppError(unrecognized + cmmd); } StringTokenizer tokens = new StringTokenizer(cmmd); String result = "You entered " + 238 tokens.countTokens() + " token(s): "; while (tokens.hasMoreTokens()) { result += tokens.nextToken() + ' '; } return result; } Test Harness Demo -> help Console Help Menu: about: displays application information help: displays this message load: load a saved model new: create a new model quit: terminate application save: save model save as: save model with a new name Application-specific Help Menu: Sorry, no application-specific help available -> about Console Framework copyright (c) 2001, all rights reserved Sorry, no application-specific info available -> throw Application error, Unrecognized command: throw -> 46 + 19 * 23 You entered 5 token(s): 46 + 19 * 23 -> Example: Account Manager Continue the session begun with the ConsoleModel test harness, but change models dynamically using the new command: -> new enter class name: Account done -> help Console Help Menu: about: displays application information help: displays this message load: load a saved model new: create a new model quit: terminate application save: save model save as: save model with a new name Application-specific Help Menu: deposit AMT balance += AMT withdraw AMT balance -= AMT balance display balance -> -> deposit 250 done -> balance balance = $250.0 -> withdraw 900 Application error, insufficient funds 239 -> save enter file name: acct1 done -> -> deposit 50 done -> balance balance = $300.0 -> load Save changes?(y/n): n changes discarded enter file name: acct1 done -> balance balance = $250.0 -> Note how little work is involved for the customizer. public class Account extends ConsoleModel { private double balance = 0.0; public String execute(String cmmd) throws AppError { ... } public void help(PrintWriter str) { str.println(" deposit AMT balance += AMT"); str.println(" withdraw AMT balance -= AMT"); str.println(" balance display balance"); } // test harness: public static void main(String[] args) { Console cui = new Console(new Account()); cui.controlLoop(); } } public String execute(String cmmd) throws AppError { try { StringTokenizer tokens = new StringTokenizer(cmmd); String op = tokens.nextToken(); if (op.equals("deposit")) { double amt = Double.valueOf(tokens.nextToken()).doubleValue(); if (amt < 0) { throw new AppError("negative deposit: " + amt); } balance += amt; setUnsavedChanges(true); return "done"; } else if (op.equals("withdraw")) { double amt = Double.valueOf(tokens.nextToken()).doubleValue(); if (balance < amt) { throw new AppError("insufficient funds"); } balance -= amt; setUnsavedChanges(true); return "done"; } else if (op.equals("balance")) { return "balance = $" + balance; } else { 240 throw new AppError(unrecognized + cmmd); } } catch (NumberFormatException e) { throw new AppError("Amount must be a number"); } catch (NoSuchElementException e) { throw new AppError("usage: deposit/withdraw AMOUNT"); } } AFW 2.0: A GUI Application Framework with Multiple Views Design The framework provides an application window with a menu bar. Users could also place other controls in this window, which makes this window the application controller. Views are modeless dialog boxes that connect to the model. Implementation public class AppWindow extends MainJFrame implements ActionListener, MenuListener { protected AppModel model; private File currentDirectory = new File("."); protected JMenu fileMenu, editMenu, viewMenu, helpMenu; public JMenu makeMenu(String name, String[] items) { ... } public AppWindow(AppModel m) { ... } public void menuSelected(MenuEvent e) { ... } public void menuDeselected(MenuEvent e) { } public void menuCanceled(MenuEvent e) { } public void actionPerformed(ActionEvent e) { ... } protected void error(String gripe) { ... } protected void handleCreateView() throws Exception { ... } 241 protected void handleNew() throws Exception { ... } protected void handleLoad() throws IOException, ClassNotFoundException { ... } protected void handleSave() throws IOException { ... } protected void handleSaveAs() throws IOException { ... } protected void handleQuit() throws IOException { ... } protected void handleCopy() { ... } protected void handleCut() { ... } protected void handlePaste() { ... } protected void handleUndo() { ... } protected void handleRedo() { ... } protected void handleAbout() { ... } protected void handleHelp() { ... } private void saveChanges() throws IOException { ... } } public AppWindow(AppModel m) { model = m; setTitle(model.getClass().getName() + " Control"); JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); fileMenu = makeMenu("&File", new String[] {"&New", "&Save", "Sa&ve As", "&Load", "&Quit"}); editMenu = makeMenu("&Edit", new String[] {"&Copy", "Cu&t", "&Paste", null, "&Undo", "&Redo"}); viewMenu = makeMenu("&View", new String[] {"Create"}); helpMenu = makeMenu("&Help", new String[] {"&About", "&Help"}); menuBar.add(fileMenu); menuBar.add(editMenu); menuBar.add(viewMenu); menuBar.add(helpMenu); } public JMenu makeMenu(String name, String[] items) { // factory method JMenu result; int j = name.indexOf('&'); // used as the keyboard shortcut if ( j != -1) { char c = name.charAt(j + 1); String s = name.substring(0, j) + name.substring(j + 1); result = new JMenu(s); result.setMnemonic(c); } else { result = new JMenu(name); } for(int i = 0; i < items.length; i++) { if (items[i] == null) { result.addSeparator(); } else { j = items[i].indexOf('&'); JMenuItem item; if ( j != -1) { char c = items[i].charAt(j + 1); String s = items[i].substring(0, j) + items[i].substring(j + 1); item = new JMenuItem(s, items[i].charAt(j + 1)); item.setAccelerator( 242 KeyStroke.getKeyStroke(c, InputEvent.CTRL_MASK)); } else { // no accelerator or shortcut key item = new JMenuItem(items[i]); } item.addActionListener(this); result.add(item); } result.addMenuListener(this); } return result; } public void menuSelected(MenuEvent e) { int j = editMenu.getItemCount(); for(int i = 0; i < j; i++) { JMenuItem mi = editMenu.getItem(i); String arg2 = null; if (mi != null) arg2 = mi.getActionCommand(); if (arg2 != null && (arg2.equals("Copy") || arg2.equals("Cut") || arg2.equals("Paste"))) { mi.setEnabled(false); // disable menu item } } } public void actionPerformed(ActionEvent e) { // it would be better to provide a handler for each menu item // rather than a multiway switch; override these handle methods try { if (e.getSource() instanceof JMenuItem) { String arg = e.getActionCommand(); if (arg.equals("New")) handleNew(); else if (arg.equals("Load")) handleLoad(); else if (arg.equals("Save")) handleSave(); else if (arg.equals("Save As")) handleSaveAs(); else if (arg.equals("Quit")) handleQuit(); else if (arg.equals("Copy")) handleCopy(); else if (arg.equals("Cut")) handleCut(); else if (arg.equals("Paste")) handlePaste(); else if (arg.equals("Undo")) handleUndo(); else if (arg.equals("Redo")) handleRedo(); else if (arg.equals("Create")) handleCreateView(); else if (arg.equals("About")) handleAbout(); else if (arg.equals("Help")) handleHelp(); } repaint(); // make the menu disappear } catch (Exception x) { error("Exception thrown: " + x); } } protected void error(String gripe) { JOptionPane.showMessageDialog(null, gripe, "OOPS!", JOptionPane.ERROR_MESSAGE); } 243 private void saveChanges() throws IOException { if (model.getUnsavedChanges()) { int response = JOptionPane.showConfirmDialog( null, "Save changes?", "Save Changes?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (response == 0) handleSave(); } } protected void handleNew() throws Exception { saveChanges(); String cname = (String) JOptionPane.showInputDialog(null, "Enter class name", "New Model", JOptionPane.QUESTION_MESSAGE); if (cname != null) { Class c = Class.forName(cname); model = (AppModel)c.newInstance(); setTitle(model.getClass().getName() + " Control"); } } protected void handleLoad() throws IOException, ClassNotFoundException { saveChanges(); JFileChooser fd = new JFileChooser(); fd.setCurrentDirectory(currentDirectory); fd.showOpenDialog(this); currentDirectory = fd.getCurrentDirectory(); File ff = fd.getSelectedFile(); String fname = null; if (ff != null) fname = ff.getName(); if (fname != null) { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fname)); model = (AppModel) ois.readObject(); ois.close(); model.setUnsavedChanges(false); // necessary? model.setFname(fname); // necessary? } } protected void handleSave() throws IOException { String fname = model.getFname(); if (fname == null) { // first save JFileChooser fd = new JFileChooser(); fd.setCurrentDirectory(currentDirectory); fd.showSaveDialog(this); currentDirectory = fd.getCurrentDirectory(); File ff = fd.getSelectedFile(); if (ff != null) fname = ff.getName(); else fname = null; model.setFname(fname); 244 } if (fname != null && model.getUnsavedChanges()) { ObjectOutputStream obstream = new ObjectOutputStream( new FileOutputStream(fname)); model.setUnsavedChanges(false); obstream.writeObject(model); obstream.flush(); obstream.close(); } } protected void handleSaveAs() throws IOException { saveChanges(); model.setUnsavedChanges(true); model.setFname(null); handleSave(); } protected void handleQuit() throws IOException { saveChanges(); System.exit(0); } protected void handleCopy() { // override if functionality is needed error("Sorry, this item isn't implemented"); } protected void handleAbout() { JOptionPane.showMessageDialog(null, new String[] {"Application Framework", "Copyright(c) 2001", "All rights reserved"}, "About", JOptionPane.INFORMATION_MESSAGE); } protected void handleHelp() { error("Sorry, this item isn't implemented"); } protected void handleCreateView() throws Exception { AppView av = null; String cname = (String) JOptionPane.showInputDialog(null, "Enter class name", "New Model", JOptionPane.QUESTION_MESSAGE); if (cname != null) { Class c = Class.forName(cname); Constructor cons = c.getConstructor( new Class[] {this.getClass(), model.getClass()}); av = (AppView) cons.newInstance(new Object[] {this, model}); } av.show(); } A more usable View menu would list each known type of view as a menu item. In this fashion users wouldn't need to remember the names of the types of views. 245 abstract public class AppView extends JDialog implements Observer { protected AppModel model; protected AppWindow window; public AppView(AppWindow parent, AppModel m) { super(parent, m.getClass().getName() + " View", false); window = parent; // necessary? setSize(250, 250); model = m; model.addObserver(this); addWindowListener(new Terminator()); } private class Terminator extends WindowAdapter { public void WindowClosing(WindowEvent e) { setVisible(false); AppWindow ap = (AppWindow)getOwner(); //ap.remView(this); model.deleteObserver(AppView.this); } } } public class AppModel extends Observable implements Serializable { private String fname = null; private boolean unsavedChanges = false; public boolean getUnsavedChanges() { return unsavedChanges; } public void setUnsavedChanges(boolean flag) { unsavedChanges = flag; } public String getFname() { return fname; } public void setFname(String name) { fname = name; } public String help(PrintWriter str) { return "Sorry, no application-specific help available"; } public String about(PrintWriter str) { return "Sorry, no application-specific info available"; } // a test harness public static void main(String[] args) { AppWindow w = new AppWindow(new AppModel()); w.setVisible(true); } } Commands and Command Processors Instead of directly modifying the model in response to user inputs, controllers create commands and forward them to a centralized command processor. It is the job of the command processor to execute the commands. One advantage of this arrangement is that it makes it easy to provide several types of controllers that do the same thing. For example, most menu selections have corresponding tool bar buttons and hot key combinations that perform the same action. More advanced users prefer the tool bar or keyboard because they are faster to use, while beginners can make the GUI simpler by hiding the tool bar. We can avoid coding redundancy by having multiple controllers create the same type of command. Thus a menu selection and its corresponding tool bar button can create the same type of command object: 246 Command Processor [POSA] [Go4] Other Names Commands are also called actions and transactions. Problem A framework wishes to provide an undo/redo mechanism, and perhaps other features such as scheduling, concurrency, roll back, history mechanisms, or the ability to associate the same action to different types of controllers (e.g. for novice and advanced user). Solution Create an abstract base class for all commands in the framework. This base class specifies the interface all concrete command types must implement. Commands are created by controllers such as menu selections, buttons, text boxes, and consoles. The commands are forwarded to a command processor, which can store, execute, undo, redo, and schedule them. In the smart command variant of the pattern, commands know how to execute themselves. In the dumb command variant commands are simply tokens, and the command processor must know how to execute them. Scenario In the smart command variant, the commands are objects that know how to execute and undo themselves: 247 AFW 3.0: A GUI Application Framework with a Command Processor Design Assign command processor duties to the AppWindow. The AppWindow maintains two stacks of commands: the stack of commands that can be undone, and the stack of commands that can be redone. The AFW Command Hierarchy 248 Implementation The AppView and AppModel classes are unchanged from AFW 2.0. The Command Class public abstract class Command extends AbstractAction { protected AppModel model; protected AppWindow window; private boolean undoable; // e.g., Print is not undoable public ActionEvent ae = null; public boolean getUndoable() { return undoable; } public void setUndoable(boolean b) { undoable = b; } public Command(AppWindow w) { undoable = true; window = w; model = window.getModel(); } protected void error(String gripe) { // used? JOptionPane.showMessageDialog(null, gripe, "OOPS!", JOptionPane.ERROR_MESSAGE); } public void undo() throws AppError { throw new AppError("this command can't be undone"); } public void execute() throws AppError { throw new AppError( "Sorry, this command isn't implemented yet"); 249 } public void actionPerformed(ActionEvent evt) { ae = evt; window.execute(this); } } Swing components can fire abstract actions which provide their own actionPerformed() methods. We can think of abstract actions as a general case of our more specific notion of an AFW 3.0 command. To preserve compatibility with Swing's abstract actions, we subclass the AbstractAction class and provide an actionPerformed() method that asks the command processor to execute this command. The AppWindow Class (Controller) AFW 3.0 application windows are different from their AFW 2.0 ancestors in several ways. First, they provide the command processor machinery: an undo stack, a redo stack, and undo(), redo(), and execute() methods. Second, menu items create commands and pass them to the execute() method. Third, an AFW 3.0 application window has a toolbar with buttons that duplicate popular menu items. These buttons simply create the same command objects their menu item counterparts create. public class AppWindow extends MainJFrame implements MenuListener { protected AppModel model; private File currentDirectory = new File("."); protected JMenu fileMenu, editMenu, viewMenu, helpMenu; private Stack undo = new Stack(); private Stack redo = new Stack(); public AppWindow(AppModel m) { ... } protected void error(String gripe) { ... } public AppModel getModel() { return model; } // utilities for making menus & toolbars: public JMenu makeMenu(String name, Command[] actions) { ... } public JToolBar makeToolBar(Command[] actions) { ... } // command processor methods: public void undo() { ... } public void redo() { ... } public void execute(Command c) { ... } // MenuListener methods: public void menuSelected(MenuEvent e) { ... } public void menuDeselected(MenuEvent e) { } public void menuCanceled(MenuEvent e) { } } public AppWindow(AppModel m) { model = m; setTitle(model.getClass().getName() + " Control"); JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); Command[] fileCmmds = new Command[] { new FileNewCommand(this), new FileSaveCommand(this, false), 250 new FileSaveCommand(this, true), new FileLoadCommand(this), new FileQuitCommand(this) }; Command[] editCommands = new Command[] { new EditCommand(this, "Copy"), new EditCommand(this, "Cut"), new EditCommand(this, "Paste"), null, new EditUndoRedoCommand(this, true), new EditUndoRedoCommand(this, false) }; Command[] helpCommands = new Command[] { new AboutCommand(this), new HelpCommand(this, "help") }; Command[] viewCommands = new Command[] { new ViewCommand(this) }; Command[] toolBarCommands = new Command[] { new FileSaveCommand(this, false), new FileLoadCommand(this), null, new EditUndoRedoCommand(this, true), new EditUndoRedoCommand(this, false) }; fileMenu = makeMenu("&File", editMenu = makeMenu("&Edit", viewMenu = makeMenu("&View", helpMenu = makeMenu("&Help", menuBar.add(fileMenu); menuBar.add(editMenu); menuBar.add(viewMenu); menuBar.add(helpMenu); fileCmmds); editCommands); viewCommands); helpCommands); JToolBar toolBar = makeToolBar(toolBarCommands); Container pane = getContentPane(); pane.setLayout(new BorderLayout()); pane.add(toolBar, BorderLayout.NORTH); } // AppWindow() public JMenu makeMenu(String name, Command[] actions) { JMenu result = null; String s = name; int j = name.indexOf('&'); if ( j != -1) { char c = name.charAt(j + 1); s = name.substring(0, j) + name.substring(j + 1); result = new JMenu(s); result.setMnemonic(c); } else { result = new JMenu(s); } for(int i = 0; i < actions.length; i++) { 251 if (actions[i] == null) { result.addSeparator(); } else { result.add(actions[i]); } } return result; } public JToolBar makeToolBar(Command[] actions) { // Toolbar Factory Method JToolBar result = new JToolBar(JToolBar.HORIZONTAL); result.setFloatable(true); for(int i = 0; i < actions.length; i++) { if (actions[i] == null) { result.addSeparator(); } else { result.add(actions[i]); } } return result; } Both factory methods work because our Command class extends the AbstractAction class. public void undo() { try { Command cmmd = (Command)undo.pop(); cmmd.undo(); redo.push(cmmd); } catch(Exception e) { error(e.toString()); } } public void redo() { try { Command cmmd = (Command)redo.pop(); cmmd.execute(); undo.push(cmmd); } catch(AppError e) { error(e.toString()); } catch (Exception e) { error(e.toString()); } } public void execute(Command c) { try { c.execute(); if (c.getUndoable())undo.push(c); repaint(); } catch(AppError e) { error(e.toString()); } catch (Exception e) { error(e.toString()); } } 252 The commands fired by all File menu items extend the FileCommand base class. class FileCommand extends Command { static protected File currentDirectory = new File("."); public FileCommand(AppWindow w) { super(w); setUndoable(false); } protected void saveChanges() throws IOException { ... } protected void handleSave() throws IOException { ... } public void execute() { try { saveChanges(); } catch(Exception e) { throw new AppError(e.toString()); } } } protected void saveChanges() throws IOException { if (model.getUnsavedChanges()) { int response = JOptionPane.showConfirmDialog( null, "Save changes?", "Save Changes?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (response == 0) handleSave(); } } protected void handleSave() throws IOException { String fname = model.getFname(); if (fname == null) { // first save JFileChooser fd = new JFileChooser(); fd.setCurrentDirectory(currentDirectory); fd.showSaveDialog(null); currentDirectory = fd.getCurrentDirectory(); File ff = fd.getSelectedFile(); if (ff != null) fname = ff.getName(); else fname = null; model.setFname(fname); } if (fname != null && model.getUnsavedChanges()) { ObjectOutputStream obstream = new ObjectOutputStream( new FileOutputStream(fname)); model.setUnsavedChanges(false); obstream.writeObject(model); obstream.flush(); obstream.close(); } } class FileLoadCommand extends FileCommand { public FileLoadCommand(AppWindow w) { super(w); putValue(Action.NAME, "Load"); 253 putValue(Action.SHORT_DESCRIPTION, "Load model file"); setUndoable(false); } protected void handleLoad() throws IOException, ClassNotFoundException { saveChanges(); JFileChooser fd = new JFileChooser(); fd.setCurrentDirectory(currentDirectory); fd.showOpenDialog(null); currentDirectory = fd.getCurrentDirectory(); File ff = fd.getSelectedFile(); String fname = null; if (ff != null) fname = ff.getName(); if (fname != null) { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fname)); model = (AppModel) ois.readObject(); ois.close(); model.setUnsavedChanges(false); // necessary? model.setFname(fname); // necessary? } } public void execute() { try { System.out.println("Load selected"); handleLoad(); } catch (Exception e) { throw new AppError(e.toString()); } } } class FileSaveCommand extends FileCommand { private boolean saveAsFlag = false; public FileSaveCommand(AppWindow w, boolean sa) { super(w); saveAsFlag = sa; if (!saveAsFlag) { putValue(Action.NAME, "Save"); putValue(Action.SHORT_DESCRIPTION, "Save model to a file"); } else { putValue(Action.NAME, "Save As"); putValue(Action.SHORT_DESCRIPTION, "Save model to a new file"); } setUndoable(false); } public void execute() { try { if (!saveAsFlag) { handleSave(); } else { saveChanges(); model.setUnsavedChanges(true); model.setFname(null); handleSave(); } } catch (Exception e) { throw new AppError(e.toString()); } 254 } } class FileNewCommand extends FileCommand { public FileNewCommand(AppWindow w) { super(w); putValue(Action.NAME, "New"); putValue(Action.SHORT_DESCRIPTION, "Create a new model"); setUndoable(false); } protected void handleNew() throws Exception { saveChanges(); String cname = (String) JOptionPane.showInputDialog(null, "Enter class name", "New Model", JOptionPane.QUESTION_MESSAGE); if (cname != null) { Class c = Class.forName(cname); model = (AppModel)c.newInstance(); window.setTitle(model.getClass().getName() + " Control"); } } public void execute() { try { handleNew(); } catch (Exception e) { throw new AppError(e.toString()); } } } class FileQuitCommand extends FileCommand { public FileQuitCommand(AppWindow w) { super(w); putValue(Action.NAME, "Quit"); putValue(Action.SHORT_DESCRIPTION, "Quit application"); setUndoable(false); } public void execute() { try { saveChanges(); System.exit(0); } catch (Exception e) { throw new AppError(e.toString()); } } } The commands fired by all Edit menu items extend the EditCommand base class: class EditCommand extends Command { static protected Object clipBoard; public EditCommand(AppWindow w) { super(w); setUndoable(false); } public EditCommand(AppWindow w, String name) { super(w); 255 setUndoable(false); putValue(Action.NAME, name); } } class EditUndoRedoCommand extends EditCommand { private boolean redoCommand; public EditUndoRedoCommand(AppWindow w, boolean redoFlag) { super(w); redoCommand = redoFlag; if (!redoCommand) { putValue(Action.NAME, "Undo"); putValue(Action.SHORT_DESCRIPTION, "Undoes last undoable"); } else { putValue(Action.NAME, "Redo"); putValue(Action.SHORT_DESCRIPTION, "Redoes last undone command"); } setUndoable(false); } public void execute() { if (redoCommand) window.redo(); else window.undo(); } } class HelpCommand extends Command { public HelpCommand(AppWindow w) { super(w); setUndoable(false); } public HelpCommand(AppWindow w, String name) { super(w); setUndoable(false); putValue(Action.NAME, name); } } class AboutCommand extends HelpCommand { public AboutCommand(AppWindow w) { super(w); putValue(Action.NAME, "About"); putValue(Action.SHORT_DESCRIPTION, "About this application"); } protected void handleAbout() { JOptionPane.showMessageDialog(null, new String[] {"Application Framework", "Copyright(c) 2001", "All rights reserved"}, "About", JOptionPane.INFORMATION_MESSAGE); } public void execute() { handleAbout(); } } class ViewCommand extends Command { public ViewCommand(AppWindow w) { 256 super(w); putValue(Action.NAME, "Create"); putValue(Action.SHORT_DESCRIPTION, "Create a new view of the model"); setUndoable(false); } protected void handleCreateView() throws Exception { AppView av = null; String cname = (String) JOptionPane.showInputDialog(null, "Enter class name", "New Model", JOptionPane.QUESTION_MESSAGE); if (cname != null) { Class c = Class.forName(cname); Constructor cons = c.getConstructor( new Class[] {window.getClass(), model.getClass()}); av = (AppView) cons.newInstance( new Object[] {window, model}); } av.show(); } public void execute() throws AppError { try { handleCreateView(); } catch(Exception e) { throw new AppError(e.toString()); } } } Example: Polygon Viewer Customization of Version 3.0 of AFW 257 Some menu items are unimplemented 258 Two views of a five-sided polygon model have been created Clicking on either view with the left mouse button causes the number of sides to increment by one. This change is made in the underlying polygon model. All open views immediately redraw themselves using the updated number of sides: If the user attempts to quit the application or load a new model. 259 Design Implementation class Poly extends AppModel { private int sides = 5; public int getSides() { return sides; } public void incSides() { sides = Math.max((sides + 1) % 20, 3); setUnsavedChanges(true); setChanged(); notifyObservers(); clearChanged(); } public void decSides() { sides = sides - 1; if (sides < 3) sides = 20; setUnsavedChanges(true); setChanged(); notifyObservers(); clearChanged(); } // test harness: public static void main(String[] args) { AppWindow aw = new AppWindow(new Poly()); aw.setVisible(true); } } class IncSidesCommand extends Command { public IncSidesCommand(AppWindow w) { super(w); } public void execute() { Poly p = (Poly) model; p.incSides(); } 260 public void undo() { Poly p = (Poly) model; p.decSides(); } } public class PolyGraphView extends AppView { PolyPanel myPanel; public void update(Observable o, Object m) { myPanel.repaint(); } class PolyPanel extends JPanel { ... } class MouseHandler extends MouseAdapter { public void mouseClicked(MouseEvent e) { window.execute(new IncSidesCommand(window)); } } public PolyGraphView(AppWindow aw, Poly p) { super(aw, p); Container contentPane = getContentPane(); myPanel = new PolyPanel(); contentPane.add(myPanel); addMouseListener(new MouseHandler()); } } class PolyPanel extends JPanel { public void paintComponent(Graphics g) { super.paintComponent(g); Poly pol = (Poly) PolyGraphView.this.model; int sides = pol.getSides(); int red = 255; int blue = 175; int green = 0; g.setColor(new Color(red, green, blue)); // calculate client area dimensions: Dimension d = getSize(); Insets in = getInsets(); int clientWidth = d.width - in.right - in.left; int clientHeight = d.height - in.bottom - in.top; int xUpperLeft = in.right; int yUpperLeft = in.top; int xLowerRight = xUpperLeft + clientWidth; int yLowerRight = yUpperLeft + clientHeight; int xCenter = xUpperLeft + clientWidth/2; int yCenter = yUpperLeft + clientHeight/2; // draw polygon: Polygon p = new Polygon(); double radius = Math.min(clientHeight, clientWidth)/2; double angle = 2 * Math.PI/sides; for(int i = 0; i < sides; i++) p.addPoint( (int)(xCenter + radius * Math.cos(i * angle)), (int)(yCenter + radius * Math.sin(i * angle))); g.drawPolygon(p); // draw "sides = ..." string g.setColor(Color.black); String s = "# sides = " + sides; 261 FontMetrics fm = g.getFontMetrics(); int w = fm.stringWidth(s); int h = fm.getHeight(); g.drawString(s, xCenter - w/2, yCenter - h/2); } } // paintComponent() Mementos Memento [Go4] Other Names Token Problem The command processor pattern assumes commands know how to undo themselves. Using this pattern in a framework can mean a lot of work for customizers who must provide implementations of undo() in each of their concrete command classes. Alternatively, the framework's command processor or command base class could simply save the non re-computable state of the model before each command is executed. If the command is undone, then the former state of the model can easily be restored. Unfortunately, the internal state of a model is usually private. Allowing the framework to access this data would violate encapsulation. Solution A memento is an object created by a model that encapsulates some or all of its internal state at a given moment in time. The memento can be stored inside of a command or command processor without allowing access to its encapsulated data. The object that stores the memento is called a caretaker. The undo() function simply passes the memento back to the model, where the encapsulated data is extracted and used to restore the model's state to what it was at the time the memento was created. Static Structure When a command is executed, it first asks its model to make a memento encapsulating its state information. Although the command has no idea what's inside the memento, it keeps the memento until the command is undone: 262 Resource Managers What would happen, for example, if a program created a fake desktop, modified a database while another program was querying it, or created a run away thread that couldn't be interrupted? Resource Manager [ROG] Other Names Object manager, lifecycle manager Problem In some situations we may need to hide from clients the details how certain resources are allocated, deallocated, manipulated, serialized, and deserialized. In general, we may need to control client access to these objects. Solution A resource manager is responsible for allocating, deallocating, manipulating, tracking, serializing, and deserializing certain types of resource objects. A resource manager adds a layer of indirection between clients and resources. This layer may be used to detect illegal or risky client requests. In addition, the resource manager may provide operations that can be applied to all instances of the resource such as "save all", statistical information such as "get count", or meta-level information such as "get properties". Static Structure 263 class Manager { // singleton public int open() { ... } // resource factory method public bool close(int i) { ... } public bool serviceA(int i) { ... } public bool serviceB(int i) { ... } // etc. private Map open = new Hashtable(); // all instances of open // resources private bool authorized(...); // various parameters } bool serviceA(int i) { if (!authorized(...)) return false; // fail Resource r = (Resource)open.get(Integer(i)); r.serviceA(); return true; // success } Authorization can have a variety of meanings: Does the requested resource exist? Does the client have access rights to it? Is it currently available? Is the proposed operation legal? An operating system provides resource managers for most classes of system resources: 264 Integrating Patterns in the Design of a Hierarchical File System (reference: PATTERN HATCHING Design Patterns Applied J. Vlissides, Addison-Wesley, 1998) Four main benefits of patterns: 1. They capture expertise and make it accessible to non-experts. 2. Their names collectively form a vocabulary that helps developers communicate better. 3. They help people understand a system more quickly when it is documented with the patterns it uses. 4. They facilitate restructuring a system whether or not it was designed with patterns in mind. Goals for the system: Provide code for obtaining name of the files/directories Treat files and directories in a uniform fashion. Provide for extensions without major revisions (easy to extend the system) 265 Application of the Composite Pattern Classes that immediately stand out: File Directory We should provide a common interface for files/directories Note that directories aggregate files (this property dictates the use of an aggregation variable in the Directory class) 266 class Node { public: // declare common interface here protected: Node(); Node(const Node&); }; class File : public Node { public: File(); // redeclare common interface here }; class Directory : public Node { public: Directory(); // redeclare common interface here private: list<Node*> _nodes; }; What is in the common interface? names and sizes What do we do with operations that don't apply to both? e.g. retrieve a list of files: virtual Node* getChild(int n); // get the nth child of the current Node; this child can be either a File or // Directory. // What if we try to get the child of a child? n->getChild(i) -> getChild(j) // Since getChild returns a Node it must be defined in the common base // class which means in order for the cascaded getChild to work we need to // downcast the result of n->getChild(i) to a Directory. // But, we should avoid downcasting whenever possible since this will add // complexity to the code 267 The file system requirements indicate that the Composite Pattern is a good fit. Intent of the Composite Pattern: Compose objects into tree structures to represent part-whole hierarchies and give clients a uniform way of dealing with these objects whether they are internal nodes or leaves. Applicability Section - use Composite when you want to represent part-whole hierarchies of objects. you want clients to be able to ignore the difference between compositions of objects and individual objects. Clients will treat all objects in the composite structure uniformly. Component operation() getChild(int) Leaf Composite operation() operation() getChild(int) children Notes: Classes in italics are abstract classes. The diamond indicates that Composite aggregates its child instances (it owns them deleting a Composite will cause the deletion of its children) The circle at the arrowhead indicates the Composite aggregates one or more Components. 268 Directory Add new kids (files/directories): virtual void adopt(Node* child) Client creates Node; directory adopts kid ==> owns kid ==> if dir deleted, all kids are deleted When a kid is given up it relinquishes ownership: virtual void orphan(Node* child) Include a mkdir command e.g. mkdir newSubDir mkdir subDirA/subDirB/newSubDir subDirA and subDirB must already exist 269 mkdir version 1 - problems: void Client::mkdir( Directory* current, const string& path) { string subPath = subpath(path); if (subPath.empty()) { current->adopt(new Directory(path)); } else { string name = head(path); Node* child = find(name, current); if (child) { mkdir(child, subpath); // compilation error: see note 2 } else { cerr << name << " nonexistent." << endl; } } } Node* Client::find( const string& name, Directory* current) { Node* child = 0; for (int i=0; child = current->getChild(i); ++i) { if (name == child->getName()) { return child; } } return 0; } Notes: 1. head and subpath are string manipulation routines that extract the first name in a path and the remaining names, respectively. 2. We have a problem since child should be a Directory 3. find must return a Node* since it can yield a file or a directory 270 mkdir version 2 - simplification via uniform treatment: We can simplify the coding by treating files and directories in a uniform fashion - include adopt and orphan functions in the Node class, as well as in the Directory class: virtual void Node::adopt(Node* child) { cerr << getName() << " is not a direcotry." << endl; } virtual void Node::orphan(Node* child) { cerr << child->getName() << " not found." << endl; } virtual void Directory::adopt(Node* child) { ... } virtual void Directory::orphan(Node* child) { ... } Now, we can modify the heading of mkdir and find to: void Client::mkdir( Node* current, const string& path) { Node* Client::find( const string& name, Node* current) { 271 mkdir version 3 - use of downcasting: If we didn't include adopt and orphan in the Node class we would have to resort to downcasting: void Client::mkdir( Directory* current, const string& path) { string subPath = subpath(path); if (subPath.empty()) { current->adopt(new Directory(path)); } else { string name = head(path); Node* node = find(name, current); if (node) { Directory* child = dynamic_cast<Directory*>(node); if (child) { mkdir(child,subPath); } else { cerr << getName()<< " is not a directory." <<endl; } } else { cerr << name << " nonexistent." << endl; } } } Note that this non-uniform treatment of adopt and orphan forces us to use downcasting, which produces more complex code with a new control path! 272 Adding Symbolic Links: Proxy Pattern Similar to Unix Symbolic Links Provides references to other nodes Deleting links do not delete the referenced nodes Contains there own access rights which might be different from the referenced node Behave just like nodes (i.e. it's transparent whether operations are done on links or other types of nodes - e.g. editing, etc.) Which pattern is appropriate for adding Symbolic Links? Refer to the Design Patterns text page 30 - Structural Patterns: Structural Design Patterns Adapter Bridge Composite Decorator Facade Flyweight Proxy Aspect(s) That Can Vary interface to an object implementation of an object structure and composition of an object responsibilities of an object without subclassing interface to a subsystem storage costs of objects how an object is accessed; its location The Proxy pattern seems like a close fit. Refer to the Intent of the Proxy pattern (page 207): Provide a surrogate or placeholder for another object to control access to it. Refer to the Applicability of Proxy (page 208) A protection proxy controls access to the original object. Protection proxies are useful when objects should have different access rights. 273 Refer to its structure (page 209) Subject Client Request() ... ... RealSubject realSubject Request() ... Proxy Request() ... realSubject->Request() Notes: Node is the Subject RealSubject is either File or Directory - links can refer to either Refer to the Participants of Proxy: maintains a reference that lets the proxy access the real subject. Proxy may refer to a Subject if the RealSubject and Subject interfaces are the same. In our case, the Subject and RealSubject are both Nodes 274 File System Class Structure with Composite and Proxy Node Proxy pattern subject getName() getProtection() streamIn(istream ) streamOut(ostrea m) getChild(int) adopt(Node) orphan(Node) Link File streamIn(istream) streamIn(istream) streamOut(ostrea m) getSubject() Directory streamIn(istream) streamOut(ostrea streamOut(ostrea m) m) getChild(int) adopt(Node) orphan(Node) Composite pattern 275 children Visitor Pattern The Node class is growing ==> as we add new file system features we will keep expanding the Node class and will be forced to continually debug the class. Nodes should implement a simple, coherent Node interface. What if we want to add new operations such as cat, ls, etc. Let's move these operations outside of the Node class so it remains fixed and we don't have to continually remove new bugs. void Client::cat (Node* node) { Link* l; if (dynamic_cast<File*>(node)) { node->streamOut(cout); // stream out contents } else if (dynamic_cast<Directory*>(node)) { cerr << "Can't cat a directory." << endl; } else if (l = dynamic_cast<Link*>(node)) { cat(l->getSubject()); // cat the link's subject } } Note that again, we are using dynamic_casts, which add complexity. This strategy of fixing the structure classes and providing for extensions on operations calls for the Visitor pattern. Refer to the Intent of the Visitor pattern (page 331): Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates. An example of the use of Visitor is drawn from the compiler project discussed in CSC 413. An Abstract Syntax Tree (AST) is used as an internal representation of user programs processed by the compiler. There are several operations that must be performed on the AST such as typechecking, printing for debug purposes, code generation, code optimization. There may be more operations to be performed on the AST in practice. It would be very messy to continually update the AST each time we desire to perform new operations on it. The use of the Visitor pattern dictates the separation of the AST processors from the AST itself. This scheme simplifies the design of the AST, as well as clarifying the specifications of each of the operations in separate, distinct pieces of code. 276 The Visitor pattern dictates that we add an accept function to the Node class to accept visitors and then add appropriate visitor functions within each class that specifies the operation to perform. Visitor Code With No Common Operation Behavior class Visitor { public: virtual ~Visitor() {} virtual void visit(File*) = 0; // no operation in common; pure virtual // visitors must indicate what it means to visit a File virtual void visit(Directory*) = 0; virtual void visit(Link*) = 0; protected: Visitor(); Visitor(const Visitor&); } We will subclass Visitor for each type of Visitor operation. Two examples follow: class CatVisitor : public Visitor{ // implement the cat operation public: CatVisitor(); ~CatVisitor() {} void visit(File* f); void visit(Directory* f); void visit(Link* f); } CatVisitor:: visit(File* f) { f->streamOut(cout); } CatVisitor:: visit(Directory* f) { cerr << "Can't cat a directory." << endl; } CatVisitor:: visit(Link* f) { f->getSubject()->accept(*this); } 277 class SuffixPrinterVisitor : public Visitor { // print appropriate suffixes for a Node public: SuffixPrinterVisitor (); ~SuffixPrinterVisitor() {} void visit(File* f) { }; void visit(Directory* f) { cout << "/"; }; void visit(Link* f) { cout << "@; }; } Now, we can define a client ls operation: void Client::ls (Node* n) { SuffixPrinterVisitor suffixPrinter; Node* child; for (int i = 0; child = n->getChild(i); ++i) { cout << child->getName(); child->accept(suffixPrinter); cout << endl; } } 278 Visitor Code With Common Operation Behavior class Visitor { public: virtual ~Visitor() {} virtual void visit(File*) { // common default behavior for File visiting } virtual void visit(Directory*) { // common default behavior for Directory visiting } virtual void visit(Link*) { // common default behavior for Link visiting } protected: Visitor(); Visitor(const Visitor&); } 279 Modifications to Code Using Visitor Recall the previous version of cat with Downcasting complexity: void Client::cat (Node* node) { Link* l; if (dynamic_cast<File*>(node)) { node->streamOut(cout); // stream out contents } else if (dynamic_cast<Directory*>(node)) { cerr << "Can't cat a directory." << endl; } else if (l = dynamic_cast<Link*>(node)) { cat(l->getSubject()); // cat the link's subject } } Revised version of cat: void Client::cat (Node* node) { node->accept(CatVisitor); } Modification to the Node class - add the following function: virtual void Node::accept(Visitor&) = 0; Modification to the File class - add the following function: void File::accept(Visitor& v) { v.visit(this); } Modification to the Directory class - add the following function: void Directory::accept(Visitor& v) { v.visit(this); } Modification to the Link class - add the following function: void Link::accept(Visitor& v) { v.visit(this); } These modifications are quite simple and avoid downcasting complexities. Visitor Benefit We can modify the file system by adding code rather than modifying existing code and possibly breaking that old code. 280 Deleting Files and Directories within a Single User Protection Framework (e.g. single user on a PC, not a multi-user system under e.g. UNIX) - the Template Pattern Recall the diagram presented earlier: Node subject getName() getProtection() streamIn(istream) streamOut(ostream) getChild(int) adopt(Node) orphan(Node) Directory Link File streamIn(istream) streamIn(istream) streamOut(ostrea m) getSubject() streamOut(ostream ) streamIn(istream) streamOut(ostream) getChild(int) adopt(Node) orphan(Node) Consider the following functions in the Node class: const Protection& getProtection(); void setProtection(const Protection&); // we're adding this to the current phase void setName(const string&); 281 children The following table defines the protection mechanisms: Protection writable unwritable readable unreadable Operation notes on Nodes clients can't delete nodes like other objects; can't change attributes/structure; no access to setName, streamIn, adopt, orphan no streamOut; no access to kids via getChild More notes on the unwritable protection: protect the destructor can only delete within the Node class hierarchy, not by a client this suggests the use of a private destructor which would, in turn, disallow subclassing disallows local Node objects (those on the stack) since the destructor cannot be called destructor can only be called within the Node hierarchy (this provides good control of destruction) if we use a friend to destroy Nodes then we would break encapsulation since the rest of the Node's operations would be exposed, too Use static member destruction so the destruction is still in the Node hierarchy We could have: static Node::destroy(Node*); // this function will check that the Node is writable and then destroy it if so Note that Node destruction involves two fundamental operations which must be performed in a set order: 1. Checking whether the protection mechanism allows the destruction (is it writable or ???) and 2. Actually performing the destruction operation Subclasses of Node can override the checking operation, as well as the destruction operation. This suggests the use of the Template Method Pattern. 282 Refer to the Intent of the Template Method Pattern (page 325): Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure. Refer to the Applicability of the Template Method Pattern (page 326): The Template Method pattern should be used to implement the invariant parts of an algorithm once and leave it up to subclasses to implement the behavior that can vary. void Node::destroy (Node* node) { // note the fixed template of control if (node->isWritable()) { delete node; } else { node->doWarning(undeleteableWarning); } } class Node { public: static void destroy(Node*); // ... protected: virtual ~Node(); // control Node destruction virtual bool isWritable() = 0; virtual void doWarning(int) = 0; // the primitive functions isWritable and doWarning must be overridden // ... }; Notes: The invariant algorithm is given in Node::destroy Subclasses will define their own version of the primitives isWritable and doWarning they're protected in the Node class 283 Similar to destroy, streamOut should be a Template Method. Note that previously, it was a pure virtual in the Node class. Now, we'll define the template in the Node class. void Node::streamOut (ostream& out) { if (isReadable()) { doStreamOut(out); } else { doWarning(unreadableWarning); } } Clearly, the Template Method primitives in this case are doStreamOut and doWarning. 284 Summary of Design Patterns Used in the File System Design Composite: Component Proxy: Subject TemplateMethod:AbstractClass Visitor: Element Link Leaf Proxy TemplateMethod: ConcreteClass Node File Director y Leaf Proxy: RealSubject TemplateMethod : Composite Proxy: RealSubject TemplateMethod : ConcreteClass ConcreteClass NodeVisit or CatVisitor Visitor ConcreteVisitor 285 Active and Distributed Objects (Appendix 8) Multi-Threading Concurrent programming-- doing several tasks in parallel, then putting the pieces together at the end to produce a final answer. Single processor system – time to switch programs/tasks/processes Threads - light-weight processes; isn't a full fledged program with its own data and control information. Implementing and Scheduling Threads In a simplified operating system, a thread is always in one of at least four states: Ready (waiting for the CPU in the ready queue), Running (using the CPU), Blocked (waiting for input), or Terminated. We can represent these states, the transitions between them, and the actions that cause transition to occur using a state diagram: Scheduler – determines which thread gets to use the cpu. Running thread gives up control of a processor: 1. When its task is complete 2. When it blocks for i/o 3. When an o/s preempts a thread that attempts to starve its siblings by consuming large slices of processor time without releasing it to the others. 4. When cooperative threads voluntarily release processor control, either by autotransitioning back to their Ready state, or by auto-transitioning to their Blocked state for a specific amount of time (sleeping). 286 Inter-Thread Communication Message passing - parent thread acts like a post office for child threads. Promotes decoupling among the child threads; but, all messages must pass through the broker. Must be limit on the amount of information that can be sent in a single message. Shared memory – use global memory/data structures for holding communicated information; Snchronizing issues arise. Programmers cannot predict order of execution; but, o/s provides synchronization mechanisms: objects that work like latches and locks. If we equip mailbox M with a locked latch, L, then if Thread B doesn't have a "key", it must wait next to the mailbox for the latch to be unlocked. Of course this will only happen after thread A, which does have a key, puts its message inside M. Active Objects Passive object does nothing unless a client calls one of its member functions. Active object: "owns" a thread of control. It can use its thread to execute a control loop that perpetually searches for work to be done: 1. 2. 3. 4. 5. Inspect environment Compare environment state to goal state If same, quit Update environment Repeat The Master-Slave Design Pattern Master-Slave [POSA] Problem An identical computation must be performed many times, but with different inputs and context. The results of these computations may need to be accumulated. If possible, we would like to take advantage of multiple processors. Solution A master thread creates multiple slave threads. Each slave performs a variation of the computation, reports the result to the master, then terminates. The master accumulates the results. 287 Each slave performs some task, and then reports its result back to the master. The master accumulates results and produces a final result. Java Threads Java's Thread class makes multithreading of the underlying host operating system available to programmers. class Robot extends Thread { public void run() { // overridden from Thread class while(true) { // run around doing stuff forever } } // etc. } public class Factory { public static void main(String[] args) { for(int i = 0; i < 100; i++) { Robot r2d2 = new Robot(...); r2d2.start(); // launch thread; start calls run() } // etc. } } Alternative class Robot extends Device implements Runnable { private Thread runner = null; public void run() {...} public void start() { if (runner == null) { runner = new Thread(this); runner.start(); // calls run() above } } // etc. } 288 Scheduling Thread States Preemptive vs. Nonpreemptive Scheduling Java multithreading is implemented using the multithreading system provided by the host platform. Non-preemptive system: all runnable threads wait in a ready queue for the currently running thread to release the CPU Preemptive system: the CPU is allocated to a thread for a fixed time slice (e.g. one second). If a thread doesn't release the CPU before its time slice expires, it will be interrupted, sent back to the ready queue, and the CPU will be allocated to the "next" thread in the ready queue. The Java ready queue is a priority queue - threads are inserted according to their priority (110) Thread.getPriority() and Thread.setPriority() Cooperative Multitasking using yield() or sleep() Cooperative threads: use sleep/yield to share CPU use. This will mitigate the o/s specific scheduling schemes Example: Bouncing Balls In the bouncing ball applet each ball is animated by its own slave thread. New balls are created by mouse clicks. Keyboard inputs stop, suspend, and resume the balls: 289 import import import import java.awt.*; java.awt.event.*; java.util.*; // for Vector java.applet.*; public class Bounce extends Applet { // client area metrics protected int xUpperLeft, yUpperLeft, xLowerRight, yLowerRight; private Vector balls; private int ballDiam; private Random numberGen; private boolean paused; private void killAll() { showStatus("Killing all balls"); for(int i = 0; i < balls.size(); i++) { Ball b = (Ball)balls.elementAt(i); b.stop(); } balls.removeAllElements(); repaint(); } private void resumeAll() { if (paused) { showStatus("Resuming all balls"); 290 paused = false; for(int i = 0; i < balls.size(); i++) { Ball b = (Ball)balls.elementAt(i); if (!b.isAlive()) showStatus("This ball is dead!"); b.resume(); // b moves to ready queue } } } private void suspendAll() { if (!paused) { showStatus("Suspending all balls"); paused = true; for(int i = 0; i < balls.size(); i++) { Ball b = (Ball)balls.elementAt(i); b.suspend(); } } } public void start() { updateMetrics(); resumeAll(); } public void stop() { suspendAll(); } public void destroy() { killAll(); } public void init() { addMouseListener(new MouseEventHandler()); addKeyListener(new KeyEventHandler()); Object parent = getParent(); balls = new Vector(); ballDiam = 10; numberGen = new Random(); paused = false; } protected void updateMetrics() { Dimension d = getSize(); //size of drawing area of applet Insets in = getInsets(); int clientWidth = d.width - in.right - in.left; int clientHeight = d.height - in.bottom - in.top; xUpperLeft = in.right; yUpperLeft = in.top; xLowerRight = xUpperLeft + clientWidth; yLowerRight = yUpperLeft + clientHeight; } class KeyEventHandler extends KeyAdapter { public void keyTyped(KeyEvent e) { // int key = e.getKeyCode(); // always 0! char key = e.getKeyChar(); if (key == 'k') killAll(); else if (key == 'r') resumeAll(); else if (key == 's') suspendAll(); else showStatus("key pressed = " + key); } } class MouseEventHandler extends MouseAdapter { public void mouseClicked(MouseEvent e) { showStatus("New Ball"); Point p = e.getPoint(); 291 Ball b = new Ball(p.x, p.y); balls.addElement(b); b.start(); } public void mouseReleased(MouseEvent e) { showStatus(""); } } public void paint(Graphics g) { // use graphics context to paint for(int i = 0; i < balls.size(); i++) { Ball b = (Ball)balls.elementAt(i); g.setColor(b.color); g.fillOval(b.xc, b.yc, ballDiam, ballDiam); } } // slave thread class Ball extends Thread { public int xc, yc; private int dx, dy, oldxc, oldyc, red, green, blue; public Color color; public Ball(int x, int y) { xc = x; yc = y; dx = numGen.nextInt() % 7 - 3; dy = numGen.nextInt() % 7 - 3; int max = Thread.MAX_PRIORITY + Thread.MIN_PRIORITY + 1; int pri = (numGen.nextInt() % max) - Thread.MIN_PRIORITY; red = numberGen.nextInt() % 256; blue = numberGen.nextInt() % 256; green = numberGen.nextInt() % 256; color = new Color(red, green, blue); } private void move() { oldxc = xc; oldyc = yc; xc += dx; yc += dy; if (xc <= xUpperLeft || xLowerRight <= xc) dx = -dx; if (yc <= yUpperLeft || yLowerRight <= yc) dy = -dy; } public void run() { while(true) { move(); repaint(oldxc, oldyc, 2 * ballDiam, 2 * ballDiam); try { Thread.sleep(5); } catch(InterruptedException e) {} } } } } // Bounce HTML 292 <HTML> <TITLE> Bouncing Balls </TITLE> <BODY> Mouse click creates a ball. Press s to suspend, r to resume, and k to kill.<BR> <APPLET CODE="Bounce.class" WIDTH=300 HEIGHT=500> Sorry, your browser can't show applets. </APPLET> </BODY> </HTML> Producer-Consumer Problems Communication through shared memory: easy to set up in the Master-Slave architecture because all slaves share their master's heap and static memory segments. In this case thread A simply makes a modification to some global data structure. For example, the data structure might represent a mailbox where messages can be stored. Later, thread B simply examines the global data structure to see what thread A has done. For example, thread B might remove the message from the mailbox and read it. Problem. Suppose thread B checks the mailbox before thread A has had a chance to put the message inside? This may result in an error! Use synchronization mechanisms provided by the operating system. A synchronization mechanism allows one thread to lock the global data structure until it is finished. Other threads must wait for the data structure to be unlocked before they can access it. Producer-Consumer problems: master thread creates a global buffer and two slave threads. One slave is called the producer. The producer perpetually creates imaginary objects called widgets and places them in the global buffer. The other slave is called the consumer. The consumer repeatedly removes widgets from the buffer and consumes them: If the slaves aren't clever, the consumer slave may start to consume the last widget in the buffer and suspends itself, waiting for the producer to produce more widgets. At the same time, the producer slave adds a second widget to the buffer, failing to realize that the first widget is in the process of being consumed. If the producer only notifies the consumer when it adds a widget to the empty buffer, no notification is sent. The producer proceeds to fill the buffer with widgets. When the buffer is full, the producer suspends itself, waiting for the consumer to consume some widgets to make more room in the buffer. Of course at this point both the producer and consumer are suspended! Example: A Joint Checking Account Simulation Console application. consumer and producer worker threads will be equipped with pointers to a shared checking account (the buffer). The producer repeatedly deposits money in the joint account, while the consumer repeatedly withdraws money. 293 Implementation Account (version 1) class Account { // shared buffer private double balance = 0; public Account(double amt) { balance = amt; } public void withdraw(double amt) { System.out.println("... withdrawing $" + amt); double temp = balance; // simulate consumption time: try { Thread.sleep(10); } catch(InterruptedException e) {} if (amt <= balance) { temp -= amt; balance = temp; System.out.println("... balance $" + balance); } else { System.out.println("... sorry, insufficient funds"); } } public void deposit(double amt) { double temp = balance; System.out.println("depositing $" + amt); // simulate production time: try { Thread.sleep(12); } catch(InterruptedException e) {} temp += amt; balance = temp; System.out.println("balance $" + balance); } } Producer class Producer extends Thread { private Account account; public Producer(Account acct) { account = acct; } public void run() { for(int i = 0; i < 5; i++) { account.deposit(10); // deposits $10 294 } } } Consumer class Consumer extends Thread { private Account account; public Consumer(Account acct) { account = acct; } public void run() { for(int i = 0; i < 5; i++) { account.withdraw(5); // withdraws $5 } } } Master class PCMaster { // the master only ends after its slaves terminate public static void main(String[] args) { Account acct = new Account(100); Producer depositor = new Producer(acct); Consumer withdrawer = new Consumer(acct); withdrawer.start(); depositor.start(); } } Program Output C:\Pearce\JPOP\PC>java PCMaster ... withdrawing $5.0 depositing $10.0 ... balance $95.0 how did this happen?? ... withdrawing $5.0 balance $110.0 depositing $10.0 ... balance $90.0 ... withdrawing $5.0 balance $120.0 depositing $10.0 ... balance $85.0 ... withdrawing $5.0 ... balance $80.0 ... withdrawing $5.0 balance $130.0 depositing $10.0 ... balance $75.0 balance $140.0 depositing $10.0 balance $150.0 Synchronization There was $100 in the account initially, so the closing balance should have been $100 + $50 $25 = $125, not $150. What happened? 295 The consumer starts to withdraw $5. In the middle of the transaction, the producer interrupts and begins a $10 deposit. Now the consumer interrupts the producer to complete its transaction, leaving a balance of $95 in the shared account. But it's too late. The producer has already copied the $100 balance into a local variable and incremented it to $110. Monitors Any object that can control the number of threads that can call its methods is called a monitor. We can make a Java object a monitor by writing "synchronized" in front of any methods that access important member variables. Account (version 2) class Account { private double balance = 0; public Account(double amt) { balance = amt; } synchronized public void withdraw(double amt) { System.out.println("... withdrawing $" + amt); double temp = balance; // simulate consumption time: try { Thread.sleep(10); } catch(InterruptedException e) {} if (amt <= balance) { temp -= amt; balance = temp; System.out.println("... balance $" + balance); } else { System.out.println("... sorry, insufficient funds"); } } synchronized public void deposit(double amt) { double temp = balance; System.out.println("depositing $" + amt); // simulate production time: try { Thread.sleep(12); } catch(InterruptedException e) {} temp += amt; balance = temp; System.out.println("balance $" + balance); 296 } } Program Output Notice that the producer and consumer no longer interrupt each other: C:\Pearce\JPOP\PC>java PCMaster ... withdrawing $5.0 ... balance $95.0 depositing $10.0 balance $105.0 ... withdrawing $5.0 ... balance $100.0 depositing $10.0 balance $110.0 ... withdrawing $5.0 ... balance $105.0 depositing $10.0 balance $115.0 ... withdrawing $5.0 ... balance $110.0 depositing $10.0 balance $120.0 ... withdrawing $5.0 ... balance $115.0 depositing $10.0 balance $125.0 Improving Consumption Let’s try another experiment. Suppose the master created a joint account with an initial balance of $0. Here's the output produced: C:\Pearce\JPOP\PC>java PCMaster ... withdrawing $5.0 ... sorry, insufficient funds we don’t want to turn away customers! depositing $10.0 balance $10.0 ... withdrawing $5.0 ... balance $5.0 depositing $10.0 balance $15.0 ... withdrawing $5.0 ... balance $10.0 depositing $10.0 balance $20.0 ... withdrawing $5.0 ... balance $15.0 depositing $10.0 balance $25.0 ... withdrawing $5.0 ... balance $20.0 depositing $10.0 balance $30.0 Account (version 3) 297 When a thread calls wait() inside a monitor, the thread is blocked placed in the associated queue. It can only be unblocked if another thread calls notifyAll() inside the same monitor. This allows us to achieve more sophisticated forms of synchronization. class Account { private double balance = 0; public Account(double amt) { balance = amt; } synchronized public void withdraw(double amt) { System.out.println("... withdrawing $" + amt); while(balance < amt) try { wait(); } catch(InterruptedException e) {} double temp = balance; // simulate consumption time try { Thread.sleep(10); } catch(InterruptedException e) {} if (amt <= balance) { temp -= amt; balance = temp; System.out.println("... balance $" + balance); } else { System.out.println("... sorry, insufficient funds"); } } synchronized public void deposit(double amt) { double temp = balance; System.out.println("depositing $" + amt); // simulate production time try { Thread.sleep(12); } catch(InterruptedException e) {} temp += amt; balance = temp; System.out.println("balance $" + balance); notifyAll(); } } Program Output Now the consumer is never turned away hungry: C:\Pearce\JPOP\PC>java PCMaster depositing $10.0 balance $10.0 ... withdrawing $5.0 ... balance $5.0 depositing $10.0 balance $15.0 ... withdrawing $5.0 ... balance $10.0 depositing $10.0 balance $20.0 ... withdrawing $5.0 ... balance $15.0 depositing $10.0 balance $25.0 ... withdrawing $5.0 ... balance $20.0 298 depositing $10.0 balance $30.0 ... withdrawing $5.0 ... balance $25.0 Direct Communication Objects A and B on different machines: send messages across a network using a socket. Socket is a transceiver: combines a transmitter (an object that can be used to send or transmit messages) with a receiver (an object that can receive messages). Telephones, CB radios, and mailboxes are examples of transceivers. class Socket { public Socket(String host, int port) { ... } public InputStream getInputStream() { ... } public OutputStream getOutputStream() { ... } // etc. } 299 Example: A Date Client Most standard Internet servers- ftp servers, telnet servers, web servers, etc.- perpetually listen for clients at well known port numbers below 100. For example, most computers are equipped with a date server that listens for clients at port 13. When a connection is made, the date server sends the local date and time to the client. import java.util.*; import java.io.*; import java.net.*; public class DateClient { protected Socket sock; protected BufferedReader sockin; protected PrintWriter sockout; public DateClient(String host) { ... } String getDate() throws IOException { return sockin.readLine(); } public static void main(String[] args) { try { DateClient client = new DateClient("localhost"); System.out.println(client.getDate()); } catch (IOException ioe) { } } } public DateClient(String host) { try { Socket sock = new Socket(host, 13); sockin = new BufferedReader( new InputStreamReader( sock.getInputStream())); sockout = new PrintWriter( sock.getOutputStream(), true); } catch(UnknownHostException uhe) { System.err.println("unknown host " + uhe); System.exit(1); } catch(IOException ioe) { System.err.println("failed to create streams " + ioe); System.exit(1); } } 300 A Server Framework Design Implementation public abstract class Server { // Master protected ServerSocket mySocket; protected int myPort; protected InetAddress myAddr; // not used much public Server(int port) { ... } // abstract handler factory: abstract public RequestHandler makeHandler(Socket s); // listens for a request, then creates & starts a handler public void listen() { ... } } public Server(int port) { try { myPort = port; mySocket = new ServerSocket(myPort); myAddr = mySocket.getInetAddress(); } catch(IOException ioe) { System.err.println("Failed to create socket; " + ioe); System.exit(1); } // catch } public void listen() { try { while(true) { System.out.println("Server listening at " + myAddr); Socket request = mySocket.accept(); // blocks RequestHandler handler = makeHandler(request); handler.start(); } // while } catch(IOException ioe) { System.err.println("Failed to accept socket, " + ioe); System.exit(1); 301 } // catch } public abstract class RequestHandler extends Thread { // Slave protected Socket request; protected BufferedReader in; protected PrintWriter out; public RequestHandler(Socket s) { ... } public void run() { ... } public abstract boolean processRequest(); } public RequestHandler(Socket s) { request = s; try { in = new BufferedReader( new InputStreamReader( request.getInputStream())); out = new PrintWriter( request.getOutputStream(), true); } catch(IOException ioe) { System.err.println("failed to create streams; " + ioe); System.exit(1); } } public void run() { try { boolean more = true; // processing the request is dependent on the instantiation // of the framework while(more) more = processRequest(); request.close(); } catch (IOException ioe) { System.err.println("" + ioe); } } Example: A Command Server Framework class CommandHandler extends RequestHandler { public CommandHandler(Socket s) { super(s); } public boolean processRequest() { ... } // calls execute() String execute(String cmmd) throws IOException { if (cmmd.equals("quit")) throw new IOException("client quitting"); return "echo: " + cmmd; // for now } } public boolean processRequest() { boolean morePlease = true; try { System.out.println("processing a request"); String cmmd = in.readLine(); System.out.println("command = " + cmmd); String result = execute(cmmd); System.out.println("result = " + result); out.println(result); } catch(IOException ioe) { 302 System.err.println("" + ioe); morePlease = false; } return morePlease; } public class CommandServer extends Server { public static int COMMAND_PORT = 4242; public CommandServer() { super(COMMAND_PORT); } public RequestHandler makeHandler(Socket s) { return new CommandHandler(s); } public static void main(String[] args) { Server server = new CommandServer(); server.listen(); } } public class CommandClient { protected Socket sock; protected BufferedReader sockin; protected PrintWriter sockout; protected BufferedReader stdin; protected PrintWriter stdout; protected PrintWriter stderr; public CommandClient() { ... } public void controlLoop() { ... } public static void main(String[] args) { CommandClient client = new CommandClient("localhost"); client.controlLoop(); } } public CommandClient(String host) { try { Socket sock = new Socket(host, 4242); sockin = new BufferedReader( new InputStreamReader( sock.getInputStream())); sockout = new PrintWriter( sock.getOutputStream(), true); stdout = new PrintWriter( new BufferedWriter( new OutputStreamWriter(System.out)), true); stderr = new PrintWriter( new BufferedWriter( new OutputStreamWriter(System.out)), true); stdin = new BufferedReader( new InputStreamReader(System.in)); } catch(IOException ioe) { System.err.println("failed to create streams; " + ioe); System.exit(1); } } public void controlLoop() { boolean more = true; String cmmd, result; while(more) { try { 303 stdout.print("-> "); stdout.flush(); // force the write cmmd = stdin.readLine(); if (cmmd.equals("quit")) { more = false; } else { // an application-specific command? sockout.println(cmmd); result = sockin.readLine(); stdout.println(result); } } catch (Exception exp) { stderr.println("Serious error, " + exp); more = false; } } // while stdout.println("bye"); } Indirect Communication Two objects separated by a process boundary must communicate with each other through sockets. Using sockets requires some awareness of network protocols and addressing formats. Also, transmitting anything other than a string through a socket is difficult. However, this form of communication is used many places, e.g., ftp, telnet protocols. In the context of C++ objects, "face-to-face conversation" means invoking member functions. No special transceiver device is required, parameters and return values can be 304 arbitrary objects, and objects can be located using ordinary pointers rather than protocoldependent network addresses. Remote method invocation (RMI) combines the best of both worlds by allowing distributed objects to communicate by ordinary member function invocations. This illusion is created by introducing proxies- local representatives of remote objects- into the address space of each object. Instead of direct communication, distributed objects communicate indirectly through proxies, which hide the details of direct communication. Proxies Proxy [POSA], [Go4] Other Names Proxies are also called surrogates, handles, and wrappers. They are closely related in structure, but not purpose, to adapters and decorators. Problem 1. A server may provide the basic services needed by clients, but not administrative services such as security, synchronization, collecting usage statistics, and caching recent results. 2. Inter-process communication mechanisms can introduce platform dependencies into a program. They can also be difficult to program and they are not particularly object-oriented. Solution Instead of communicating directly with a server, a client communicates with a proxy object that implements the server's interface, hence is indistinguishable from the original server. The proxy can perform administrative functions before delegating the client's request to the real server or another proxy that performs additional administrative functions. Structure 305 The class diagram suggests that process or machine boundaries may exist between proxies. Proxies that run in the same process or on the same machine as the client are called client-side proxies, while proxies that run in the same process or on the same machine as the server are called server-side proxies. Scenario As in the decorator pattern, proxies can be chained together. 306 Examples of Client-Side Proxies A firewall proxy is essentially a filter that runs on the bridge that connects a company network to the Internet. It filters out client requests and server results that may be inconsistent with company policies A cache proxy is a client-side proxy that searches a local cache containing recently received results. If the search is successful, the result is returned to the client without the need of establishing a connection to a remote server. Otherwise, the client request is delegated to the server or another proxy. For example, most web browsers transparently submit requests for web pages to a cache proxy, which attempts to fetch the page from a local cache of recently downloaded web pages. Virtual proxies provide a partial result to a client while waiting for the real result to arrive from the server. For example, a web browser might display the text of a web page before the images have arrived, or a word processor might display empty rectangles where embedded objects occur. Examples of Server-Side Proxies Protection proxies can be used to control access to servers. For example, a protection proxy might be inserted between the CIA's web server and the Internet. It demands user identifications and passwords before it forwards client requests to the web server. A synchronization proxy uses techniques similar to the locks discussed earlier to control the number of clients that simultaneously access a server. For example, a file server may use a 307 synchronization proxy to insure that two clients don't attempt to write to the same file at the same time. High-volume servers run on multiple machines called server farms. A load balancing proxy is a server-side proxy that keeps track of the load on each server in a farm and delegates client requests to the least busy server. Counting proxies are server-side proxies that maintain usage statistics such as hit counters. Remote Proxies and Remote Method Invocation Communicating with remote objects through sockets is awkward. It is entirely different from communicating with local objects, where we can simply invoke a member function and wait for a return value. The socket adds an unwanted layer of indirection, it restricts us to sending and receiving strings, it exposes the underlying communication protocol, and it requires us to know the IP address or DNS name of the remote object. A remote proxy encapsulates the details of communicating with a remote object. This creates the illusion that no process boundary separates clients and servers. Clients communicate with servers by invoking the methods of a client-side remote proxy. Servers communicate with clients by returning values to server-side remote proxies. This is called Remote Method Invocation or RMI. << Refer to other notes on RMI for further discussion>> Conclusion We began this chapter with a discussion of threads, locks, and the Master-Slave design pattern. This was the architecture we subsequently used for our server framework, which we subsequently specialized into the command server framework. Our clients and servers communicated through awkward, transceiver-like devices called sockets. We were able to hide sockets inside remote proxies on both the server side (skeletons) and the client side (stubs). Remote proxies created the illusion that clients and servers were communicating using ordinary method call and return mechanisms. Remote method invocation completes the merger of object-oriented programming with clientserver programming, the two great programming paradigms of the 1990s. By adding a sophisticated persistence mechanism such as an object-oriented database, the location of an object (local, remote, or in storage) becomes irrelevant. 308 Documentation and Coding Standards Consider the following levels of documentation - each level serves a welldefined purpose: The Code - It should be readable so as not to need internal comments choose appropriate identifiers (don't abbreviate unless you are using a conventional abbreviation). The code should be self-documenting and easy to read (be careful of your spacing, etc.) Internal Comments - Minimize these comments since they distract the reader from understanding the code. These might be used to clarify conventional coding tricks or special cases or notes regarding constraints. DO NOT restate what is obvious in the code. Include important decisions and hints about future modifications and current problems. External Comments - These elaborate on special algorithms and other issues that aren't appropriate to document at the lower levels mentioned above User Manual - This indicates the functional specifications (constraints) of the system. Developer's Manual - This provides information necessary for people that need a more detailed view of the system including the system architecture. Advertising - Entice people to use the system For our purposes, we will focus on the first two levels of documentation described above - the code and internal comments. Since this documentation will be used by various people involved in the critical aspects of system development it will serve as the most valuable communication mechanism. As such, it should adhere to principles that have been developed over a long period of time for effective communication. It is not advisable for each 309 programmer to develop his/her own (unproven) styles. The most straightforward mechanism for describing the style of interest is to simply view examples of that style used by experts. Therefore, the Java String class as found in the VisualCafe class hierarchy is used for demonstration. The following sections of the source code are included to demonstrate appropriate coding standards. 310 Sample Coding Standards /* * @(#)String.java 1.85 98/07/01 * * Copyright 1995-1998 by Sun Microsystems, Inc., * ... * All rights reserved. * * This software is the confidential and proprietary information.. */ Provide a file prelude - note the use of a copyright notice, use of *'s (on each line and their indentation), date of modification package java.lang; import java.util.Hashtable; The package name occurs as the first non-commented line; it is followed by the import(s) /** * The <code>String</code> class represents character strings. All * string literals in Java programs, such as <code>"abc"</code>, are * implemented as instances of this class. * <p> * Strings are constant; their values cannot be changed after they * are created. String buffers support mutable strings. * Because String objects are immutable they can be shared. For example: * <p><blockquote><pre> * String str = "abc"; * </pre></blockquote><p> * is equivalent to: * <p><blockquote><pre> * char data[] = {'a', 'b', 'c'}; * String str = new String(data); * </pre></blockquote><p> * Here are some more examples of how strings can be used: * <p><blockquote><pre> * System.out.println("abc"); * String cde = "cde"; * </pre></blockquote> * The Java language provides special support for the string * concatentation operator (&nbsp;+&nbsp;), and for conversion of * other objects to strings. String concatenation is implemented * through the <code>StringBuffer</code> class and its * <code>append</code> method. * inherited by all classes in Java. For additional information on * string concatenation and conversion, see Gosling, Joy, and Steele, * <i>The Java Language Specification</i>. * * @author Lee Boynton * @version 1.85, 07/01/98 * @see java.lang.Object#toString() * @since JDK1.0 */ A detailed description of the class is provided using JavaDoc, 311 including special cases, constraints, author(s), system version, references, jdk dependencies; the line lengths are at most 72 chars to facilitating printouts and screen viewing public final class String implements java.io.Serializable { Class names begin with capital letters /** The value is used for character storage. */ private char value[]; /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -6849794470754667710L; Variables are private. Comments are provided to describe their usage. Names are chosen to reflect their use - simple and descriptive; important constraints are mentioned; note that descriptive identifiers are used - if words are run together then the second/subsequent words are capitalized; variable names begin with lower case /** * Allocates a new <code>String</code> constructed from a subarray * @deprecated This method does not properly convert bytes into * characters. * As of JDK&nbsp;1.1, the preferred way to do this is via the * <code>String</code> constructors that take a character-encoding * name or * that use the platform's default encoding. * * @param ascii the bytes to be converted to characters. * @exception StringIndexOutOfBoundsException if the * <code>offset</code> * or <code>count</code> argument is invalid. * @see java.lang.String#String(byte[], java.lang.String) */ Methods are commented; comments include usage, constraints, parameters, exceptions, deprecation information, return values, references; note the alignment of the JavaDoc parts (param, etc.) and the fields (ascii, etc.) and associated comments - a block style is used; sentences are used to describe the functionality public String(byte ascii[], int hibyte, int offset, int count) { if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } Note the careful check of pre-conditions used to assure correctness // Note: offset or count might be near -1>>>1. if (offset > ascii.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } Comments referring to special constraints are provided; all compound statements should use braces, even if it's optional (even though the if statement only has one statement in its body braces are used; this helps to avoid future errors where you might want to add code to the body and forget to include the required braces); note that each 312 subpart of a construct is indented; note the opening brace is on the same line as the construct and the closing brace is aligned with the construct. if (hibyte == 0) { for (int i = count ; i-- > 0 ;) { value[i] = (char) (ascii[i + offset] & 0xff); } } else { ... } } Note the placement of the else on the same line as the closing brace of the then-clause; the opening brace of the else clause is on the same line } // Private constructor which shares value array for speed. private String(int offset, int count, char value[]) { this.value = value; } /** * Tests if two string regions are equal. * <p> * If <code>toffset</code> or <code>ooffset</code> is negative, or * * @param ignoreCase if <code>true</code>, ignore case when * comparing * characters. * @param toffset the starting offset of the subregion in * this * string. * @return <code>true</code> if the specified subregion of this * string * matches the specified subregion of the string argument; * <code>false</code> otherwise. Whether the matching is * exact * or case insensitive depends on the * <code>ignoreCase</code> * argument. */ Method names begin with lower case public boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) { char ta[] = value; // Note: toffset, ooffset, or len might be near -1>>>1. if ((ooffset < 0) || (toffset < 0) || (toffset > count - len) || (ooffset > other.count - len)) { return false; } else-clauses are not used except when necessary - they add to the complexity of the program while (len-- > 0) { if (c1 == c2) continue; if (ignoreCase) { // If characters don't match but case may be ignored, // try converting both characters to uppercase. 313 // If the results match, then the comparison scan should // continue. char u2 = Character.toUpperCase(c2); // Unfortunately, conversion to uppercase does not work // properly // for the Georgian alphabet, which has strange rules about // case // conversion. So we need to make one last check before // exiting. if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) continue; } return false; Note the alignment of braces in the while; comments are indented at the same level as the construct it refers to } return true; } /** * Removes white space from both ends of this string. * <p> * All characters that have codes less than or equal to * <code>'&#92;u0020'</code> (the space character) are considered to * be * white space. * * @return this string, with white space removed from the front and * end. */ public String trim() { int off = offset; /* avoid getfield opcode */ char[] val = value; /* avoid getfield opcode */ return ((st > 0) || (len < count)) ? substring(st, len) : this; Always use parentheses to avoid mistakes regarding priority/associatively of operators; use the ?: operator in place of an if statement to provide more concise code } } 314 Summary of Notes Mentioned in the String Class Provide a file prelude including file name, date of creation/modification, copyright notice, author(s); commenting *'s should be indented and aligned; the package name occurs as the first noncommented line; it is followed by the import(s) A detailed description of the class is provided using JavaDoc, including special cases, constraints, author(s), system version, references, jdk dependencies; the line lengths are at most 72 chars to facilitating printouts and screen viewing Class names begin with capital letters Variables are private. Comments are provided to describe their usage. Names are chosen to reflect their use - simple and descriptive; important constraints are mentioned; note that descriptive identifiers are used if words are run together then the second/subsequent words are capitalized; variable names begin with lower case Methods are commented; comments include usage, constraints, parameters, exceptions, deprecation information, return values, references; the JavaDoc fields/subfields are aligned (param, etc.); a block style is used; associated comments are aligned; sentences are used to describe the functionality Pre-condition checks are used in methods to assure correctness Comments referring to special constraints are provided; all compound statements should use braces, even if it's optional (even though the if statement only has one statement in its body braces are used; this helps to avoid future errors here you might want to add code to the body and forget to include the required braces); note that each subpart of a construct is indented; note the opening brace is on the same line as the construct and the closing brace is aligned with the construct. Note in an if-statement the placement of the else is on the same line as the closing brace of the then-clause; the opening brace of the else clause is on the same line Method names begin with lower case else-clauses are not used except when necessary - they add to the complexity of the program The alignment of braces in the while is the same as in the if-statement the opening brace is on the same line as the conditional and the closing brace aligns with the while; comments are indented at the same level as the construct it refers to Always use parentheses to avoid mistakes regarding priority/associatively of operators; use the ?: operator in place of an if statement to provide 315 more concise code If you find that you must vary from these standards you should: 1. Convince others that your variation is worthwhile and 2. Be consistent to avoid confusion Miscellaneous Issues Indentation for constructs not described above: SWITCH. switch (expression) { case n: case indented statement; construct indented break; case x: statement; // Continue to default case default: always add the default case statement; break; } TRY/CATCH/FINALLY.: try { statement; } catch (ExceptionClass e) { statement; } finally { statement; } General Comments: Names: Packages: Prefix each package with the domain name cedit - e.g. package Cedit.query; Interfaces: Prefix each interface name with I - e.g. interface Istore Methods: Methods for debug-only implementation should begin debug.- e.g. debugDumpToScreen () Getters and setters should begin with "get" / "set" and return the appropriate object type. - e.g. void setVal(int theVal); int getVal(); Boolean getters should use "is" or "can" as a prefix, such as "isUndoable()" rather than "getUndoable()" "MAGIC" NUMBERS. Literal ordinal constants embedded within source should almost never be used. Whenever possible, use constants instead of literal ordinal constants: int totalDays = 10 * DAYSINWEEK; //boo, hiss! static final int kDaysInWeek = 7; //hooray! hooray! 316 COMPONENT FACTORY NAMES . A component factory is a public class that implements only static methods. These static methods are "Factory functions" or "component constructors". Factory class names should include the word "Factory". Factory method names should start with the word "Make." For example, public class WidgetFactory { static Button MakeButton(int aButtonType); static ListBox MakeListBox(); }; General Programming 1. Special Comments. Use XXX in a comment to flag something that is bogus but works. Use FIXME to flag something that is bogus and broken. LAYOUT OF SOURCE FILES (*.java) The layout for a class will be broken up into the following main sections: Copyright Notice, File Description; Package Name, Imports, Constants, Methods, protected and private members. Each section will be prefaced by an appropriate header block defining the section. 1. 2. 3. 4. 5. 6. COPYRIGHT NOTICE . This will be the standard "legalese" copyright notice. /* Copyright Notice ===================================== * This file contains proprietary information of CEDIT. * Copying or reproduction without prior written approval is prohibited. * Copyright (c) 1997 ===================================*/ FILE DESCRIPTION . This is a brief description of what the file is, does and represents. It should describe the overview of how to use the file (or classes therein). JavaDoc compatible commenting style is required. /* Description * Appropriate Description. */ PACKAGE NAME . Package names should occur on the first non-commented line of the source file and should following the naming conventions defined in this document. IMPORTS . Immediately following the package name should be the imported class names. CONSTANTS . See the naming conventions defined in this document. METHOD DECLARATIONS . Each method is preceded by a description in JavaDoc format. Public methods MUST have a standard javadoc comment header. These must be professionally commented. Remember, you never know what code will eventually be made public outside of the company. Standard access methods may be grouped without a description (access methods assign or return an object attribute). Optionally, methods may be grouped within logical groupings. Here are some suggestions: Factory Private Protected Interfaces Accessor Temporal (fickle) I/O Debugging /*================================================ * CONSTRUCTOR/DESTRUCTOR METHODS * ================================================== */ public CkSomeClass(); public CkSomeClass(CkSomeClass aSourceToCopy); 317 /*================================================== * FACTORY METHODS (usually static) *================================================== */ /** brief description */ public CkSomeClass createSomeClass(void){ } 7. /*================================================ * ACCESSOR METHODS *================================================== */ public int getState(void); public void setState(int aNewValue); /*================================================= * STANDARD METHODS * ================================================== */ //...whatever these might be... /*=============================================== * DEBUGGING METHODS * ================================================= */ void DoUnitTest(void); CLASS DEFINITIONS. This section is the actual implementation of the class. Each method (and private function) will be prefaced by the standard documentation header. Read the Documentation for Methods and Functions defined in this document. 318 SCRUM: An Empirically-Based Process for Software Project Management References: 1. Agile Software Development with Scrum, Ken Schwaber & Mike Beedle, Prentice-Hall, 2001 2. IEEE Computer, Special Issue on Agile Software Development, June 2003 Invented by Jeff Sutherland. Formalized and commercialized by Sutherland and Ken Schwaber, 1994. It’s an Agile process (http://www.agilemanifesto.org/principles.html): Principles behind the Agile Manifesto We follow these principles: Our highest priority is to satisfy the customer through early and continuous delivery of valuable software. Welcome changing requirements, even late in development. Agile processes harness change for the customer's competitive advantage. Deliver working software frequently, from a couple of weeks to a couple of months, with a preference to the shorter timescale. Business people and developers must work together daily throughout the project. Build projects around motivated individuals. Give them the environment and support they need, and trust them to get the job done. The most efficient and effective method of conveying information to and within a development team is face-to-face conversation. Working software is the primary measure of progress. Agile processes promote sustainable development. The sponsors, developers, and users should be able to maintain a constant pace indefinitely. Continuous attention to technical excellence and good design enhances agility. Simplicity--the art of maximizing the amount of work not done--is essential. The best architectures, requirements, and designs emerge from self-organizing teams. At regular intervals, the team reflects on how to become more effective, then tunes and adjusts its behavior accordingly 319 Overview Identify players/stakeholders and related conditions affecting product development: Customer Management Available technology Resources available, including financial commitment Market conditions Product owner Scrum master Scrum team(s) 320 Steps in the Scrum Process 1. Develop vision of product 2. The customer and development team define requirements that will deliver the highest business value from the vision; define Product Backlog 3. Form Scrum team(s) (5-8 people) 4. Hold Sprint Planning Meeting Develop a prioritized Sprint Backlog of tasks/issues from Product Backlog 5. Start the Sprint (30 days) Hold Daily Scrum Meetings Scrum Team self-organizes, as necessary to accomplish goal Adjust/maintain Sprint Backlog, Burndown Chart (visual depiction of Sprint progress) 6. Conduct Sprint Review 7. Repeat steps 4. through 6., as long as necessary The Scrum process is ideally suited for projects with short term to market, rapidly changing requirements – e.g. Web projects or product development for new markets. Product requirements evolve; Scrum is not prescriptive. It can react quickly to requirements changes. The process is empirical; it can accommodate for changes to requirements, as well as changes gleaned by players/stakeholders over time based on their dynamic experiences. The short feedback loop (relatively short “inspect and adapt” Sprint cycle) is essential to this empirical process. 321 Scrum is an iterative and incremental process: each Sprint produces a new increment of the product, Scrum iterates over Sprints Business Conditions & Requirements Standards, Conventions, Guidelines Product Backlog: a prioritized list of all work to be completed prior to releasing a product Sprint Planning Meeting Sprint Backlog Scrum Master/ Scrum Team Daily Scrum Sprint 30 days Sprint Review Executable Product Increment SCRUM PROCESS 322 Scrum Summary Similar to Rugby – both are: Adaptive Quick Self-organizing Have few rests Scrum promotes transparency Each Scrum Team member reports his or her work to the team on a daily basis Management can attend Daily Scrum meetings to observe status reports Scrums Since each participant is fully informed of work of others it’s possible to help other developers (e.g., developer A can ask developer B to make a slight mod that will save much time for A) 323 Scrum Components and Processes– Definitions and Functionality Product Backlog Business Conditions & Requirements SCRUM PROCESS: Product Backlog Standards, Conventions, Guidelines Product Backlog: a prioritized list of all work to be completed prior to releasing a product Sprint Planning Meeting Sprint Backlog Scrum Master/ Scrum Team Daily Scrum Sprint 30 days Sprint Review Executable Product Increment 324 Product Backlog: A list of all desired work on the project Story-based work (“let user search and replace”) Task-based work (“improve exception handling”) Tasks are grouped into increments that should take no more than 30 days Lists all outstanding work to be done Contains everything needed or good ideas in product List of: Features Functions Technologies Enhancements Bug fixes for future releases 325 Example: Allow users to access and view account balances for last six months Improve scalability of product Simplify installation process when multiple databases are used Determine how workflow can be added to product Initially only include requirements for 30 day Sprint (e.g. concepts, wish list) It is dynamic – requirements evolve. Product Marketing determines features/functions Sales includes customer features Product Backlog Engineering includes relevant technology Customer Support (fix major bugs) 326 Deliverables Prioritize Product Backlog Address top priority items first Better-defined items have higher priority, more value Include issues – e.g., fast response time Product Owner turns issues into work for Scrum Team Grows with time as clarity of product emerges; requirements change First Scrum => sort out architecture; the deliverable is the architecture 327 Product Owner Only person to manage/control the Product Backlog (makes visible to all) This person might be a product manager; officially responsible for the product – meets with customer and management to make the decisions on requirements and functionality. She/he is the only person that can tell Scrum Teams what to do via the Product Backlog – e.g. the product’s requirements and functionality. Her/his decisions are reflected in the Product Backlog. Estimation of the Product Backlog Effort Product Owner talks to developers, tech writers, quality control staff, etc. Time includes: architecture, design, build, and test Time is not very accurate until the team gains experience Estimate is the best guess (not binding on team) Scrum Team selects amount of Product Backlog to handle in a Sprint based on these estimates Starts estimates with top priority items (best understood) As development occurs, estimates are modified 328 Sprint Planning Meeting Business Conditions & Requirements SCRUM PROCESS: Sprint Planning Meeting, Sprint Backlog Standards, Conventions, Guidelines Product Backlog: a prioritized list of all work to be completed prior to releasing a product Sprint Planning Meeting Sprint Backlog Scrum Master/ Scrum Team Daily Scrum Sprint 30 days Sprint Review Executable Product Increment 329 Scrum Teams meet with Scrum Master and others to plan each Sprint. There are 2 consecutive meetings: 1. Team meets with Product Owner, management and users to determine functionality to build during the next Sprint 2. Team meets to figure how to build functionality into a product increment during Sprint; the input provided is from Product Backlog, latest increment of the product, capabilities/performance of the team Product Backlog Team Capabilities Business Conditions Review, Consider & Organize Next Sprint goal Technology Stability Executable Product Increment (Figure 3.1 from Scrum text) 330 Identify Sprint Backlog, Goal for next Sprint Product Owner presents top priority Product Backlog; leads discussion about appropriate changes to the Product Backlog, given demo of deliverables at end of prior Sprint The Sprint Backlog list is built. This is a list of all the tasks/activities needed to develop the Product Backlog selected for this Sprint. Top Priority Product Backlog: Implement middleware alternative providing secure access to legacy database – should develop details such as Implement Object Oriented middleware between applications and legacy databases using Tuxedo from BEA and CORBA wrappers Sprint Goal: to provide a standardized middleware mechanism for the identified customer service transactions to access backend databases (this is less precise, provides “wiggle room” regarding functionality) Sprint Review: management, customers and Product Owner review how implemented functionality meets projected goal. If there are problems then modify requirements or team composition, etc. 331 Define Sprint Backlog to meet Sprint Goal 1. Establish goal 2. Team determines what work is needed based on Product Backlog All team members must be present; may invite others for advice (e.g., domain experts, Product Owner) Team self-organizes here. Management does not affect meeting Team compiles list of tasks with details for development Tasks are detailed (4-16 hours to complete each task) These tasks will form the Sprint Backlog Team assigns tasks to members Define initial investigation, design, and architecture e.g. 1. Map transaction elements to backend database tables 2. Write business objects in Java to handle transaction and interfaces 3. ... Sprint Backlog is modified dynamically as tasks and time estimates are clarified If the team discovers it selected too much for the goal, the Scrum Master meets with Product Owner and team to remove items and still meet goal; or, remove functionality 332 Changes are made to Sprint Backlog during Sprint 333 Scrum Master/Scrum Team Business Conditions & Requirements SCRUM PROCESS: Scrum Master; Scrum Team Standards, Conventions, Guidelines Product Backlog: a prioritized list of all work to be completed prior to releasing a product Sprint Planning Meeting Sprint Backlog Scrum Master/ Scrum Team Daily Scrum Sprint 30 days Sprint Review Executable Product Increment 334 Scrum Master Scrum Master is a new management role; oversees Scrum operation: typically a Project Manager or Team Leader Responsibilities: Represents management Represents team members to each other Checks progress vs. Sprint goals and predictions during prior Daily Scrum e.g., if someone spends too much time on a task the Scrum Master will try to find help Checks team progress and recognizes problems; provides help Works with customer and management to identify/institute Product Owner (“owns” and is responsible for product delivery; decides order in which components are built). Works with management to form Scrum Teams Works with Product Owner and Scrum Teams to create Sprint Backlog for a Sprint Conducts all Daily Scrums - identifies and removes impediments Scrum Master is normally a team leader, project leader or project manager. Initially, if there are significant impediments the Scrum Master should be a Senior Manager or outside Scrum consultant Should make immediate decisions in Daily Scrum meetings, even in the context of incomplete information – must keep the team moving. Decisions can always be modified later. 335 Scrum Team Scrum Master meets with team to review Product Backlog and establish Sprint Goal Team commits to the work Team is self-organizing to draw on strengths of members; if a member has problems then focus shouldn’t be on problems but should be on strengths Team sorts out problems, not Scrum Master Size: 5 – 8; complexity is maintained by limiting the size of the team If there are slightly more than 8 people then break into smaller teams. Scrum Master picks a team to select from Product Backlog. Then other teams select, etc. (teams select from Product Backlog based on members’ strengths) If there are 2 or more teams then conduct Daily Scrums for each team and then daily Scrum of Scrums (Scrum Masters from each team meet after the Daily Scrum for their own Daily Scrum) 336 Team Composition: Based on people needed to meet the Scrum Goal Include analysts, designers, QA, coders Very experienced engineer should mentor junior engineers If there is no QA person available then each developer should do their own testing. Team members commit based on time available; e.g., consultants or part-time people have less time Team members DO NOT have titles; no job descriptions; everyone contributes Scrum Master or Project Manager can change team at the end of a Sprint (e.g. Domain experts might be added, under performers might be moved out) 337 Team Responsibility/Authority: Must meet Sprint Goal The amount of Product Backlog to address is up to the team Members must make impediments known and request their removal They must conform to organizational responsibilities (e.g., coding standards, architectures) Team can cancel a Sprint if they cannot meet the Sprint Goals. THIS IS SERIOUS! Working Environment: Adequate resources must be provided (e.g., workstations, monitors) Adequate technology must be provided – Scrum Master checks on this and remedies, if necessary, to ensure the Sprint Goal is met Facilitate communication between team members (e.g., put their cubicles nearby, supply conference rooms with whiteboards for brainstorming, etc.) Team picks (reasonable) work hours 338 Business Conditions & Requirements Standards, Conventions, Guidelines Product Backlog: a prioritized list of all work to be completed prior to releasing a product Sprint Planning Meeting Sprint Backlog Scrum Master/ Scrum Team Daily Scrum Sprint 30 days Sprint Review Executable Product Increment SCRUM PROCESS: Daily Scrum 339 Daily Scrum Meetings Held each day Lasts 15 minutes Status reporting, not discussions Presentations by each Scrum Team member: 1. Accomplishments since last meeting (24 hours ago) 2. What member will do before next meeting 3. Obstacles encountered 340 Meeting operation, results and benefits: Familiarizes team members with team-based, rapid, intense, cooperative development Improves communication, project knowledge Scrum Master conducts Daily Scrum – ensures people speak briefly. Managers can attend AS OBSERVERS, NOT PARTICIPANTS; the meetings provide a mechanism for managers to understand progress, learn of results and impediments; but can’t interfere in any way; can learn of under-performing workers Use rooms with whiteboards e.g. SCI 254 Use same room, same time each day Room should be easily accessible from team’s workplace If team members cannot physically be present set up a conference call with speakerphone Fixed time, fixed location 341 Starting Meeting Scrum Master oversees, Scrum Master takes care for everything to be set up (e.g., chairs, tables) to ensure everything can be done in 15 minutes. Team sits around a round table, observers (managers, etc.) may sit further out from table, not within the Scrum Team seats Everyone arrives on time! Meeting starts on time. Late arrivals are penalized (e.g. must pay $1 for a charity fund) Format Only 1 person talks at a time, no side conversations Scrum Master picks people, in order, seated around the table to speak on the 3 presentation items (see beginning of this section). Stay focused on commitments Distractions are impediments Members speak briefly and to the point; don’t discuss, e.g., design issues 342 Identifying Impediments Scrum Master records discussion items at the Daily Scrums and removes impediments – writes notes on a whiteboard – e.g., pc problems, slow network, management asking members to attend other meetings, member does not understand technology Members can provide advise to Scrum Master for improvements Address stated impediments at the next Daily Scrum If there are too many impediments, perhaps the company isn’t providing necessary support – cancel Sprint? Making Decisions Team has full authority to make decisions to support Sprint Goal – e.g., bring in consultants, new technologies, books, etc. (within budgetary constraints); Scrum Master shouldn’t make too many decisions Make decisions quickly so as not to impede work – bad decisions can be corrected at end of Sprint If Scrum Master must make a decision then do so at the Daily Scrum else within 1 hour afterwards Establishing Follow-up Meetings If additional discussions (brainstorming, design issues) are necessary then the follow-up meeting should occur right after the Daily Scrum 343 Sprint Lasts 30 days Team self-organizes and improves delivery over time Team, alone, determines its destiny Nobody outside can change scope/nature of work Management’s risk is 30 days; Daily Scrum provides continuous feedback Team can hold meetings or ? It controls its working hours, etc. (within reason) Maintain Sprint Burndown Chart to better understand progress (diagram completed over time depicting remaining work in a Sprint) The Sprint Burndown Chart is one of the artifacts produced by instrumenting the Scrum process to better understand the variables such as remaining work, state of the product development, cost, functionality, etc. Processes, such as testing, inspections, and requirements analysis, may be used within a Scrum project. The difference between the Scrum process and other standard software engineering processes (e.g., Waterfall development method) however, is that the use of these processes is determined empirically by need, not prescriptively … if the team feels that the aforementioned processes are needed, they are used; otherwise, they are not used. 344 If the organization is transitioning from a plan-based development method, such as the Waterfall development method, to an Agile-based method such as Scrum then it might help to consider the following Sprint types: Prototyping Requirements capture Analysis and design Implementation Stabilization 345 346 Sprint Review Business Conditions & Requirements Standards, Conventions, Guidelines Product Backlog: a prioritized list of all work to be completed prior to releasing a product Sprint Planning Meeting Sprint Backlog Scrum Master/ Scrum Team Daily Scrum Sprint 30 days Sprint Review Executable Product Increment SCRUM PROCESS: Sprint Review 347 4 hour informational meeting; working meeting – questions, observations, suggestions, etc. Team presents to management, customers, users and Product Owner the product increment built during the Sprint Team mentions successes and failures Management observes progress with given resources Customers provide feedback on deliverables Product Owner checks on built functionality Other engineers learn more about team Preparation for the Sprint Review Scrum Master conducts meeting; meets with team to establish agenda and presentation and who presents what Team organizes presentation – discuss architecture, design, functionality, etc. Compare results with Sprint Goal, Product Backlog and reasons for non-delivery Team member reviews simple architecture diagram; highlights previously completed technology/functions and then adds new functionality to diagram with demos No extensive preparations by team; no PowerPoint slides, etc. Team should spend less than 2 hours to prepare Scrum Teams meet with Scrum Master and others to plan each Sprint. There are 2 consecutive meetings: 348 3. Team meets with Product Owner, management and users to determine functionality to build during the next Sprint 4. Team meets to figure how to build functionality into a product increment during Sprint; the input provided is from Product Backlog, latest increment of the product, capabilities/performance of the team Product Backlog Team Capabilities Business Conditions Review, Consider & Organize Next Sprint goal Technology Stability Executable Product Increment (Figure 3.1 from Scrum text) Identify Sprint Backlog, Goal for next Sprint Product Owner presents top priority Product Backlog; leads discussion about appropriate changes to the Product Backlog, given demo of deliverables at end of prior Sprint 349 The Sprint Backlog list is built. This is a list of all the tasks/activities needed to develop the Product Backlog selected for this Sprint. Top Priority Product Backlog: Implement middleware alternative providing secure access to legacy database – should develop details such as Implement Object Oriented middleware between applications and legacy databases using Tuxedo from BEA and CORBA wrappers Sprint Goal: to provide a standardized middleware mechanism for the identified customer service transactions to access backend databases (this is less precise, provides “wiggle room” regarding functionality) Sprint Review: management, customers and Product Owner review how implemented functionality meets projected goal. If there are problems then modify requirements or team composition, etc. Define Sprint Backlog to meet Sprint Goal 3. Establish goal 4. Team determines what work is needed based on Product Backlog All team members must be present; may invite others for advice (e.g., domain experts, Product Owner) Team self-organizes here. Management does not affect meeting Team compiles list of tasks with details for development Tasks are detailed (4-16 hours to complete each task) These tasks will form the Sprint Backlog 350 Team assigns tasks to members Define initial investigation, design, and architecture e.g. 4. Map transaction elements to backend database tables 5. Write business objects in Java to handle transaction and interfaces 6. ... Sprint Backlog is modified dynamically as tasks and time estimates are clarified If the team discovers it selected too much for the goal, the Scrum Master meets with Product Owner and team to remove items and still meet goal; or, remove functionality 351 Scalability Scrum has been used in groups of more than 600 people. Form many Scrum Teams and form a hierarchy of Scrum Teams (Scrum of Scrums, etc.) Every part of organization is made up of teams (Scrums), including management teams Developer scrums meet daily Scrum of Scrums (team leaders from each Scrum in a product line) meet weekly Management Scrum meets monthly Individual teams have their daily Scrum meeting, where core members provide updates and members from other teams can sit in to hear the progress and obstacles as they arise. Depending on the degree of interdependency among the teams, an additional Scrum meeting by designated members from each team can provide a fast and structured forum to monitor progress on cross-team requirements and issues. During the design and system architecture phase of Scrum, the designers and architects divide the project into packets. Packets are assigned to teams based on priority and scheduling constraints. Based on the degree of coupling between packets, groups of up to six teams are organized into a management control cluster. This continues upwards until a top-level cluster has been created. 352 Management Issues Involved on a daily basis (Daily Scrum) Scrum master identifies impediments – some may be quite surprising to management (management might have spent time setting up a reasonable environment but might have missed the mark) Scrum Team operations are transparent to management via the Daily Scrums Risk is addressed – management can input concerns on a 30-day period (Sprint Review) If a company was recently involved in a merger then there might be added issues for consideration such as organizational politics and culture. These issues are also relevant when the Scrum teams are not co-located, such as when there is joint development with global partners. 353 Scrum Notes for 668/868 We will hold Daily Scrums, etc. during class to ensure all are present (these are really weekly Scrums) Dr. Levine will serve as Scrum Master. Sprints will be at most 3 weeks Scrum Teams must develop estimates (be careful) and Burndown Charts Product Owner, Customer selected from outside the Scrum Team – classmates participating in other projects. Use the following samples to produce your own artifacts 354 Sprint Burndown Chart Today’s Date: November 3, 2003 Sprint Review Date: November 11, 2003 Task Install Oracle Set up CVS Set up Apache on home PC Set up Tomcat on home PC Expected Duration (days) 3 %Complete Comments 2 4 50 20 3 80 100 Unicorn Problems Works great PC problems mod_jk problems Person Responsible Jane Jack Sharon Jim Use Excel to produce the chart above and the chart below. The chart below was produced from the following data in Excel: task 1 task 2 task 3 Date Days Remaining 1/1/2003 1/5/2003 1/20/2003 12 7 4 You can produce the Excel chart by following the steps: 0. Select Data to chart 1. Select the Chart Wizard (tool bar) choose XY (Scatter) - scatter with data points connected by lines 2. Step 2 of wizard: Next 3. Step 3 of wizard: Titles tab Chart: Sprint Burndown Chart (Sprint Review 1/15/2003) X axis: Date Y axis: Estimated Days Remaining Data labels tab: "show value" 4. Finish Data points should be taken/plotted just prior to each weekly Scrum meeting. 355 Sprint Burndown Chart (Sprint Review 1/20/2003) 14 Estimated Days Remaining 12 12 10 8 7 6 4 4 2 0 12/30/2002 1/4/2003 1/9/2003 1/14/2003 Date Sprint Progress Report Jane Doe - November 10, 2003 Progress Since Last Scrum: Set up CVS Installed Oracle ... Tasks for the Next Week: Install CourseWork Port Database ... Impediments Exams in other courses Unicorn problems ... 356 1/19/2003 1/24/2003 Group 1: Jim Marx, Anita Wall, Todd Lincoln, Sophia Rand, Tim Nunn Product Backlog - may include any workproducts of value to the project Product Owner prioritizes Item number Requirement 1 Log credit payments to AR 2 process sale-simple cash scenario 3 slow credit payment approval 4 sales commission calculation 5 lay-away plan payments 6 PDA sale capture 7 process sale-credit pmt scenario Category feature use case issue defect enhance technology use case 357 Estimated Development Status Priority Time (hours) underway 5 2 underway 5 60 not started 4 10 complete 4 2 not started 3 20 not started 1 100 underway 5 30 Weekly Deliverables (due each Friday) Team Lead Posts the following (check ilearn for sample forms): Product Backlog (Product Backlog.xls) Tasklist spreadsheet (TaskList868.xls) Sprint Backlog/Burndown Chart (Sprint Backlog.xls - updated versions should be posted each week) Be sure to create 3 discussion topics, one for each of the above titles for threaded discussions for each series of reports. For example, there should be a discussion topic Product Backlog. Each item in this thread will be an update of the previous backlog so it’s easy to see the succession of backlogs Each team member submits the following: email me the weekly status report – see ilearn; be sure to include the following in the email subject line: last name, first name, group number, status report (e.g., Doe, Jane group 2 status report) When work starts on the project create a discussion topic in your group’s discussion area of ilearn titled Sprint Progress Report – Your Name You will submit a report each week by posting this report in the discussion thread (see below for the form of the report Sprint Progress Report Progress Since Last Scrum: Set up CVS Installed Oracle ... Tasks for the Next Week: Install CourseWork Port Database ... Impediments Exams in other courses Unicorn problems ... 358 Squeak Smalltalk Exercise Define a binary tree class and construct a tree internally via Smalltalk statements, e.g. node1 _ BinTree new: ‘A’ “build a new tree with node labeled A”. node2 _ BinTree new: ‘C’. node3 _ BinTree new: ‘B’. node1 addLeftKid: node2. node2 addRightKid node3. At this point, the tree looks like: A C B Perform traversals on the tree; print the node labels as they are encountered Inorder traversal (left root right) CBA Preorder traversal (root, left, right) A C B Postorder traversal (left, right, root) B C A Note: view a tree as a collection (ordered) of nodes; use first, next to yield nodes in indicated order; you should have a BinTree class and traversal classes with constructors - e.g. you would like to do something like: suppose theTree is the variable referencing the tree just constructed; traversal _ InorderTraversal new setTree: theTree. “the statement above performs initialization; it DOES NOT create a new collection” nextLabel _ traversal first. (nextLabel notNil) whileTrue: [ nextLabel print. nextLabel _ traversal next ] ... DO NOT create a collection, besides the original BinTree; simply keep track where you are traversing in the tree (for each traversal type) so you can determine which node to yield for the next request of first/next. DO NOT include a link from each node to its parent. Hint: For each traversal maintain a stack of nodes which form the path from the last node yielded to the root of the tree. Turn in: 1. Test cases 2. Class diagrams 3. Documentation on your traversal algorithms. This documentation should include pictures depicting the operation of your algorithm. Since you will select different 359 algorithms you will need to ensure I understand your algorithm by providing adequate documentation. DO NOT describe in words a concept that would be easier to understand via pictures/diagrams. Show a series of pictures for each traversal, demonstrating with an interesting case how the algorithm works. Be sure to provide the invariant associated with the Stack you use (i.e. how is the stack being used and what must always hold true regarding the stack) 4. Smalltalk code – use Squeak Smalltalk. File out the source code and then print it for submission. Teamwork All group members are expected to contribute to the best of their abilities Each group member MUST participate in teamwork and group work, attend meetings and be courteous and responsible All group members get the same grade UNLESS the instructor is told or discover that somebody is not contributing fully 360 Project Information Write a project proposal for the OOP course. The complexity and scope of the project should be greater than the assignments that you have been doing in the course. Be careful not to take on too much work. You must program your project on a computer in a departmental computer laboratory (to be designated), or be prepared to bring any equipment you use to the lab for the demonstration upon completion. You may program the project in Java, C++ or an approved OOP language.. Type your proposal in the form: 1. Brief overview of system 2. System functionality (describe, e.g., a sample session using your system...be as specific as possible) 3. Expected size of code (number of lines) 4. Expected time to complete 5. Project activities/timeline for completion Upon completion you will demonstrate your project on a computer in the lab or classroom. Your demonstration should not last longer than 5 minutes. The proposals should be accepted by me before <in a few weeks> The projects should be completed before <announced later> USE PICTURES...show simple examples, class hierarchy, object interactions using UML...also, if you are working with GUIs you must show relevant screen-shots.. Use categories within your class definitions to aid in readability: e.g. YourClass : SuperClass <accessing functions> Public: getSize(...) Get size of generated picture ... Private: 361 setScope(...) Set scope of visual items ... <traversing functions> ... Project Milestones (Deliverables include mockups, specs, architecture diagrams as determined by the indicated milestone) Milestone 1. High Level Specs/Analysis (use cases, conceptual model, threads.) 2. Design (specification of patterns, class diagrams, packages) 3. Specification of GUI (screen snapshots of actual GUI) 4. Test of project 5. Project completion TOTAL Weight 5 Due Date --- 10 --- 5 --- 5 75 100 --Last Class Session Submission for each Milestone should include the current Milestone, project proposal, and all prior Milestones. Final Project Deliverables Upon project completion (the last class session) you will submit the following - be sure to use the comments provided above as guidelines on documentation: 1. Contributions made by group members 2. Indicate the platform and software used 3. Original project proposal 4. User Guide 5. Design overview 6. External documentation on algorithms, etc., as appropriate 7. Use Cases (these should be updated per my prior comments) 8. Conceptual model (these should be updated per my prior comments); be sure the model represents all of the concepts included in the project specification – check the nouns you included in the project specification 9. Relevant sequence diagrams – one sequence diagram per use case 10. Class Diagrams (these should be updated per my prior comments) 362 11. Package diagram/descriptions 12. Javadoc comments You will not submit the code. However, it should be accessible to me via java.net should I need to examine it. Please ask me if you have questions concerning your documentation...I'm interested in reading/analyzing your documentation RATHER THAN your code. Comments on your submission User Guide - as you describe the operation of your system be sure to include screen snapshots as an aid (professional system tutorials include these to help aid in understanding). As usual, you may write on the snapshots to help explanations. YOU MUST integrate these snapshots in the user guide so I can fully understand your system. Design overview - provide a detailed design overview; use pictures to help with your description. You will be downgraded quite a bit if you do not provide necessary pictures. Include comments on problems in meeting the goals of your proposal. INCLUDE PICTURES and simple examples to help me understand the thrust of your project (include pictures to help describe your interesting algorithms). I should be able to understand all aspects of your project without delving into your code. Provide written descriptions of threads used by your system; describe the roles played by each thread Package diagrams to depict the architecture: vertical layers and horizontal partitions Class Diagram - provide layered description with labels, as needed. Use a UML tool to produce these diagrams. Include public (NOT PRIVATE) methods with their types/return values and important data fields with type information. Provide a summary showing class associations, an expanded version including all variables and methods and a class hierarchy; be sure to provide a few diagrams and not one large, detailed diagram which would not be useful to the reader Your external documentation should allow me to fully understand: a) the OOP design principles you used in your project b) the organization of your project c) any interesting algorithms d) descriptions of all of your classes, including class descriptions (both class diagrams and written descriptions) and design decisions e) comments on the extensibility/flexibility of your designs, as well as constraints your design imposes YOU WILL RECEIVE LITTLE CREDIT FOR PROGRAMS THAT ARE TOO DIFFICULT TO UNDERSTAND DUE TO POOR DOCUMENTATION 363 Your grade will be based on your external (and internal) documentation, the success of the application of OOP principles and the scope and complexity of your project. If you submit all of your work in a binder then be sure to use margins when printing so any documentation is not hidden. Flesh out the architecture by describing use-cases for the project (focus on some of the methods and show the sequence of events that occur once the method is invoked). identify all of the objects/classes that will be involved in implementing the method show a time-line of events that occur and the messages passed between the objects identify the threads to be used; be careful to keep the number of threads under control since they add to the complexity of the application. provide a picture of objects and communication paths/mechanisms. provide a pictorial class diagram depicting all of the major classes and their associations be sure to use interfaces appropriately. use packages appropriately Consider using a layered design. Identify events/listeners, streams, exception hierarchy, data handling, etc. 364