Fix cyclic dependency between protocol and business classes

I'm designing a library for CANopen protocol. The basic CANopen frames are PDO and SDO, where PDO is like UDP (fire and forget) and SDO is like HTTP (request and response). A CANopen device is a PDO client, PDO server, SDO client, SDO server at the same time. The library should implement the protocol so that the user only need to provide the business logic. // canopen.ts class CanOpenFrame { // id, data, etc } type ResponseOrTimeout = CanOpenFrame|undefined; class CanOpenDevice { // user can send a PDO sendPdo(pdo: CanOpenFrame); // user can send a SDO request and expect a response or timeout sendSdoRequest(request: CanOpenFrame, timeout: number): Promise; } // user interface class CanOpenBusiness { // user should define how to process a PDO frame, and how to process a SDO request // CanOpenDevice will call user callbacks at the right time processPdo(pdo: CanOpenFrame): void | PromiseLike; processSdoRequest(request: CanOpenFrame): ResponseOrTimeout | PromiseLike; } I need some advice about the class relationship. The current design is to make CanOpenBusiness abstract, and let CanOpenDevice contain a CanOpenBusiness reference to call user callbacks at the right time. Problem is the user-defined CanOpenBusiness subclass also needs a CanOpenDevice reference to call sendSdoRequest and sendPdo, so there is a cyclic dependency. How should I improve the design?

Jan 16, 2025 - 02:04
Fix cyclic dependency between protocol and business classes

I'm designing a library for CANopen protocol. The basic CANopen frames are PDO and SDO, where PDO is like UDP (fire and forget) and SDO is like HTTP (request and response). A CANopen device is a PDO client, PDO server, SDO client, SDO server at the same time. The library should implement the protocol so that the user only need to provide the business logic.

// canopen.ts
class CanOpenFrame {
    // id, data, etc
}
type ResponseOrTimeout = CanOpenFrame|undefined;
class CanOpenDevice {
    // user can send a PDO
    sendPdo(pdo: CanOpenFrame);
    // user can send a SDO request and expect a response or timeout
    sendSdoRequest(request: CanOpenFrame, timeout: number): Promise;
}
// user interface
class CanOpenBusiness {
    // user should define how to process a PDO frame, and how to process a SDO request
    // CanOpenDevice will call user callbacks at the right time
    processPdo(pdo: CanOpenFrame): void | PromiseLike;
    processSdoRequest(request: CanOpenFrame): ResponseOrTimeout | PromiseLike;
}

I need some advice about the class relationship. The current design is to make CanOpenBusiness abstract, and let CanOpenDevice contain a CanOpenBusiness reference to call user callbacks at the right time. Problem is the user-defined CanOpenBusiness subclass also needs a CanOpenDevice reference to call sendSdoRequest and sendPdo, so there is a cyclic dependency. How should I improve the design?