Inheritance? In Apama?

One of the common questions we hear about Apama, and EPL, is: “How can I create derived classes?” The naive answer is “you can’t” – but let’s dig a little deeper.

First off, a little bit of background. Apama EPL doesn’t have classes – it has events. An event can have both data and methods (actions) but it is not a class. Among other things, this means that you can’t directly extend or inherit an event. You can do something like:

However, this approach means that ExtendedEvents are not DataEvents – unlike inheritance. It also makes it harder to have configuration-based behaviour – I cannot create a different behaviour at run-time.

By using action variables, instead of actions, we can overcome this. The approach is:

  1. Write the interface for your “class”.
  2. Create implementations.
  3. Write a factory class, that sets up the members on the interface and returns the populated interface.

Let’s take a look at a sample. This is a (slightly enhanced) version of “Hello World”. The first piece to look at is UsingTheSample.mon. This is how we actually use an interface like this.

In the onload() action, we call the factory’s static create method, which gives us a completed interface. The optional return type is there just in case there’s a problem, for example. This sample has very naive error handling, but at least it does something.

OK, we’ve got an interface, with an implementation behind it. Note that we don’t need to know what the implementation is to use it: this is a classic example of implementation masking, just like in a truly object-oriented language. Let’s interact with it: we tell the code to introduce itself, waiting for a user name, and then greeting the user by name. Some sample output:

Now let’s take a look at how this works. The interface file first (SampleInterface.mon):

This one’s very simple. All it does is define the actions signatures for the “object”: anything else is handled elsewhere.

Now we’ll consider the Factory:

This wraps up the various implementations – in this case, English and French – and makes them available for creation. We can create an instance by specifying a language – and if there’s no implementation for the desired language, we can return an empty optional – this is an approximate equivalent of NULL in other languages. Note that, if you wanted to, you could implement a default behaviour here – for example, we could write the interface to return the English-language version unless a recognised language was requested.

This create method constructs a SampleInterface object, and defines the implementation of each of the interfaces. Note that, if an interface is unset, you can try calling it – but it will cause a stack dump, so be careful here.

Finally, there’s the implementation itself. All this does is take the action variables in the Interface, and create actions matching those signatures. You can think of this as providing an implementation for a (pure) virtual class.

So there you have it – an interface, allowing you to write code without caring about the implementation; and a factory, to create instances of that interface with whichever implementation you choose. There are some other things you can do with that pattern – such as standard functionality that’s the same across all implementations, or using optional types to support variable overriding, but those are more detailed than we’re going to get into here.

You can download the project here.

— Henry Lockwood