The factory method design pattern

The factory method design pattern exists in order to encapsulate an actual class instantiation. It simply provides an interface to create an object, and then the subclasses of the factory decide which concrete class to instantiate. This design pattern could become useful in cases where we want to create different objects during the runtime of the application. This design pattern is also helpful when object creation would otherwise require extra parameters to be passed by the developer.

Everything will become clearer with an example, and we will provide one in the following subsections.

Class diagram

For the factory methods, we will be showing an example with databases. To keep things simple (just because the actual java.sql.Connection has a lot of methods), we will define our own SimpleConnection and it will have concrete implementations for MySQL and PostgreSQL.

The diagram for the connection classes looks like the following:

Class diagram

Now, creating these connections will depend on the database we want to use. However, using them will be exactly the same because of the interface they provide. The actual creation might also involve some extra computations that we want to hide from the user and which will be relevant if we're talking about a different constant for each database. That's why we use a factory method design pattern. The following figure shows how the rest of our code will be structured:

Class diagram

In the preceding figure, MysqlClient and PgsqlClient are the concrete implementations of the DatabaseClient. The factory method is connect and it returns a different connection in different clients. Since we override, the signature in the code still shows that the method returns a SimpleConnection but the actual types are concrete. In the diagram, for clarity we have chosen to show the actual returned type.

Code example

From the preceding diagram, it is clear that depending on the database client we use, a different connection will be used and created. Let's now have a look at the code representation for the preceding diagrams. First is the SimpleConnection with its concrete implementations:

trait SimpleConnection {
  def getName(): String
  def executeQuery(query: String): Unit
}

class SimpleMysqlConnection extends SimpleConnection {
  override def getName(): String = "SimpleMysqlConnection"

  override def executeQuery(query: String): Unit = {
    System.out.println(s"Executing the query '$query' the MySQL way.")
  }
}

class SimplePgSqlConnection extends SimpleConnection {
  override def getName(): String = "SimplePgSqlConnection"

  override def executeQuery(query: String): Unit = {
    System.out.println(s"Executing the query '$query' the PgSQL way.")
  }
}

Using these implementations happens in our factory method, called connect. The following code snippet shows how we can take advantage of connect and how we can implement it in specific database clients:

abstract class DatabaseClient {
  def executeQuery(query: String): Unit = {
    val connection = connect()
    connection.executeQuery(query)
  }
  
  protected def connect(): SimpleConnection
}

class MysqlClient extends DatabaseClient {
  override protected def connect(): SimpleConnection = new SimpleMysqlConnection
}

class PgSqlClient extends DatabaseClient {
  override protected def connect(): SimpleConnection = new SimplePgSqlConnection
}

Using our database clients is then straightforward, as shown here:

object Example {
  def main(args: Array[String]): Unit = {
    val clientMySql: DatabaseClient = new MysqlClient
    val clientPgSql: DatabaseClient = new PgSqlClient
    clientMySql.executeQuery("SELECT * FROM users")
    clientPgSql.executeQuery("SELECT * FROM employees")
  }
}

The preceding code example will produce the following output:

Code example

We saw how the factory method design pattern works. If we need to add another database client, then we can just extend DatabaseClient and return a class that extends SimpleConnection when we implement the connect method.

Note

The preceding choice to use an abstract class for the DatabaseClient and a trait for the SimpleConnection was just random. We could, of course, change the abstract class with a trait.

In other cases, the objects created by the factory method might require parameters in their constructor and these parameters could depend on some specific state or functionality of the object that owns the factory method. This is where this design pattern could actually shine.

Scala alternatives

As with anything in software engineering, this design pattern could also be achieved using different approaches. Which one to use really boils down to the requirements and specific features of the application and objects being created. Some possible alternatives include the following:

  • Passing the needed components to the class that needs them in the constructor (object composition). This, however, would mean that these components will be specific instances rather than new ones, every time a request for them is made.
  • Passing a function that will create the objects we need.

Using the richness of Scala, we can avoid this design pattern or we can just be smarter about how we create the objects that we will be using or exposing whatever the factory method will be creating. There is no right or wrong way in the end. There is, however, a way that would make things simpler both in terms of usage and maintenance and it should be chosen by considering the specific requirements.

What is it good for?

As with other factories, the details of object creation are hidden. This means that if we change the way a specific instance has to be created, we would have to change only the factory methods that create it (this might involve a lot of creators though, depending on the design). The factory method allows us to use the abstract version of a class and defer the object creation to subclasses.

What is it not so good for?

In the preceding examples, we might quickly run into issues if we have more than one factory method. This would first require the programmer to implement many more methods, but more importantly, it could lead to the returned objects being incompatible. Let's see this in a short example. First, we will declare another trait called SimpleConnectionPrinter, which will have one method that prints something when called:

trait SimpleConnectionPrinter {
  def printSimpleConnection(connection: SimpleConnection): Unit
}

Now we want to change our DatabaseClient and name it differently (BadDatabaseClient). It will look like the following:

abstract class BadDatabaseClient {
  def executeQuery(query: String): Unit = {
    val connection = connect()
    val connectionPrinter = getConnectionPrinter()
    connectionPrinter.printSimpleConnection(connection)
    connection.executeQuery(query)
  }

  protected def connect(): SimpleConnection
  protected def getConnectionPrinter(): SimpleConnectionPrinter
}

The only difference here to our original example is that we have another factory method, which we will also call when executing a query. Similar to the SimpleConnection implementations, let's now create two more for MySQL and PostgreSQL for our SimpleConnectionPrinter:

class SimpleMySqlConnectionPrinter extends SimpleConnectionPrinter {
  override def printSimpleConnection(connection: SimpleConnection): Unit = {
    System.out.println(s"I require a MySQL connection. It is: '${connection.getName()}'")
  }
}

class SimplePgSqlConnectionPrinter extends SimpleConnectionPrinter {
  override def printSimpleConnection(connection: SimpleConnection): Unit = {
    System.out.println(s"I require a PgSQL connection. It is: '${connection.getName()}'")
  }
}

We can now apply the factory design pattern and create MySQL and PostgreSQL clients, as shown here:

class BadMySqlClient extends BadDatabaseClient {
  override protected def connect(): SimpleConnection = new SimpleMysqlConnection

  override protected def getConnectionPrinter(): SimpleConnectionPrinter = new SimpleMySqlConnectionPrinter
}

class BadPgSqlClient extends BadDatabaseClient {
  override protected def connect(): SimpleConnection = new SimplePgSqlConnection

  override protected def getConnectionPrinter(): SimpleConnectionPrinter = new SimpleMySqlConnectionPrinter
}

The preceding implementations are completely valid. We can now use them in an example:

object BadExample {
  def main(args: Array[String]): Unit = {
    val clientMySql: BadDatabaseClient = new BadMySqlClient
    val clientPgSql: BadDatabaseClient = new BadPgSqlClient
    clientMySql.executeQuery("SELECT * FROM users")
    clientPgSql.executeQuery("SELECT * FROM employees")
  }
}

This example will have the following output:

What is it not so good for?

What happened in the preceding example is that we got a logical error. And nothing notifies us about this. When the number of methods to implement grows, this could become a problem and mistakes could be easily made. For example, our code didn't throw an exception, but this pitfall could lead to runtime errors that could be really hard to discover and debug.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.139.8