390 likes | 404 Views
Object Evolution A chapter from the research work on Applying Aspect-Oriented Software Development to Middleware Frameworks. Tal Cohen Research supervisor: Yossi Gil. Object Evolution. Dynamic reclassification : allowing an object to change its type at runtime. e.g., prince → frog .
E N D
Object EvolutionA chapter from the research work onApplying Aspect-Oriented Software Development to Middleware Frameworks Tal Cohen Research supervisor: Yossi Gil
Object Evolution • Dynamic reclassification: allowing an object to change its type at runtime. • e.g., prince → frog. • Supported by Smalltalk, several others. • Many real-world uses • e.g., State design pattern. • Type safety problems… Prince p = new Prince(); if (…) p → Frog(); p.drawSword(); // potential runtime type error • Our solution: limit to monotonic changes only. • "Object evolution" -- moving down the inheritance tree. • Prince → king is okay! Overview begin::Overview Motivation I-Evolution M-Evolution S-Evolution
Three Kinds of Evolution • I-Evolution: Moving down the inheritance tree. • M-Evolution: Evolving by applying mixins. • S-Evolution: Evolving by applying shakeins. Overview Motivation I-Evolution M-Evolution S-Evolution
Contributions • The case for object evolution. • Why you want your language to support this feature. • Concrete language extension. • The idea is simple, the details are not trivial. • Three kinds of evolution. • A language can support any one, two, or all. • Analysis of potential failures. • What can go wrong, and how to cope with it. • Implementation strategies. • The nitty-gritty details. Overview end:: Motivation I-Evolution M-Evolution S-Evolution
The State Design Pattern Overview begin::Motivation Motivation I-Evolution M-Evolution S-Evolution
State Example: TCPConnection • TCPConnection responds differently to messages (open, close, etc.) depending on its state. • Possible states: Listen, Established, Close. • A switch statement in each method…!? Overview Motivation I-Evolution M-Evolution S-Evolution
TCPConnection using State • Two objects in memory • Repeated delegation code • API must be identical • No state-specific public methods. Overview Motivation I-Evolution M-Evolution S-Evolution
Knowledge Refinement Parsing the source line: InterfaceMethodInvocation MethodInvocation • State pattern not applicable: subclass has many additional methods. • Should the "changeable" class contain the union of all methods of all possible states!? • Creating a new object implies: • Copying existing state (fields), • Chasing references. x.equals(y) Overview ObjectRef x MethodName equals ParamsList (y) Motivation I-Evolution M-Evolution S-Evolution
Lazy Data Structures • A specific case of knowledge refinement. • Example: XML/HTML parsing. • "Varying interface" problem particularly acute. • Should the "Node" superclass include the features of every possible node type? • 80 different node types in XHTML… Overview Motivation I-Evolution M-Evolution S-Evolution
Data Covariance class LinkedList { static class Node { Node next; Object data; … constructor not shown … } Node head, tail; void append(Object obj) { Node newNode = new Node(obj); … add newNode at end of list … } • Because each class needs a different node type, we can't use refinement. • The result: code duplication. Overview Motivation class BidiLinkedList extends LinkedList { static class BidiNode extends Node { Node prev; … new constructor not shown … } void append(Object obj) { BidiNode newNode = new BidiNode(obj); … add newNode at end of list … } I-Evolution M-Evolution S-Evolution
Dynamic Shakeins • Shakeins are a superior alternative to AspectJ-style aspects. • Newer AOP languages support dynamic aspects. • Aspects can be applied/removed at runtime. • Applying/removing a shakein at runtime implies changing the object's class! • This was the original motivation for this work. Overview Motivation I-Evolution M-Evolution S-Evolution
Dynamic Shakeins • For example: given an object of type Account, we wish to enable logging. • From Account to Logging<Account>. • … and back, as needed. • Another example: protect a List object by applying the ReadOnly shakein to it. • Turning it into ReadOnly<List> without changing the list content. Overview end:: Motivation I-Evolution M-Evolution S-Evolution
I-Evolution • Move an object down the inheritance tree. • Syntax: v→T(…) • v is an object reference. • T is a type. • Must be a subtype of v's static type. • … indicates optional parameters. • → can be typed as ->. begin::I-Evolution Overview Motivation I-Evolution M-Evolution S-Evolution
I-Evolution Example 1 class TCPConnection { // Listen state public void open() { … establish connection … this → TCPConnectionEstablished(); } … } • No need for method delegation. • Only one object in memory. • Note that connection objects cannot be "recycled" • No way to reverse state. • Solution: later… Overview class TCPConnectionEstablished extends TCPConnection { public void close() { … close connection … this → TCPConnectionClosed(); } … } Motivation I-Evolution class TCPConnectionClosed extends TCPConnectionEstablished { public void close() { throw new IllegalStateException(); } … } M-Evolution S-Evolution
I-Evolution Example 2 class LinkedList { static class Node { Node next; Object data; … constructor not shown … } Node head, tail; void append(Object obj) { Node newNode = new Node(obj); … add newNode at end of list … } • BidiLinkedList.append is now a refinement of the inherited version. • No code duplication! Overview Motivation class BidiLinkedList extends LinkedList { static class BidiNode extends Node { Node prev; … new constructor not shown … } void append(Object obj) { BidiNode oldTail = (BidiNode) tail; super.append(obj); tail → BidiNode(); tail.prev = oldTail; } I-Evolution M-Evolution S-Evolution
Evolvers • The change from class T to subclass T' might require code execution. • Required for preserving class invariants. • For example: when evolving from LinkedList to BidiLinkedList, we must go over the list and turn each Node into a BidiNode. • And instate the prev pointer while doing so. Overview Motivation I-Evolution M-Evolution S-Evolution
Evolver Example class BidiLinkedList extends LinkedList { static class BidiNode extends Node { Node prev; … } public →BidiLinkedList() { // Evolver if (head == null) return; head → BidiNode(); BidiNode current = head; while (current.next != null) { current.next → BidiNode(); current.next.prev = current; current = current.next; } … • The evolver represents the "delta" between the superclass constructor and this class's constructor. • However, it must take into account the possibility that the object is in a non-virgin state. Overview Motivation I-Evolution M-Evolution S-Evolution • If no evolver is specified, default evolvers can often be deduced.
How Can I-Evolution Fail? • I-Evolution never introduces runtime type errors. • i.e., when an object accepts a message, it knows how to handle it. • Yet the evolution operation itself can fail. • Failure #1:v→T(…), v is null. • Not an "empty operation" because we might expect the evolver code to do something. Overview Motivation I-Evolution M-Evolution S-Evolution
How Can I-Evolution Fail? • Failure #2:v→T(…), and the evolver code →T throws an exception. • Just as new can fail due to exceptions. • Failure #3:v→T(…), but v's dynamic type is not a superclass of T. • Given: • This might fail: Overview end:: Motivation I-Evolution A Note: This is just like the downcast operation (B)a can fail! M-Evolution B C void foo(A a) { a → B(); // But what if ainstanceofC!? } S-Evolution
Mixin Example • This mixin can be applied to any class that implements the List interface: begin::M-Evolution Overview Motivation mixin ReadOnly { inherited public void add(Object o); inherited public void remove(int index); public final void add(Object o) { throw new IllegalStateException(); } public final void remove(int index) { throw new IllegalStateException(); } } I-Evolution M-Evolution S-Evolution
Is "ReadOnly" Useful? • We can now create objects of type, e.g., ReadOnly<Vector>. • However, even the constructor of this class will fail to add items to it! • The mixin looks useless, unless we can turn existing list objects into read-only. • … Evolution! Overview Motivation I-Evolution M-Evolution S-Evolution
Evolving to ReadOnly • ReadOnly<Vector> is a new class, which extends Vector. • So we can use regular I-Evolution with it. • But a reference to List can actually be of type Vector, ArrayList, LinkedList, or any other implementation class! Overview Motivation I-Evolution M-Evolution if (lst instanceof Vector) lst → ReadOnly<Vector>(); else if (lst instanceof ArrayList) lst → ReadOnly<ArrayList>(); else … unbounded number of possibilities … S-Evolution I-Evolution doesn't cut it in this case.
M-Evolution • Syntax: v→M<v>(…) • v is an object reference. • M is a mixin. • v is evolved into the class created by applying M to v's dynamic type! Overview Motivation I-Evolution M-Evolution lst → ReadOnly<lst>(); … works for any implementation of List! … S-Evolution
Idempotent Mixins • M-Evolution can result in infinitely long inheritance threads, generated at runtime. • ReadOnly<ReadOnly<ReadOnly<Vector>>> • To prevent this, if mixin M is marked @Idempotent (or detected to be idempotent), then applying M to M<T> yields M<T>. Overview Motivation I-Evolution M-Evolution S-Evolution
How Can M-Evolution Fail? • Existing failures: Evolving null, exception-throwing evolvers. • Avoided failures:never fails due to incorrect dynamic type! • New failure type: inability to apply mixin. • Final class / final members. • Accidental overriding. Overview end:: Motivation I-Evolution M-Evolution S-Evolution
What is a Shakein? • A Shakein makes a re-implementation of a class. • It does not change the type. • We get a new implementation of an existing type. • Never introduces new non-private members. • A parameterized, generic-like structure. begin::S-Evolution Overview Motivation I-Evolution M-Evolution S-Evolution
Shakein Example Overview shakein Logging { before execution of public methods { Log.log("Began " + methodName); } after successful execution of same { Log.log(methodName + " ended successfully"); } after failed execution of same { Log.log(methodName + " caused exception: " + e); } } Motivation I-Evolution M-Evolution S-Evolution
S-Evolution • Syntax: v→S<v>(…) • v is an object reference. • S is a shakein. • v is evolved into the class created by applying S to v's dynamic type! Overview Motivation I-Evolution M-Evolution obj → Logging<obj>(); … works for just about any object! … S-Evolution
Replacing One Shakein by Another • S-Evolution does not change the object's type. • Only it's (implementation) class. • It is therefore possible to undo S-Evolution without any risk for runtime type errors. • Or, replace one shakein with another. Overview Motivation I-Evolution M-Evolution S-Evolution
State Groups • A set of shakeins can be declared as belonging to the same state group. • If S1, S2S, then: • State transition: Applying S1 to S2<T> yields S1<T>. • And vice versa. • All states are idempotent: Applying S1 to S1<T> yields S1<T>. • Likewise for S2. • i.e., an object can only have one shakein (state) from a given state group. • Note: idempotent mixins can be viewed as one-state groups. Overview Motivation I-Evolution M-Evolution S-Evolution
State Group Example class TCPConnection { // Default = listen state public void open() { … establish connection … this → Established<this>(); } … } • Connection objects can now be recycled. Overview @StateGroup("Connection") shakein Established { public void close() { … close connection … this → Closed<this>(); } … } Motivation I-Evolution @StateGroup("Connection") shakein Closed { public void reset() { this -> Listen<this>(); } … } M-Evolution @StateGroup("Connection") shakein Listen { // Empty: restore default state } S-Evolution
Dynamic Aspects with Shakeins @StateGroup("Log") shakein Logging { before execution of public methods { Log.log("Began " + methodName); } after successful execution of same { Log.log(methodName + " ended successfully"); } after failed execution of same { Log.log(methodName + " caused exception: " + e); } } @StateGroup("Log") shakein NotLogging { // Empty: restore default } Overview Motivation • Unlike some other dynamic aspect solutions: • No boolean test at every method call. • No list of aspects to iterate over. • Zero performance overhead when no shakeins are applied. • However, applying/removing a dynamic aspect (i.e., evolution) can be costly. • Depending on implementation strategy. • We expect this operation to be less common than method invocation. I-Evolution M-Evolution obj → Logging<obj>(); … obj → NotLogging<obj>(); S-Evolution
How Can S-Evolution Fail? • Existing failures: Evolving null, exception-throwing evolvers. • Applying to final class / final members. • Avoided failures:never fails due to incorrect dynamic type! • Never fails due to accidental overriding! • Since no new members are introduced. Overview end:: Motivation I-Evolution M-Evolution S-Evolution
Summary • The quest for dynamic shakeins was successful. • … with several extra benefits. • Language designers can choose any subset of {I,M,S}-Evolution. • Some issues not discussed here: • The fine details of evolvers. • Multi-state objects. • Implementation strategies.