Suppose we've put together a portion of a design in the form of a role model (a la Reenskaug) or a collaboration diagram. What we want to do is define a class (or tagged type in Ada parlance) that implements one or more roles. We can choose to have some behavioral implementation associated in general with the role, or keep the whole thing abstract as in a Java interface.
So, suppose we look at the publish-subscribe / observer-observable pattern. In Ada style we'd likely put the entire pattern into a single package, since it doesn't make a lot of sense to define one with out the other. (I'm hoping my mailer preserves indentation here):
package Publish_Subscribe is -- "Push" information associated with observer notification, -- basically a "command" subpattern: type Event is abstract tagged limited private; type Event_Ptr is access all Event'Class; procedure Action(This: access Event; Subject: access Observable'Class) is abstract; -- The publisher/observable interface / role: type Observable is abstract tagged limited private; type Observable_Ptr is access all Observable'Class; procedure Add_Observer(This: access Observable; Obs: access Observer'Class); procedure Delete_Observer(This: access Observable; Obs: access Observer'Class); procedure Notify_Observers(This: access Observable; Evt: access Event'Class); -- The observer interface / role: type Observer is abstract tagged limited private; type Observer_Ptr is access all Observer'Class; procedure Update( This: access Observer; Source: access Observable'Class; Evt: access Event'Class) is abstract; private -- Information necessary for the compiler to physically handle the defined types/classes: type Event is abstract tagged limited null record; type Observable is abstract tagged limited record -- Head of a naive list implementation: Observers: Observer_Ptr; end record; type Observer is abstract tagged limited record -- Pointer to next observer in the naive list implementation Next_Observer: Observer_Ptr; end record; end Publish_Subscribe;
Note that the approach taken here is to provide an implementation of common parts of the role. Therefore, the various methods other than Action for the Event type, and Update for the Observer are not abstract - they're involved with a common implementation used for the Observer/Observable pattern. Also note that the three typed defined are all abstract - one never creates a role "object" directly. I've left out the implementation of the concrete methods here.
Now, supposing we want to define a class or type of object that implements the observer role. First we create a concrete observer class relative to object we wish to create:
-- Forward reference to the type implementing the observer interface:: type Implementing_an_Observer; -- Just a way to look at the implementing class as an observer. -- The "access" parameter "This" allows access to other views and fields of an instance of the class: type Observer_Role(This: access Implementing_an_Observer) is new Observer with null record; -- Provide an implementation of the abstract update method: procedure Update(This: access Observer_Role; Source: access Observable'Class; Evt: access Event'Class) ; -- Full definition of the implementing type: type Implementing_an_Observer is record -- view allows looking at a type instance as an observer: Observer_View: aliased Observer_Role(Implementing_an_Observer'access); -- Other fields, possibly providing other role views end record;
Having declared an object:
we can now look view it as an observer like so:
Any number of subfields can provide different views of the object corresponding to multiple inheritance, with the concrete type for the view either inheriting methods or overriding them. A fully abstract role description corresponds to a Java interface.
This IS a bit cumbersome when you first run into it, but it's pretty straightforward once you get the idea, and it's extremely flexible. It also avoids the multiple inheritance diamond problem since each view has its own associated members.
And, as I mentioned, if you just want to do a decorator or similar mix-in, a generic with a formal that allows anything inheriting from some type provides a simpler mechanism for such more common uses of multiple inheritance.