Search code examples
darttypesinterfacecasting

How to access a field on an object that has been passed using an interface, when the interface does not define that field


I am using an external API, which I cannot modify and want to access a field on an object that is not defined by its interface.

Interface IFoo does not define the field headers. Both Foo and Bar implement IFoo and both have an additional field headers of type String.

This is working in Dart, but is far from elegant:

void doSomething (IFoo data) {
    String headers;
    if (data is Foo) {
      headers = data.headers;
    } else if (data is Bar) {
      headers = data.headers;
    }
}

This does not work in dart:

void doSomething (IFoo data) {
    String headers;
    if (data is Foo || data is Bar) {
      headers = data.headers;
    }
}

Is there a more elegant way than the above first example?


Solution

  • Is there a more elegant way than the above first example?

    Taking a look at a higher level of the whole picture, the "elegant way" that I'd suggest in this case is following the Interface Segregation Principle.

    It's clear that the declaration of the headers property is common for some of the types (such as Foo and Bar), however, if declaring it in IFoo does not make sense (for code design reasons, such as there are other types of IFoo don't need to have the headers declared), it's still sensible to have a common type for all subtypes that have the headers property.

    In Dart, using mixin would be a proper way to achieve it. Example:

    abstract class IFoo {}
    
    mixin MixinFoo {
      String get headers => 'MixinFoo';
    }
    
    class Foo with MixinFoo implements IFoo {
      @override
      String get headers => 'Foo';
    }
    
    class Bar with MixinFoo implements IFoo {
      @override
      String get headers => 'Bar';
    }
    

    Note that Foo and Bar are still IFoo types, additionally, they are MixinFoo as well! This means that they have the headers property without mixing the logic of being IFoos.

    Therefore, you are able to cast the given value, as:

    void doSomething (IFoo data) {
        String headers;
        MixinFoo? dataMixin = (data is MixinFoo ? data : null) as MixinFoo?;
    
        // if the data is not a MixinFoo, then dataMixin will be null
        if (dataMixin != null) {
          headers = dataMixin.headers;
        }
    }
    

    Or (since we are talking about an "elegant way" 🙂):

    void doSomething (IFoo data) {   
        try {
            String headers;
            final dataMixin = data as MixinFoo;
            headers = dataMixin.headers;
        }
        on TypeError catch(_) {
              // fallback, if the data is not a MixinFoo...
        }
    }
    

    Any MixinFoo type will be castable now, it makes more sense from a code design perspective.