I have three classes, Chat
, Quest
and Receiver
.
It has three actions (methods) that are needed by another class, i.e. Class Quest
. To avoid tightly coupling the two, I defined a getter (i.e. chatInterface
) on class Chat
to expose the three methods and pass them to class Receiver
. I also used bind
to avoid losing the this
context.
Two of the actions (actionB
and actionC
) were moved to a third class, Quest
. They're also exposed by a different getter in class Quest
.
Is there a way I can combine the three methods (one method returned by class Chat
, and 2 methods returned by class Quest
), and pass them as a single type to class Receiver
, without losing this context.
Here is a minimal reproducible example:
type ChatInterface = {
actionA(): void;
}
type QuestInterface = {
actionB(): void;
actionC(): void;
}
class Chat {
readonly quest: Quest = new Quest();
readonly receiver: Receiver = new Receiver(this.chatInterface, this.quest.questInterface);
get chatInterface(): ChatInterface{
return {
actionA: this.actionA.bind(this),
}
}
actionA() {
}
}
class Quest{
get questInterface(): QuestInterface{
return {
actionB: this.actionB.bind(this),
actionC: this.actionC.bind(this),
}
}
actionB() {
}
actionC() {
}
}
class Receiver {
constructor(private readonly chatInterface: ChatInterface, private readonly questInterface: QuestInterface){}
}
A single object type with all the properties from both object type A
and object type B
would the intersection of A
and B
, which is expressed in TypeScript as A & B
.
(Aside: things get tricky if A
and B
have overlapping or conflicting property keys, but this doesn't happen in your examples. So it would be a digression to go into all the weird things that could happen, and I will ignore that possibility in what follows.)
Conceptually therefore all you'd have to do is take the Receiver
constructor and collapse the two parameters of type ChatInterface
and QuestInterface
into a single parameter of type ChatInterface & QuestInterface
:
class Receiver {
constructor(private readonly chatAndQuest: ChatInterface & QuestInterface) {
}
callActions() {
this.chatAndQuest.actionA()
this.chatAndQuest.actionB()
this.chatAndQuest.actionC()
}
}
Of course that means you have to change the constructor call from new Receiver(this.chatInterface, this.questInterface)
to something else. The easiest way to do this would be to spread both this.chatInterface
and this.questInterface
into a new object:
new Receiver({ ...this.chatInterface, ...this.quest.questInterface });
Normally you need to be careful when copying method-like properties from one object to another because of issues with the this
context. But in your case you've already used bind()
to fix the this
context, so the particular object holding these methods no longer matters.
So this should work as expected. If each actionX
method calls console.log("X", this)
where X
is A
or B
or C
, then you can verify that Receiver
has everything it needs:
const c = new Chat();
c.receiver.callActions();
/*
"A", Chat: {
"quest": {},
"receiver": {
"chatAndQuest": {}
}
}
"B", Quest: {}
"C", Quest: {}
*/