meeting -*- Outline -*- * GRASP: More Patterns for Assigning Responsibilities (Larman, Chapter 22) ** new patterns, context ------------------------------------------ PREVIOUS GRASP PATTERNS FOR RESPONSIBILITY ASSIGNMENT Earlier patterns: - Information Expert - Creator - High-Cohesion - Low Coupling - Controller New Patterns: - Polymorphism - Indirection - Pure Fabrication - Protected Variations ------------------------------------------ ** Polymorphism (22.1) ------------------------------------------ POLYMORPHISM Solution: when behaviors or executions vary by type, assign responsibility for the variations to these types, using dynamic dispatch (virtual functions). Record the common parts in a supertype. Problem: How to handle different types of objects with similar, but not identical behavior? How to create pluggable components? ------------------------------------------ ... Solution corollary: don't test for object types and don't use if-then-else to vary behavior based on type. Q: what's the problem with if-then-else's to handle different types? if the tests happen in many modules then if add new types have to - find all existing modules that perform such tests, - have to edit all of them both are painful, and a sign of poor maintainability (high coupling, poor information hiding) ------------------------------------------ Example: support different tax calculators |------------------| | <> | | TaxCalcAdapter | |------------------| |------------------| | getTaxes | | (Reservation): | | List of TaxLine | |------------------| /_\ | . |- - - - - - - - - - -| . . | | |------------------| |------------------| | SuperTaxCalc | | EZTaxesCalculator| | Adapter | | Adapter | |------------------| |------------------| | | | | |------------------| |------------------| | getTaxes | | getTaxes | | (Reservation): | | (Reservation): | | List of TaxLine | | List of TaxLine | |------------------| |------------------| ------------------------------------------ Need for external tax system: tax calculations vary by state and by type of flight, airport, etc. Adapters are local facade objects that front for the external tax calculator systems. The adapter is supposed to change the getTaxes request into method calls (e.g. through an RMI connection) to the external system, using its vocabulary and interface. The key is that these are all doing similar things, but in different ways. That's when you use polymorphism (virtual function calls, message passing, dynamic binding). Discussion: This is fundamental to OO design, it's what makes a design object-oriented. this is why you need message passing (virtual function color, dynamic binding) mechanism in an object-oriented language. The idea is that the system uses the super type, and its specification to reason about the underlying objects with their similar but different behavior. The specification captures the similarity in the behavior of the underlying objects. Behavioral subtyping, LSP, ... Q: Where did the if-then-else go? it's in the programming language's implementation Contraindications: don't "future proof" the design against unknown possible variations Benefits: - new extensions are easy to add to the system don't have to find all places to edit - new implementations can be introduced without affecting clients clients are validated against the supertype's specification Related patterns: - protected variation - many other patterns rely on polymorphism (adapter, command, ...) Also known as: choosing message, don't ask "what kind?", supertype abstraction ** pure fabrication (22.2) ------------------------------------------ PURE FABRICATION Solution: use a made-up class, not in the problem domain Problem: how to assign responsibility when using other patterns would violate high cohesion and low coupling, or other goals? Example: Need to save reservations persistently. Why not let Reservation do that? - lots of database specific knowledge it doesn't have ==> low cohesion - coupling to DBMS classes ==> hurts reuse - different kind of responsibility So ------------------------------------------ ... create a PersistentStorage class, not part of domain, but a pure fabrication Q: how does this solve the problems with having Reservation save themselves persistently? Discussion: objects can be designed based on: - data decomposition (e.g., Reservation) - functional decomposition (e.g., IndexGenerator) The point of this pattern is to let students know that not everything has to be found in the domain model. Sometimes information expert doesn't do the right thing... Benefits: high confusion, better reuse Contraindications: Mostly it's good to follow info expert and domain concepts. Don't uses as an excuse to do exclusively functional decomposition, affecting coupling... Related Patterns: - High Cohesion, low coupling - A pure fabrication takes responsibilities from classes that would have been the information experts. - Controller, Adapter, Command, Strategy, and most other design patterns are pure fabrications. ** indirection (22.3) ------------------------------------------ INDIRECTION (22.3) Solution: assign responsibility to an intermediary to avoid coupling other components and services. The intermediary creates an indirection between the other components. Problem: how to de-couple objects so that they can change independently? How to avoid direct coupling? Examples: PersistentStorage is an intermediary between Reservation and the DBMS. TaxCalculatorAdapter is an intermediary between system and external tax calculators. ------------------------------------------ draw picture like Figure 22.3 Discussion: This is especially useful for portability, and for avoiding coupling between one system and another. "Most problems and computer science can be sold by another level indirection" (Larman quotes an unknown source) Benefits: Q: What are the benefits? lower coupling Related patterns: - protected variations - low coupling - many others (Controller, Adapter, Facade, Observer, Mediator) - Many indirection intermediaries are pure fabrications. ** Protected Variations (22.4) ------------------------------------------ PROTECTED VARIATIONS Solution: Assign responsibility to a stable interface that hides predicted instability Record the stable responsibilities in the interface. Problem: How to isolate changes or variations? Examples: Tax calculation variations Marketing gimmicks change prices ------------------------------------------ ... we avoided variation in the taxes by using an external tax calculator. We avoid variation in the APIs of external tax calculators by using the polymorphism pattern, as described before. Q: How is this done in automobile design? In medicine? Standard dashboard, generic drugs, ... Standards organizations also do this... ------------------------------------------ Discussion: - Very old principle of design - Movivates lots of mechanisms: - abstract data types, encapsulation - templates (generic polymorphism) - polymorphism (subtype polymorphism) LSP, supertype abstraction - data-driven designs - service lookup - interpreters, virtual machines - reflective/meta-level designs - uniform access to arrays, methods, fields - structure-hiding designs Law of Demeter: should only send to - this (or self) - a parameter - an attribute of this, - an element of a collection contained in "this" - an object freshly created ------------------------------------------ polymorphism in OO is a special case of data-driven design law of Demeter (Lieberherr et al.) is also called "don't talk to strangers" The further along a pass close message sends one travels the more fragile the code becomes e.g., reserv.getPayment().getAccount().getAccountHolder() vs. reserv.getPaymentName() // better ------------------------------------------ Contraindications: Def: a *variation point* is a difference that must be supported in the current system. Def: an *evolution point* is a difference that may arise in the future, but is not present in existing requirements. Be realistic and only protect evolution points that are likely and expensive to change. ------------------------------------------ Choose your battles Benefits: Q: What are the benefits? - and extensions for new variations are easy to add - new implementation can be introduced without affecting clients - lower coupling - impact of change can be lowered Related Patterns: - Most of them are mechanisms for particular variation of some sort. - This differs from the polymorphism because it's a more general concept, and polymorphism is a specific way to achieve one kind of protective variation. Also known as: - information hiding (Parnas 1972) Hiding information about design from other modules. Data encapsulation is a special case. - Open-closed principle (Meyer 1988) Modules open to extension in their implementation Modules closed to change of specfication (so changes don't affect clients) - Abstraction by specification (Liskov and Guttag) The entity that is doing the protecting is an abstraction of its variations the specification records the abstraction