Home > Back-end >  Design pattern for creating a class with multiple methods which should be executed in an order
Design pattern for creating a class with multiple methods which should be executed in an order

Time:01-03

I am trying to create a template class for testing Room migrations. The steps which should be followed for testing a Room migration are as follows.

  1. Create a database in the previous version.
  2. user migration helper to migrate to the next version.
  3. insert some data.
  4. verify the integrity of that data.

Now I have created a class MigrationHelper which implements all this methods accordingly.

class MigrationHelper {

 fun createDatabase(version : Int) : MigrationHelper{
       initialDatabase = migrationTestHelper.createDatabase(TEST_DB_NAME , version)
       return this
 }

 fun insertData(db : SupportSQLiteDatabase){
   ... 
 }

}

Now for this class I am currently using the builder method so that the for writing tests developers can call methods in a clean and understandable way.

    @Test
    fun runMigration_78_79(){
        migrationHelper.setUp("test_db")
            .addTablesInvolved("packs")
            .createDatabaseWithData(78)
            .addMigrations(MIGRATION_77_78,MIGRATION_78_79)
            .runMigration(79)
            .cleanOut()

    }

But since builder pattern , any method can be called in any order . This is not good for my implementation , since some methods are neccessary to be called in an order . for eg : Can call the 'runMigration' method before the 'addMigration' method, Since for running the migration you need to add an array of all the migrations involved.

What would be the best design pattern for this kind of situation ? I understand I might be over complicating , I could just create class with a bunch of methods and call them individually . but I would like to have a clean approach. Thank you.

CodePudding user response:

The builder pattern alone does not help when you put everything into one builder, but you could just split your methods into different builders reflecting the state you are currently in. When each builder consist only of the operations that can only be done in the corresponding state, it is not possible to call the operations in a wrong order.

For instance, you could have an InitialBuilder that has a function setUp that returns an EmptyDbBuilder which may then contain a function addTablesInvolved, returning a DbWithTablesBuilder, and so on.

This way you have full control over the functions that can or cannot be called in different situations.

CodePudding user response:

Your "builder pattern" example isn't really the builder pattern. It's just using chained method calls without building anything.

The builder pattern would actually allow you to submit the calls in any order because the method calls only collect parameters, and then there's a final method call to execute the whole set. This final method call will perform the work in the correct order. Something like the following. This is just a sample--I haven't done Room testing like this so some of my syntax is probably wrong for that part.

class MigrationHelper private constructor(val databaseName: String) {

  companion object {
    fun build(databaseName: String) = MigrationHelper(databaseName)
  }

  private var startingDatabaseVersion: Int? = null
  private val tables = mutableSetOf<String>()
  private val migrations = mutableSetOf<Migration>()
  private val migrationsToRun = mutableSetOf<Int>()

  fun createDatabase(version : Int): MigrationHelper {
    startingDatabaseVersion = version
    return this
  }

  fun addTablesInvolved(vararg tables: String): MigrationHelper {
    for (table in tables) this.tables  = table
    return this
  }

  fun addMigrations(vararg migrations: Migration): MigrationHelper {
    for (migration in migrations) this.migrations  = migration
    return this
  }

  fun runMigration(version: Int): MigrationHelper {
    migrationsToRun  = version
    return this
  }

  fun execute() {
    val startingVersion = startingDatabaseVersion ?: 
      error("Missing initial version. Must call createDatabaseWithData() before execute()")

    migrationTestHelper.createDatabase(databaseName, startingVersion)
      .addMigrations(*migrations.toTypedArray())
      .build()
      .use {
        for (table in tables) {
          // add table to db
        }
        for (migration in migrationsToRun.sorted()) {
          // run migration
        }
      }
  }

}

Usage:

MigrationHelper.build("test_db")
  .addTablesInvolved("packs")
  .createDatabaseWithData(78)
  .addMigrations(MIGRATION_77_78,MIGRATION_78_79)
  .runMigration(79)
  .execute()

// Everything between the first and last lines above can be entered in any order.
  • Related