Rewriting Design Patterns with COP Alternative Implementations of Decorator, Observer, and Visitor
Total Page:16
File Type:pdf, Size:1020Kb
Classes as Layers: Rewriting Design Patterns with COP Alternative Implementations of Decorator, Observer, and Visitor Matthias Springer‡ Hidehiko Masuhara‡ Robert Hirschfeld†,§ ‡ Department of Mathematical and Computing Sciences, Tokyo Institute of Technology, Japan † Hasso Plattner Institute, University of Potsdam, Germany § Communications Design Group (CDG), SAP Labs, USA; Viewpoints Research Institute, USA [email protected] [email protected] [email protected] Abstract this paper and present the alternative implementations of the This paper analyzes and presents alternative implementations three design pattern using that mechanism. For every design of three well-known Gang of Four design patterns: Decora- pattern, we present an example, identify shortcomings in a tor, Observer, and Visitor. These implementations are more conventional implementation, show a COP-based implemen- than mere refactorings and take advantage of a variant of tation, and discuss consequences and possible disadvantages context-oriented programming that unifies classes and layers of our solution. to overcome shortcomings in a conventional, object-oriented implementation. 2. Mechanism and Notation Keywords Design Patterns, Decorator, Observer, Visitor, For the implementation of the design patterns that we show Context-oriented Programming, Layers in Section 3, a number of features and mechanisms are re- quired that are not typically found in COP frameworks. We 1. Introduction evaluated some of these features separately in our previous work but not together in this combination. The code list- Software design patterns are reusable blueprints for prob- ings shown in the remainder of this paper are written in an lems that occur frequently in software design. Among them imaginary dynamically-typed, class-based, object-oriented are the most prominent ones by “Gang of Four” [2]. We se- programming language with Java-like syntax. lected three of their design patterns (Decorator, Observer, and Visitor), identified shortcomings in a conventional object- Partial Methods In layer-based context-oriented program- oriented implementation, and present an alternative imple- ming [4], layers can refine methods of other classes. Such mentation using layer-based context-oriented programming refinements are called partial methods. When a layer is ac- (COP). These new COP implementations are not merely tive, the method lookup first looks for a corresponding partial refactorings of their OO counterparts; Decorator and Ob- method in that layer, before falling back to the original im- server differ in their semantics and solve functional inade- plmentation. Multiple layers can be active at the same time, quacies, in addition to making the source code in our opinion forming a layer composition stack. more understandable by reducing object interaction. The new implementations are based on a new concep- Classes as Layers This work builds on top of ideas from tual idea for organizing partial methods. In most COP frame- our previous work which unified layers and classes [7]. We works, partial methods belong to a layer. In our system, there assume that there is no dedicated layer construct. Instead, is no dedicated layer construct and partial methods belong classes and their instances can act as layers. In addition to to classes, allowing arbitrary objects to affect other objects member methods and static methods, classes can provide by intercepting (layering) their methods. The following sec- member partial methods and static partial methods. A partial tions will give an overview of the COP mechanism used in method can refine existing methods or add new methods to the target class. Permission to make digital or hard copies of all or part of this work for personal or Since layers are implemented by objects, arbitrary (layer) classroom use is granted without fee provided that copies are not made or distributed objects can be activated/deactivated. We use the term layer for profit or commercial advantage and that copies bear this notice and the full citation on the first page. Copyrights for components of this work owned by others than the object to denote an object that acts as a layer in a certain author(s) must be honored. Abstracting with credit is permitted. To copy otherwise, situation and affected object to denote the object whose or republish, to post on servers or to redistribute to lists, requires prior specific permission and/or a fee. Request permissions from [email protected]. behavior is adapted. As long as a layer object is active (see COP’16, July 17-22 2016, Rome, Italy paragraph Layer Activation), its partial methods are in effect. Copyright is held by the owner/author(s). Publication rights licensed to ACM. If the layer object is a class, its static partial methods are ACM 978-1-4503-4440-1/16/07...$15.00. http://dx.doi.org/10.1145/2951965.2951968 in effect. If the layer object is a non-class object, its class’s this.b; // -> B.b member partial methods are in effect. this.c; // -> B.c thisLayer Accommodating State Layer objects can refine existing .a; // thisLayer undefined and add new methods to affected objects (via partial meth- /* ... */ ods), but they cannot add additional instance variables. There } are three reasons for this decision: First, it is unclear how } conflicts should be handled, i.e., what if two layer objects de- If the affected object or layer object is a class, then this or fine an instance variable with the same name. Second, adding thisLayer references static methods and fields. Otherwise, new instance variables is difficult from an implementation it references member fields. point of view, because they would have to be stored separate from the other instance variables. Third, such functionality is Layer Object Activation We assume that layer objects can not required for the examples in this paper. Instead, a layer be activated globally, in a block scope, and for a certain object’s class can define its own instance variables that can affected object. In the first case, a layer object is activated by be accessed inside partial methods. If there is a one-to-one calling its activate method and remains active in the entire mapping between a layer object and an affected object, this program until it is deactivated explicitly. In the second case, is as if the instance variable belonged to the affected object. a layer object is activated using with statements taking the Partial methods effectively belong to the target class, i.e., layer object as an argument and followed by a block, within a class different from the layer class. Method calls and which the layer object remains active. In the third case, a instance variable references inside a partial method are by layer object is activated only for a single affected object by default resolved in the target class, but we assume that the calling the affected object’s activate method with the layer thisLayer keyword can be used to access instance variables object as an argument, and the layer object remains active of the layer class. until it is deactivated explicitly. This is contrary to the first • Keyword thisObject1: Lookup in the affected object two cases, where more than one object might be affected (all instances of classes for which a partial method is defined). • Keyword thisLayer: Lookup in the layer object • Keyword this: Try lookup in affected object, then layer def affectedObject = new A(); object def layer = new B(); • No keyword given: same as this // global activation The following listing shows the this and thisLayer in layer.activate(); a simple example with a member method foo and a member layer.deactivate(); partial method bar. class A{ // target class // block scope activation def a; with (layer) { /* active in here */ } def b; without (layer) { /* inactive in here */ } } // per-object activation class B{ // layer class affectedObject.activate(layer) def b; affectedObject.deactivate(layer) def c; There must be a mechanism in place to determine the def A.bar() { // partial method for A.bar precedence of these three activation mechanisms. For exam- this.a; // -> A.a ple, if a layer object is activated globally and then deactivated this.b; // -> A.b for a single affected object, that deactivation should have this.c; // -> B.c precedence over the previous layer object activation. The de- thisLayer.a; // -> error tails do not matter in this work; previous work has proposed thisLayer.b; // -> B.b various mechanisms [6]. thisLayer.c; // -> B.c } 3. Design Patterns def foo() { This section presents three well-known design patterns [2]: this.a; // -> error Decorator, Visitor, and Observer. For every pattern, there is an example implementation using the COP mechanism 1 Not used in this paper, only mentioned for the sake of completeness. described in Section 2. 3.1 Decorator This example is hard to implement with a conventional The Decorator design pattern is used to add responsibilities to Decorator, because the decorated object is referenced by its an object at run-time. A Decorator is typically implemented four neighboring fields. Whenever a field is wrapped with as a wrapper object holding a reference to the original object. a Decorator, these references would have to be replaced by Multiple Decorators can be applied by wrapping the object the decorated object. An alternative implementation could multiple times. use object composition with a set of Decorator items (one One disadvantage of a wrapper implementation is that re- of them being fire) for every field instead of a Decorator: sponsibilities cannot be added or removed dynamically with- Every field would act as a container for items and references out loss of object identity. After wrapping a Decorator around would always point to fields instead of field wrappers. Field an object, the resulting wrapped object has an object identity methods would delegate to item methods internally. different from the original object (object schizophrenia [5]). Consequences A COP Decorator implementation can dif- If that wrapped object should be used everywhere the original fer from a conventional object-oriented implementation in a object is in use, it has to be replaced one by one.