Date: Sun, 11 Dec 1994 13:43:41 -0500 From: Dan Halbach To: patterns@cs.uiuc.edu Subject: "Multiple Contexts" pattern Submitted for your approval, comments, criticism, etc.... -Dan --------------------------------------------------------- Pattern: Multiple Contexts AKA: Actor/Roles, Multiple Factors Problem: Some classes are more complex than you would like them to be simply because they model "real" objects whose complexity is not something over which you have any choice. The eventual consumers of the class expect capabilities in the class that necessitate a complex interface. Competing Forces: It is widely accepted as common sense that the public interface to a class should be kept as simple and direct as possible. This makes the class easier to learn, maintain, extend, and use reliably. Simple interfaces clarify and concentrate the class' responsibilities. However, some classes are naturally complex, often due to the fact that instances of the class may be used differently by different applications or users, each of whom expects an interface that suits her purpose. In other words, a general class may be used in different, specific contexts. For example, (1) what is important about a car differs for a driver and a mechanic, and (2) a person can be both a parent and an employee. In both cases, consumers of the two general classes (car and person) expect methods corresponding to each context in which the general class may be used. Furthermore, code that uses a general class in one context will not care about methods that exist to support any other context. Yet, interplay between the various roles of the general class exists, thus leading to the tight coupling within the single, general class. For example, a parent's ability to attend a child's recital is not a concern for code that treats a person as an employee. Yet, the scheduling conflicts that might occur due to the person being a parent require the parent and employee to be modeled together as one object. "Traditional" Solutions and Their Shortcomings: (Note: By "traditional" I am referring to solutions actually encountered in delivered code.) 1. One large interface with methods for each context delineated by comments. Even with well written comments, the class definition can still be quite ominous. Nothing guarantees comments will be kept up to date as the code changes. Instances are still "fat" i.e. all attributes exist even when the instance is not used in all contexts. 2. Multiple separate wrappers/adapters. This does reduce/simplify individual interfaces and type integrity exists, but it is cumbersome to separately create and delete wrappers and the object being wrapped. Which wrapper user deletes the actual object and when? Separate wrappers still requires documentation (in some form) to let consumers know of their existence, and synchronizing the updates to this doc with changes to the class and wrappers must be done manually (and, in my experience, unreliably). 3. Independent objects for each context that pass mementos to each other as the context changes. All the problems of multiple wrappers (#2 above) still exist. There is an additional synchronization problem: when one context is in use, it must insure that it has the most recent copy of memento. There is also a life-span problem: the object with most recent/up-to-date memento must live long enough to hand off its state to any other object that needs it. New Approach: One class that is an aggregate of its context objects. The general/aggregate class has public methods for returning references to each context object. Each context object has its own interface appropriate to its specific purpose, role, or context. Context objects are friends of the general object that owns them to allow access to private attributes shared between contexts. Forces Resolved: 1. The Learning Curve is reduced: Consumers need only learn those contexts that are useful to them. 2. Cataloging the Contexts: Consumers can look to the general class to find the list of all supported contexts (maintained in a strongly-typed fashion). 3. Simplified Interfaces: Context objects can be passed as parameters to methods that don't need to know of any other context of the general object. 4. Sharing of attributes between contexts is automatic. Methods on the general class to access specific contexts can act as Factory methods to keep instances light weight -- only those contexts used/referenced in a given instance will ever be instantiated. 5. Synchronizing the scoping/life-span of the general class and its contexts is implied and automatic. Related Uses: This patterns is also called "Multiple Factors" because a single, complex object can be "refactored" using this pattern. An example from my own experience is a GUI class to implement a two-dimensional scrolling matrix. The requirements included: a clipboard mechanism, scrolling in 2D, and separate selection modes for whole columns, whole rows and individual cells. Pre-existing one-dimensional scrolling lists already had more public methods than were desirable in a single class. The 2D extension more than doubled the number of required public methods, resulting in an enormously complex class. Refactoring resulted in separate factors (or contexts) for: the clipboard, cursor movement, row selection, column selection, cell selection, cell formatting, vertical scrolling, horizontal scrolling, etc. As a result, each factor became simpler and manageable. Moreover, consumers who did not use the clipboard were no longer required learn or even see the clipboard-specific methods. In fact, the clipboard was never instantiated, making the scrolling matrix more light-weight. Related Patterns: Bridge: the bridge pattern describes a single interface to multiple objects. This pattern is the reverse: a single object having multiple interfaces. (There are more related patterns, some of which are obvious, but the relationship to Bridge is the most significant.) Example: class Driver; class Mechanic; class Car { public: Car(); virtual ~Car(); //Context factory and access methods virtual Mechanic& mechanic(); virtual Driver& driver(); //Methods shared by all contexts virtual bool startEngine(); virtual void stopEngine(); // etc... private: //contexts Mechanic* _mechanic; Driver* _driver; // Attributes shared by both mechanic and driver Ignition _ignition; SteeringWheel _wheel; // etc... friend class Mechanic; friend class Driver; }; class Mechanic { public: Mechanic(Car&); virtual ~Mechanic(); //Methods appropriate only for a mechanic's use virtual int transmissionFluidLevel(); //etc... private: Car& _car; //Attributes of a car required only by the Mechanic context }; class Driver { public: Driver(Car&); virtual ~Driver(); //Methods appropriate only for a driver's use virtual void adjustRearViewMirror(); //etc... private: Car& _car; //Attributes of a car required only by the Driver context }; Date: Mon, 12 Dec 1994 21:11:00 -0500 From: Dan Halbach Message-Id: <199412130211.AA25621@access1.digex.net> To: patterns@cs.uiuc.edu Subject: Re: "Multiple Contexts" pattern Raymie Stata writes: > > RE: Pattern: Multiple Contexts > > Question: why not use multiple inheritance to factor the interfaces > here. You give the example: > > > class Driver; > > class Mechanic; > > > > class Car > > { > > public: > > Car(); > > virtual ~Car(); > > > > //Context factory and access methods > > virtual Mechanic& mechanic(); > > virtual Driver& driver(); > > ..... > > }; > > Why not: > > > class Driver { ... } > > class Mechanic { ... } > > > > class Car : public Driver, public Mechanic { ... } > > ? Given a case as simple as the Car example above, you probably could make use of multiple inheritence, but I would still have several objections to it, especially in more complex, "real" situations. For starters, the "IsA" test doesn't hold: A car is not a driver, nor is it a mechanic. These are simply perspectives or contexts in which the car is used. Second, there will most likely be numerous attributes of a car that will be affected by both types of users. Multiple inheritence would require a common base class and virtual inheritence to insure a single copy of each attribute. Third, I can foresee cases where the same terminology is used to mean different things in different contexts. A mechanic's use of the phrase "Turn it over" means to start the engine, while a driver's use of the same phrase would mean something that is likely to raise his insurance rates. :) The point is that having contexts as separate, independent classes allows for each to establish an interface using whatever vocabulary makes sense for that context. Using multiple inheritence would cause a namespace problem. The example I gave of "refactoring" the scrolling matrix is probably a more realistic example. Recall that the refactored class had factors (or contexts) for: cursor movement, row selection, column selection, cell selection, clipboard usage, cell formatting, etc. It would be wrong to say that the matrix "is a" cursor or "is a" cell selection, etc. Furthermore, each of these contexts mutually affects numerous common state variables. And even if they were independent, I would hate to see a class defined as having a dozen or more superclasses! The point about using the context access methods as factory methods to keep the object light weight also holds here. If you use MI to define the scrolling matrix, each instance would have all the overhead associated with the clipboard, even if the client code never made use of the clipboard feature. -Dan Date: Tue, 13 Dec 94 18:56:11 EST From: MICHAEL_WEISS_at_Kan-Software-3@ccmail.mitel.com To: patterns@cs.uiuc.edu, Dan Halbach Subject: Re: "Multiple Contexts" pattern I strongly resonate with Dan's pattern. In our contribution to the Patterns Workshop at OOPSLA 93, Steffi and I describe a pattern that is strikingly similar. I agree that multiple inheritance is no solution to the problem of modeling an object in multiple contexts. We proposed to factor out the context-specific properties into separate objects. We further distinguish objects that represent additional concepts applicable to that context only, and objects (agents) that operate on this information. This latter separation is made, because the same context may be operated on by multiple of these agents. In the example, we were using (taxation), the same context (a balance sheet) representing the object (a car) can be viewed by multiple agents (an accountant, a federal officer). I am including the pattern in its original form with this message. Michael michael_weiss@mitel.com ----------------------- Pattern Name: Context Pattern type: Analysis Short description: When the same object is used in multiple contexts at the same time, its context-specific properties should be factored out into separate objects. Keywords: Contexts, virtual objects, agents, views, environments, domain-specific knowledge and terminology, information sharing Related patterns: Properties (specialization of this pattern) by Ron Casselman, Carleton University, Ottawa Submitted to OOPLSA-93 Pattern Workshop, Washington, by: Stefanie Brueuninghaus and Michael Weiss Lehrstuhl fuer Praktische Informatik I Universitaet Mannheim, Mannheim, Germany Version date: November 11, 1993 THE PROBLEM Description: This pattern should be applied when: - one and the same object is involved in multiple specialized contexts (the contexts are said to share this object), - the domain-specific knowledge is subject to frequent changes and changes independently from the objects it applies to, - this knowledge is typically highly interconnected and needs to be structured into conceptual categories in order to arrive at objects in the first place (this further motivates the separation into virtual objects and agents), and - a special terminology is used within each context that collides (is either inconsistent with or irrelevant to) with the shared language. Example: Consider modeling the depreciation of a car in a tax application. Representing fiscal depreciation within a class car will incur serious problems, whenever the legislation about depreciation is modified, since a change in legislation does not change the identity of the car. In addition, the properties required for the depreciation method cannot be understood outside the taxation domain. Moreover, they are most certainly not innate to the car and owe their very existance to the tax system. Therefore, the residual value of a car (i.e.~its value after depreciation) in the balance sheet, for example, does not reflect the true value of the car in any way. It only has an interpretation when extensive knowledge about the application domain (a given tax system) is applied. Comments: Data and methods that are specific to a particular context should not be made properties of the object. One might consider subclassing as a way of representing these context-specific properties separetely from the object itself. Yet this would not solve our problem, because contexts correspond to different perspectives on the same object. Instead, we argue that the context-specific counterpart of a real-world state of affairs should be modeled separately from the methods applied to it under the outlined conditions. Therefore, knowledge that is not originally part of an object should be modeled separately. Here, what makes an ``original part'' depends on proper judgement, of course. THE PATTERN Description: Proceed by separating: - the real-world object (the object ``itself'') - one or multiple virtual objects for each context (context-specific representation of the object) - agents that operate on the real-world object and on the virtual objects associated with a given context (domain-knowledge applied to the virtual objects) The borderline between what is context-independent and what is context-dependent knowledge cannot be drawn exactly and depends on the application domain. Further elements of the pattern are - views that implement access restrictions to the real-world object, and - environments, which embrace a real-world object, a virtual object and the corresponding agent for a particular context. They can be said to ``perform'' this context. Example: < Figure: Car in the context of taxation: context-specific properties are represented in a context of its own, and thereby both frequently changing and highly interconnected domain knowledge is separated from the object proper. > In the example above, the car itself will be represented by a real-world object. Its fiscal properties, however, its residual value theResidualValue or its (legally) predefined service life thePredefinedServiceLife are captured within a virtual object. The knowledge from the statutes is modeled via an agent and can itself be structured independently from the balance sheet. class car { // real-world object float theInitialCost; string theNumberPlate; carView viewRequest( taxationAgent ) { return carView( theInitialCost ); } ... } class carView { // view definition float theInitialCost; void carView( float someInitialCost ) { theInitialCost = someInitialCost;} ...} class carInBalanceSheet { // virtual object float theResidualValue; // context-specific integer thePredefinedServiceLife; ... } class taxationAgent { // agent float Depreciation( carView ); ... } class taxationEnvironment { // environment carInBalanceSheet theVirtualCar; taxationAgent theTaxationAgent; void assess( car ); ...} Comments: Agents are themselves structured into subagents and sub-subagents (like clusters). Subagents share views with agents higher in the hierarchy. Access to views is exclusively granted by the real-world object. In this approach the domain knowledge can be structured independently from the objects to which it is applied. In the same way access rights can be implemented by an inheritance hierarchy of object identities. But that is a whole pattern on itself. Although the pattern was developed for the legal domain (particularly to model taxation), as reflected by our example, the idea of separating the knowledge such that as much knowledge as possible is factored out into context-dependent objects, is not restricted to this domain. For example, we also looked at the domain of computer-supported cooperative work, in which the goal is to share enough information between people from different perspectives to allow them to collaborate effectively.