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
andinterface
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.
function identityByClass(value: MyClass): MyClass {
return value;
}
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.
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:
MyClass
?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:
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())
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With