Abstract Classes

Topics: General, Language Specification
Jul 14, 2013 at 2:39 AM
This bug asked for support for abstract classes. The bug was dismissed as not being useful or lacking appropriate justification.

This feature is important for me. Without it, it's not possible to statically type check implementations of the Template Method pattern. Most of my projects (even the plain JavaScript ones) use this pattern.

Here is an example from some of my code. I have a TemplateView that implements the Backbone render method by rendering a template. TemplateView is abstract, and requires its subclasses to define a getTemplate method that returns the name of the template to use.
class TemplateView extends Backbone.View {
  render() {
    var template_name = this.getTemplate();
    this.$el.html(_.template($(template_name).html())(this.toJSON()));
    return this;
  }

  // the context to use for rendering the template
  toJSON() {
    return {};
  }

  // the name of the template to use
  getTemplate() : string {
    throw new Error("You must implement getTemplate");
  }
}

class MyView extends TemplateView {
  getTemplate() {
    return '#my-template';
  }
}
I feel like the presence of the getTemplate method on subclasses of TemplateView should be statically checked.
abstract class TemplateView extends Backbone.View {
  abstract getTemplate() : string;
  // ...
}

// compile-time error
class MyView extends TemplateView {
  getTempalte() { // misspelled the method name
    return '#my-template';
  }
}

// ok
class MyView extends TemplateView {
  getTemplate() {
    return '#my-template';
  }
}

// also ok
abstract class LoggingTemplateView extends TemplateView {
  render() {
    console.log('rendering template');
    return super.render();
  }
}
TypeScript is supposed to perform static type checking on JavaScript, and I think abstract classes are an important part of that. The throw new Error workaround defeats the purpose of type checking, by deferring a statically-detectable error to run time.
Jul 15, 2013 at 3:44 PM
I would very much like to see support for ABCs in TypeScript. Various comments have been made that abstract classes are not something that is idiomatic in Javascript, but Typescript is supposed to be a more "OO" way to end up with Javascript and abstract classes are very useful when doing object-oriented development. It saves us having lots of error checking done by throwing an exception when a base class method that shouldn't need defining is called (see previous change request and discussion).

Could we have them, please?
Aug 12, 2013 at 2:01 PM
I'd like to add another (not so obvious) reason for supporting abstract classes: refactoring.

If you have:
class Abc {
  frobnicate():void { throw "Abstract method"; }
}

class Concrete extends Abc {
  frobnicate():void { console.log ("splines completely reticulated"); }
}
and then you rename frobnicate() to reticulate() in the Concrete class, you get no compile-time error.

However, if the Abc would be abstract, you could not have tripped over this.

This is also an argument for supporting abstract for methods as it is for supporting override.
Aug 12, 2013 at 2:45 PM
This is useful for me as well. I have base classes that have no purpose by themselves and need to be derived from.
Aug 24, 2013 at 3:15 AM
This is just an indisputable feature as long as TypeScript goes to become true application-scale language. Along with strong support for library projects that will let us develop re-usable frameworks.
Oct 21, 2013 at 1:35 AM
mtebenev wrote:
This is just an indisputable feature
Agree 100% - any OOP language should have this feature.

Adding support for this should be fairly straight forward - all it needs to do is throw a compiler error if somebody attempts to create an instance of an abstract type.

Seems kind of absurd to reject that request.
Dec 10, 2013 at 4:13 PM
I'm giving this a +1. Having abstract classes / methods are essential when building large-scale applications. I'm now using the "throw" approach, and that works quite well, but it's messy.

Are there any good reasons why abstract classes / methods are NOT implemented?
Dec 13, 2013 at 9:37 AM
I must agree, unless this is really, really hard to do it should be part of the language - most important for me was the argument for refactoring.
Dec 13, 2013 at 1:39 PM
Edited Dec 13, 2013 at 3:08 PM
One reason abstract classes are not implemented may be because abstract classes can be considered an anti-pattern.
An abstract class is both an interface and a partial implementation. You should separate this into a dependency and an interface.
You can solve this by having good dependency injection.

Rather than:
abstract class A {
  abstract function implementMe() {}
  function foo() {
     this.implementMe();
  }
}
class B extends A {
  function implementMe() { console.log("o hai"); }
}
var b = new B();
b.foo();
You could instead have:
interface implementMe {
  function bar();
}
class A {
  private im;
  constructor(implementMe im) { this.im = im }
  function foo() { this.im.bar() }
}
class B implements implementMe {
  function implementMe() { console.log("o hai"); }
}
var a = new A(new B());
a.foo();
Class A is still an abstract class in the sense that it needs an implementMe to do its work but now the dependency is clear.

edit: in gavinwahl's example you can have a similar construction where TemplateView has a TemplateSupplier dependency which can be used in getTemplate.
Dec 13, 2013 at 4:42 PM
I think "function implementMe()..." should be "function bar()..." in your example...?
Dec 13, 2013 at 4:54 PM
Edited Dec 13, 2013 at 4:56 PM
I'm not sure I'd call it an "anti-pattern" either, though, really. That is an alternative way to implement the functionality, but if you are not careful you end up implementing multiple hierarchies using your technique, one for each type of behaviour you want to inject. These all add considerably to the complexity of the code (just compare the complexity of your two examples). [Note: I appreciate that dependency injection is a very powerful technique but it is often overkill for many straightforward hierarchies unless it is needed for some other purpose, such as testing, run-time config, etc.]

I find that a regular occurrence in code is a hierarchy of types, for whatever purpose, with a common interface, but which also often share some implementation between closely-related types. An abstract class, placed in-between the interface and the derived classes is often a very simple solution that avoids duplicated code between classes, or the alternative of lots of helper classes with delegating functions all over the place. C++, C#, Java, etc., all have (differing) support for abstract classes and they are widely used.

And the implementation of this feature is also not complex: a keyword to mark a class as abstract and a check on instantiation that the class is not abstract.
Dec 13, 2013 at 4:56 PM
Creating another object to use from another object doubles the number of objects - and it was the TypeScript team itself that frowned upon this for the private variables discussion,
Dec 13, 2013 at 5:03 PM
rb126 wrote:
"solution that avoids duplicated code between classes, or the alternative of lots of helper classes with delegating functions all over the place...
...
And the implementation of this feature is also not complex: a keyword to mark a class as abstract and a check on instantiation that the class is not abstract."
I totally agree. As an API developer myself, I routinely create "helper methods/functions" so users can just call the inherited method/function as needed. It makes it easier on end users since they don't have to implement EVERY method/function - only what they need, if any at all.

I also agree, this should be very simple to implement - a simple compiler error on implementing an abstract type on its own.
Feb 13 at 10:14 AM
+1
May 24 at 5:38 PM
This is also usually solved with mixins in JS land. Instead of increasing the depth of your inheritance hierarchy, you use the fact that prototypes are mutable and inject them the desired functionality. This is similar to dependency injection. It'd be nice to see a JS-style approach in TypeScript.

For similar ideas see Traits: http://wiki.ecmascript.org/doku.php?id=strawman:trait_composition_for_classes and http://soft.vub.ac.be/~tvcutsem/traitsjs/.
Jun 18 at 1:56 AM
Edited Jun 28 at 11:11 AM
FritsvanCampen wrote:
One reason abstract classes are not implemented may be because abstract classes can be considered an anti-pattern.
I think calling an abstract class an anti-pattern is a bit extreme. Sure, you can use interfaces, but they do not allow implementation of behavior. I now either have to have duplicated behavior in child classes, or have a base class with this behavior which, by pure use case, should not be instantiated but can be. Both of those seem worse to me.
Jun 18 at 8:28 AM
Edited Jun 18 at 8:29 AM
One reason abstract classes are not implemented may be because abstract classes can be considered an anti-pattern.
What @FritsvanCampen demonstrated is an anti-pattern - but, as others pointed out, that's not the intended (or only) use case for abstract classes.

Actually, you can think of abstract classes as being classes just like any other class:

Think of "abstract class" as a class just like any other, but flagged with a restriction that says, "this is a base class for other classes, and you should not create an instance of this".

Think of abstract methods as being simply methods with empty implementations, but flagged with a restriction that says, "this method has not been implemented, a subclass needs to override the implementation".

As such, the "abstract" modifiers are mere flags - metadata that the developer can add to instruct the compiler to make some simple extra assertions. Don't overthink it.
Jun 19 at 1:37 PM
@mindplay, Well put.
Jun 22 at 4:06 AM
Dependency injection is great and all, but: 1. No way is type-abstraction an anti-pattern. 2. when a type-system is not strong enough to express distinctions, abstract types help. e.g. I may have some set of strings that are indexes one table, but not for another. Abstract types let you distinguish these different kinds of indexes and gives you compile-time errors if you mix them up by mistake. All strongly-typed languages support them, and having the option to use them would hugely valuable. I have a team of 10 engineers and it would be very valuable to us.
Jun 28 at 11:10 AM
I think it's a pretty compelling argument, given all the discussion here. Perhaps someone up top doesn't agree?

@MindPlay, while the given example may have been an anti-pattern, that cannot be means for ruling it out completely. Regex is abused on a daily basis (.."how can I build a complete HTML parser using regex?") on a daily basis. Anyhow, good to discussion! Great knowing that all this can help shape the future of TS :)
Jul 9 at 9:53 AM
+1 from me too.
Jul 11 at 12:17 PM
Abstract classes will contain abstract methods. What if I wanted not to not require overriding and provide fall-back behaviour? Would I use virtual? Given that allowing overrides is the current behaviour, a sealed keyword to disallow overriding on particular methods would be more useful.

I like the idea of including an overrides keyword, to allow refactoring of overridden base-class methods. (Mentioned above by b100dian). Also, currently, Visual Studio intellisense doesn't show base-class prototype methods when writing new ones (/overriding) -- you kind of just hope you spelt it right (a JavaScript feature TypeScript was meant to eliminate)

And to throw a curveball into the mix, I'd like to express my preference for the Visual Basic keywords in this field - they are much more descriptive:

C#: abstract
VB Class: MustInherit
VB Method: MustOverride

C#: virtual
VB: Overridable

C#: sealed
VB Class: NotInheritable
VB Method: NotOverridable

And just to be picky:
C#: override
VB: Overrides

Obviously, camelcase would look odd, so they would need to be lowercased like instanceof and typeof.
Jul 29 at 10:18 PM
This issue is moved to new GitHub repository https://github.com/Microsoft/TypeScript/issues/6. Lets discuss it there.