2

I have the following pattern repeating itself in multiple places:

abstract class Database {
  void connect() {
    this.setStatus( CONNECTING )
    try {
      await this.realConnect()
    }
    catch (error) {
      this.setStatus( ERROR )
      throw error
    }
    this.setStatus( CONNECTED )
  }

  abstract void realConnect()
}

class Sqlite3Database extends Database {
  override void realConnect() {
    await someSqlite3Library.connect(...)
  }
}

It works well, and allows the subclasses to not worry about common functionality like validating input, updating the status, or emiting events. But it leaves me with two methods for everything; method() and realMethod(). It doesn't look as bad here when I have only one connect method, but when in reality I also have disconnect, reconnect, write, read, and more, it gets very weird to write two methods for everything.

Is this a common pattern? Is there a name for it? Is there a better way to accomplish this behaviour?

2 Answers 2

2

It's a variation of the template method pattern.

AbstractClass {
    void method() {
         methodImpl();
    }

    abstract void methodImpl();
}

ConcreteClass extends AbstractClass {
    void methodImpl() {
        // do stuff
    }
}

This pattern can also be used if the main work is done by the superclass, and the specialized class only needs to implement some hook method (e.g. onConnected).

Note that it sounds like you could also try different patterns. Maybe you should instead use a decorator that calls the wrapped concrete database (you could also have multiple levels of different behavior, which is much more easily composed than via inheritance).

For decorator:

interface Database { void connect(); }

ConnectionDatabase implements Database {
    // pass in SpecializedDatabase
    ConnectionDatabase(Database actualDB) {...}
    void connect() {
        this.setStatus( CONNECTING )
        try {
            await this.actualDB.connect()
        }
        this.setStatus( CONNECTED )
    }
}

SpecializedDatabase implements Database {
    void connect() {
        // do stuff
    }
}

You could also have a look at aspect oriented programming to e.g. wrap some cross-cutting processing.

Edit: One more suggestion is the chain of responsibility. It could have different aspects of managing the connection chained one after the other (more suited towards sequentially executing the different aspects, not really wrapping around the follower). It is not really suited for what you have, but may give you something to consider for certain parts of your system.

1

Is there a better way to accomplish this behaviour?

They say favor composition over inheritance. A pattern that might give you more flexibility and still do everything you're doing here is the Decorator Pattern. You'd build it like this:

Database db = new Sqlite3Database(
    new dbValidator(
        new dbEventEmiter(
            new Database()
        )
    )
);

Done this way you're free to add, remove, rewrite, or suppress any behavior in any of these layers. You're also on the hook to do a lot of keyboard typing because you still need to write code to delegate. But you have the power to fiddle both as a call comes in and as it goes out.

Over engineered? Most likely. But in addition to being able to add a new layers later you're also free to squish layers together later if you change your mind. So it's only as over engineered as you want it to be. Separate what changes from what stays the same.

The trick here is each of these implements the same interface that it accepts in it's constructor. Nothing that talks to this cares which layer it's talking to. Any layer could be the outer layer. Changing their order changes the order the behaviors happen in.

The point is you can swap behaviors easily. Need event emitting to behave more like messages? Write up a dbMessageEmiter and swap it in.

2
  • 1
    While this is irrelevant to the point you are making, but more aligned with the problem of the OP: Would the code example not have the most specialized Sqlite3Database as the innermost database, with all decorations just adding some around-behavior to it?
    – sfiss
    Mar 1, 2023 at 16:24
  • 1
    @sfiss I was going back n forth on exactly that. Depends on if you want to put it's specialized behavior before or after the return. I decided to leave it here just to bring it up as a question. Mar 1, 2023 at 16:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.