Home > Software engineering >  What's wrong with implementing a class?
What's wrong with implementing a class?

Time:09-05

I'm learning Typescript, and on the Typescript cheat sheet for Classes, I see:

Type and value

Surprise, a class can be used as both a type and a value:

const a: Bag = new Bag();
//       ^-Type    ^-Value

So, be careful to not do this:

class C implements Bag {}

But I don't see what's wrong with a class C implementing a type Bag. Is the problem that a class can only implement an interface, and Bag is assumed to be a class here? The docs on implements only mention interfaces, so that seems to be the case.

If that's the case, then what about what I see in the next column of the cheat sheet?

Common Syntax

class User extends Account implements Updatable, Serializable {
//                                    ^- Ensures that the class 
//                                       conforms to a set of 
//                                       interfaces or types                                      
...

(The comment is specifically pointing to the implements types (Updatable and Serializable), and not the the extends type (Account).)

If a class can only implement an interface, then why does this comment mention "interfaces or types"? Shouldn't it just say "Ensures that the class conforms to a set of interfaces"? Combined with the previous vagueness, this apparent contradiction with the docs is confusing me.

Thanks!

CodePudding user response:

The distinction between "values" and "types" is that values exist at runtime, while types do not. Surprisingly for newcomers to the language, a class is both a value and a type: a value, because it describes a prototype object that will exist at runtime, and a type, because typescript can use its shape for type checking.

This means that the identifier Bar can mean a value or a type, depending on the context in which it is used. If we write:

class C extends Bar {

Bar refers to a value, because an extends clause expects a value, setting up a prototype inheritance between C and Bar that causes C to inherit all properties of Bar at runtime.

However, if we write:

class C implements Bar {

Bar refers to a type, because an implements clause expects a type, expressing that C has the same properties as Bar in the type system, but not setting up inheritance between the prototype objects of C and Bar.

For instance, if Bar declares a function:

class Bar {
  sayHello() {
    console.log("Hello from Bar");
  }
}

the code

new C().sayHello(); 

would compile, but throw an exception at runtime because the property C.sayHello contains undefined, and you can't invoke undefined.

That why it's usually a mistake to write class C implements Bar if Bar refers to a class, because we most likely want runtime inheritance as well.

if a class can only implement an interface, then why does this comment mention "interfaces or types"? Shouldn't it just say "Ensures that the class conforms to a set of interfaces"? Combined with the previous vagueness, this apparent contradiction with the docs is confusing me.

First, there is no contradiction. We are allowed to write implements Bar, even though it may not do what we want.

And the main reason they mention types here is that not all types are interfaces. For instance, you can implement an intersection type:

interface Employee {
    name: string;
    salary: number;
}

interface Student {
    name: string;
    gradeAverage: number;
}

type Intern = Employee & Student;

class John implements Intern {
    name = "John Doe";
    salary = 60000;
    gradeAverage = 3.9;
}

why the cheat sheet authors found it necessary to include a disclaimer about an edge-case that the Typescript compiler itself will catch

Probably because it helps with understanding the error message, and because the compiler can not catch all cases. For instance, if you do:

class Marker {
  // no members, used for instanceof only
}

class C implements Marker {
}

will compile just fine, but new C() instanceof Marker will return false.

CodePudding user response:

Edit: I don't think this is the correct answer.

I was not aware that this example and disclaimer, which appeared under the heading "Type and value" related to the previous sections about private members.

If you try to implement a class having private members (typed private or implemented as private), Typescript will return an error:

class Bag {
  private test = "test";
  #hidden = "hidden";
}

class C implements Bag {
//    ^- Class 'C' incorrectly implements class 'Bag'. Did you mean to extend 
//       'Bag' and inherit its members as a subclass?
//       Property '#hidden' in type 'C' refers to a different member that
//       cannot be accessed from within type 'Bag'.(2720)
  private test = "test";
  #hidden = "hidden";
}

(Although this does raise the question of why the cheat sheet authors found it necessary to include a disclaimer about an edge-case that the Typescript compiler itself will catch. Maybe they wrote the cheat sheet before that error was implemented.)

  • Related