lundi 22 août 2016

How do I improve this object design in Typescript?

I have created a class in Typescript that implements a simple stream (FRP). Now I want to extend it with client side functionality (streams of events). To illustrate my problem, here is some pseudo-code:

class Stream<T> {

    map<U>(f: (value: T) => U): Stream<U> {
        // Creates a new Stream instance that maps the values.
    }

    // Quite a few other functions that return new instances.

}

This class can be used both on the server and on the client. For the client side, I created a class that extends this one:

class ClientStream<T> extends Stream<T> {

    watch(events: string, selector: string): Stream<Event> {
        // Creates a new ClientStream instance
    } 

}

Now the ClientStream class knows about map but the Stream class doesn't know about watch. To circumvent this, functions call a factory method.

protected create<U>(.....): Stream<U> {
    return new Stream<U>(.....)
}

The ClientStream class overrides this function to return ClientStream instances. However, the compiler complains that ClientStream.map returns a Stream, not a ClientStream. That can be 'solved' using a cast, but besides being ugly it prevents chaining.

I don't really like this pattern, but I have no other solution that is more elegant. Things I've thought about:

  • Use composition (decorator). Not really an option given the number of methods I would have to proxy through. And I want to be able to add methods to Stream later without having to worry about ClientStream.
  • Mix Stream into ClientStream. More or less the same problem, ClientStream has to know the signatures of the functions that are going to be mixed in (or not? Please tell).
  • Merge these classes into one. This is a last resort, the watch function has no business being on the server.

Do you have a better (more elegant) solution? If you have an idea that gets closer to a more functional style, I'd be happy to hear about it. Thanks!

Aucun commentaire:

Enregistrer un commentaire