Search code examples
oopdartinheritancepolymorphismabstraction

Dart Abstraction & Polymorphism conundrum


Please find the below dart snippet

class A {
  String m1_a() {
    return "A -> m1_a()";
  }
}

abstract class B extends A {
  @override
  String m1_a();
}

class C extends B {}

void main() {
  B c_b = C();

  print(c_b.m1_a()); //"A -> m1_a()"
}

Q. Why C is not enforce to override the abstract method m1_a() define by class B ?

B is declared as Abstract class so ideally m1_a() is also abstract method for child C !


Solution

  • Dart class declarations introduce both a class (which can be extended and instantiated) and an interface (which can be implemented). Unlike other similar languages, Dart doesn't have a separate interface declaration, you can extract an interface from any class. A non-abstract class must always have an implementation for its own interface.

    A concrete method in a class declaration, like int foo() => 42;, introduces both a method signature to the declared interface and an implementation to the declared class, and since they match up, the class does indeed implement the interface.

    An abstract method, like int foo();, only adds a method to the interface, it does not affect the class. If the class is abstract, and has no implementation of foo, then that's perfectly fine.

    In this example, the B class declaration declares String m1_a(); which adds an interface signature for the interface of B. That has absolutely no effect since it also inherits the same signature from the interface of A. Further, the B class inherits an implementation of m1_a from the class A, so it successfully implements its interface, and all is well.

    TL;DR: The abstract method has no implementation, and has no effect on implementations, only on interfaces, so it doesn't shadow the inherited concrete method implementation from A.

    The fact that an abstract method doesn't shadow an inherited implementation, can be used to update documentation in a subclass without affecting the implementation. Example:

    class A {
      /// Does something using A.
      int foo() => something + _private();
    
      int _private() => _somethingSpecial();
    }
    class B {
      /// Does something using B.
      int foo();
    
      int _private() => _somethingSpecialForB();
    }
    

    Here you change the behavior of foo in B because you change how the private function it depends on works, but you don't change the shared code in A.foo. You can still update the documentation of B.foo to say how it differs.