Ruby Object Teams (ROT) ======================= This package enables the ObjectTeams paradigm to the ruby language. For detailed information and guide see www.objectteams.org . Short Intro =========== The ObjectTeams paradigm allows the programmer to encapsulate the interaction of a set of objects (roles) into a compound object (the team). Aspects can be implemented as roles and are weaved into domain classes via a connector at runtime (runtime weaving). These aspects can be explicitly activated and deactivated. Definition of Ruby Object Teams =============================== A team definition is an outer class that must inherit from Team and bundles a set of role-classes that are members of the team. Teams can be refined by inheritance. Each role-class is an usual ruby class that encapsulates a specific state and behaviour. Example: class ObserverPattern < Team class Observer def update(observable) #code omitted end end class Observable attr :observer def add_observer(observer) #code omitted end def remove_observer(observer) #code omitted end def notify #code omitted end end end * Expected Methods ------------------ To define methods, that have to be bound by the baseclass (similar to abstract methods, that have to be implemented in a derived class) there is a new keyword: expected. Expected methods can be used in other methods, as were implemented, but are bound by a connector to a base. Example: class Observer expected :update def test update() end end * Connector definition ---------------------- a)Player definition A connector binds a defined team to a set of domain classes. This is done by a list of played_by clauses. b)Join-Point definition Join-points are existing methods of the domain class or new methods of the role-class that are accessible via the base class. There are several binding schemes: +before(rolemethod, basemethod) invokes the specified rolemethod each time before the basemethod is called. +after(rolemethod, basemethod) invokes the specified rolemethod each time after the basemethod is called. +replace(rolemethod, basemethod) invokes the specified rolemethod instead of the basemethod. (The basemethod is accessible in the rolemethod via base()) +callin(rolemethod, basemethod) register the specified rolemethod under a given name (basemethod). The basemethod must not exist. c)Expected method binding Expected methods must be bound by the baseclass. If the baseclass implements the requested action in a method with a different name, the role-call must delegated to the right basemethod. This is done by the delegate_to modifier: +delegate_to(rolemethod, basemethod) invokes the specified basemethod each time the specified rolemethod is called. Example: -------- class ButtonObserver < ObserverPattern play_role(Observer, Window) { delegate_to("update", "updateWindow") } play_role(Observable, Button) { after("notify", "pressed") } end Means: -after Button.pressed is called, Observable.notify should be invoked -each call to Observer.update shall be delegated to Window.updateWindow * Connector activation/deactivation ----------------------------------- The defined interaction of a connector must be activated/deactivated explicitely. There are two different ways to archieve this: Static Connector ---------------- The simplest way to activate a connector is to choose a static connector. Think of a static connector as a source code weaved connection, that is always available without explicit activation. Example: Team.activate_static(ButtonObserver) Dynamic Connector ----------------- The more advanced connector is a dynamic connector. A dynamic connector is an instance of a specific team and defines a context, in which the specific action is activated/deactivated. Example: observer = ButtonObserver.new() observer.while_active { #some action while the connector is active } It is possible to have several instances of ButtonObserver (for example if you want to have different notification-channels). * Syntax -------- class < Team class end end class < play_role(, ) { before(, ) after(, ) replace(, ) delegate_to(, ) } end There are possibilities, to bind a rolemethod to a list of basemethods. The definition of such binding can be: -a list: [, ] eg: after("update", ["set_xpos", "set_ypos"]) -a regular expression: /set.*/ eg: after("update", /set.*/) Example Code ============ The whole example discussed above is found in the sample/observer directory. Limitation ========== -New keywords introduced: * expected * base Don't use this keywords in your code. -The callin method dispatching relies on ruby's method_missing feature. You can't use the method missing feature in base-classes. -Each roleclass get's a new instance attribute: @base. Be aware to not use this attribute in mistake. Implementing Issues =================== Logger ------ The objectteam uses a simple logger to log ROT-action. The logger is activated by the debug-option of ruby (enable with -d). The logger has adaptable parameters: -ActualLevel (the debug level [0=nothing to 5=everything]) -MethodInfo (display method, which generates log-entry) and can have various Appender. Current implemented: -FileAppender -ConsoleAppender (default) Set appender with: Logger.set_appender(appender) StackTrace ---------- If an exception is thrown and transported via call-bindings, the stack trace is adapted, to hide the binding facilities. In most cases this is useful to see only domain specific trace. The trace remain unchanged, if the debug-option of ruby is enabled (-d). ot_assert --------- ROT evaluates a lot of assertions, by using ot_assert. If speed is an issue the asserts could be disabled. AUTHOR ====== Matthias Veit. April 2002. email: Matthias Veit