3

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.

Nickofthyme
  • 3,032
  • 23
  • 40
  • Generally if use interface , You don't care `MyClassPlus` have more function right? , Becase system is depency interface not `MyClassPlus`. if yes you care about that , it problem maybe is how to "desigen" a more gerenic interface for your system. – TimChang Dec 17 '19 at 02:26
  • Hey @TimChang! Right That's the question. Once I have added to the class on top of what is defined on the interface, I must use the class if I want access to those added methods or properties. The question is whether to sync the interface or just use the class as the type for the augmented class. – Nickofthyme Dec 17 '19 at 03:09
  • Possible you have diffrence behavior at same time? if that's your case , you can define two interface to handle that like `void DoSome(T myclass) where T : IMyClassA , IMyClassB ` because you split interface , so you can don't sync all MyClassPlus methods/properties to one interface. – TimChang Dec 17 '19 at 03:31

2 Answers2

2

One benefit you gain by using an interface to define types is that the interface itself is eliminated after compilation. This reduces the overall bloat of your application, which is a strong argument for using an interface in this situation.

You might find this useful: https://jameshenry.blog/typescript-classes-vs-interfaces/

  • 1
    But in this question, the choice is between having a class, vs. having a class *and* an interface. – kaya3 Dec 17 '19 at 03:30
2

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())
ford04
  • 66,267
  • 20
  • 199
  • 171
  • 1
    thanks for all that information, and all the links! Especially about the interface-segregation principle. Interesting point about the `static` properties too. – Nickofthyme Dec 17 '19 at 16:12