Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class vs Interface as type in Typescript [duplicate]

I came across this recently where I am not sure whether to use the interface or class to define a particular type.

Note: This question is NOT asking about the difference between a class and interface

For example, given this class and interface

interface IMyClass {
  foo: string;
  bar: () => string;
}

class MyClass implements IMyClass {
  foo = 'foo';
  bar() {
    return 'bar';
  }
}

I would use either the class or interface as the type in a function argument.

Option A - Use class as type

function identityByClass(value: MyClass): MyClass {
  return value;
}

Option B - Use interface as type

function identityByInterface(value: IMyClass): IMyClass {
  return value;
}

From my point of view, I think either is fine but I prefer to use the class to avoid syncing all methods/properties on the interface. I see the interface as only a template/contract that a class must abide by.

However, most times, in my case, the class often adds more methods/properties that are not on the interface definition.

Case in point, this class below.

class MyClassPlus implements IMyClass {
  foo = 'foo';
  bar() {
    return 'bar';
  }
  doMore() {
    console.log('more stuff here!');
  }
}

In which case I could no longer use the two interchangeably.

Any links to best practices would be nice too. Thanks.

like image 606
Nickofthyme Avatar asked Sep 19 '25 11:09

Nickofthyme


1 Answers

I am not sure whether to use the interface or class to define a particular type.

Both classes and interfaces create a type, so in principle they can be used interchangeably. As you pointed out, an interface is like a public contract, whereas a class implements this contract.

But consider following cases:

  1. What, if you need to change MyClass?
  2. MyClass exposes more implementation details by revealing, it is a class type.

A refactored, new type signature of MyClass may not be valid to be used as function parameter in function identityByClass anymore. So you would need to refactor all consumers - duh... With a separate type interface, that is more unlikely to happen.

Example for point 2: a client could come up with the idea of getting static properties of MyClass (interfaces don't make it obvious by rather hiding implementation details):

class MyClass {
  static baz(){}
  foo = 'foo';
}

function identityByClass(value: MyClass) {
  console.log((value.constructor as any).baz) // function baz()
  // there we go. do something with static baz() method of MyClass
}

By the way: It is not only about interfaces and class types. Type aliases offer powerful features like mapped or conditional types, union etc. and can be used in favor of interfaces most times.

However, most times, in my case, the class often adds more methods/properties that are not on the interface definition.

You do not need to sync class and interface types. In order to keep it DRY, two options come to my mind:

  1. Interfaces can extend class type definitions, to get rid of redundant declarations.
  2. Pick only those class properties, that are required to the consumer.

I would favor the second point over all other alternatives, as it follows design principles like Information Hiding or Loose coupling. Example:

class MyClass {
  foo = 'foo';
  tooMuchDetail = "hide me"
}

// just pick, what we need (foo here)
type OnlyNeededProps = Pick<MyClass, "foo">

function doSomethingWithFoo(a: OnlyNeededProps) {
  a.foo
  a.tooMuchDetail // error, unknown (OK)
}

doSomethingWithFoo(new MyClass())
like image 148
ford04 Avatar answered Sep 22 '25 19:09

ford04