Design Patterns: Factory Method – with Kotlin Examples

This week I will talk about another creational design pattern which is Factory Method (aka Virtual Constructor). Last week I talked about the Abstract Factory pattern, the Abstract Factory with the Factory Method both are considered factories that encapsulate the object creation inside a factory. Factories handle the details of object creation. And every time a client needs an object it asks the factory to create that object.

The GoF book defines the intent of Factory Method as:

Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

Let’s take an example and try to explain the intent of the factory method pattern.

If you look at the example of the widget factory from the Abstract Factory pattern post, createScrollBar() and createButton() are factory methods examples. But let me give you another example here.

Let’s say that you want to build applications that present multiple documents to the user that can be used in a graphical user interface. So you start by building an application that deals with drawing documents. Then you want to create a word document but all your codebase is coupled with the drawing document so you need to add changes to it. Then you want to create another type of document and you will need more changes and soon enough things will become messy.

So let’s abstract the Application and Document classes. The Application class will be responsible for managing documents and create them as required, for example when the user selects from a menu to open a document or create a new one.

And each document to be created will be a subclass of the abstract class Document. The instantiation of each document subclass is application-specific. This means that drawing documents will be dealt with by drawing application and word documents will be dealt with by word application and so on.

When the client which is the GUI asks the Application class to create a new document, the Application class has no clue which type of document to create. But the client must instantiate classes, but it only knows about abstract classes, which cannot be instantiated.

But here comes the Factory Method pattern for rescue. It encapsulates the knowledge of creating the proper Document subclass for each specific application and move this knowledge out of the GUI.

 

As the UML above the Application class has an abstract createDocumnet() function as a factory method, this abstract function will be implemented in the Application subclasses which are DrawingApplication and WordApplication. createDoucmuent() in the DrawingApplication will return the DrawingDoument which is a Doucment subclass, and createDoucmuent() in the WordApplication will return the WordDoument which is also a Document subclass. Once an Application subclass is instantiated then it can instantiate application-specific documents.

The definition of the Factory Method pattern said “Define an interface for creating an object” and we did that with the createDocument() function, “but let subclasses decide which class to instantiate” as we said the Application class has no idea which type of Document will be created and let the subclasses which are the DrawingApplication and WordApplication decide which document to create.

Here is the structure of the Factory Method pattern:

 

The players of the Factory Method pattern the diagram above shows are:

  • Product (Document)
    • Defines the interface of objects the factory method creates
  • ConcreateProduct (DrawingDocument, WordDocument)
    • Implement the product interface.
  • Creator (Application)
    • Declare the factory method in which returns an object of type Product.
  • ConcreatCreator (DrawingApplication, WordApplication)
    • Override the factory method to return an instance of a ConcreateProduct.

The Creator defer the definition of the factory method to its subclasses so the factory method returns an instance of the appropriate ConcreateProduct.

Now let’s try to implement the Application and Document example in code using Kotlin language.

The goal of this simple program is to print the information of the specific document. We have the Document interface and its concrete subclasses DrawingDocument and WordDocument.

interface Document {
    fun showDocumentInfo()
}

class DrawingDocument : Document {
    override fun showDocumentInfo() {
        println("This is a drawing document")
    }
}

class WordDocument : Document {
    override fun showDocumentInfo() {
        println("This is a word document")
    }
}

 

DrawingApplication and WordApplication implement the factory method createDocument() in the abstract class Application. The Application class also has a getDocument() function that will be used by the client to request the specific document.

abstract class Application {
    abstract fun createDocument(): Document

    companion object {
        fun getApplicationDocument(documentType: DocumentType): Application {
            return when (documentType) {
                DocumentType.Drawing -> DrawingApplication()
                DocumentType.Word -> WordApplication()
                else -> throw Exception("Invalid document type")
            }
        }
    }
}

class DrawingApplication : Application() {
    override fun createDocument() = DrawingDocument()
}

class WordApplication : Application() {
    override fun createDocument() = WordDocument()
}

 

Fo documentType parameter of getApplicationDocument() function is of type DocumentType which is an enum class that will be used to determine which application document to get.

enum class DocumentType { Drawing, Word }

 

Here is a complete working program.

interface Document {
    fun showDocumentInfo()
}

class DrawingDocument : Document {
    override fun showDocumentInfo() {
        println("This is a drawing document")
    }
}

class WordDocument : Document {
    override fun showDocumentInfo() {
        println("This is a word document")
    }
}

abstract class Application {
    abstract fun createDocument(): Document

    companion object {
        fun getApplicationDocument(documentType: DocumentType): Application {
            return when (documentType) {
                DocumentType.Drawing -> DrawingApplication()
                DocumentType.Word -> WordApplication()
                else -> throw Exception("Invalid document type")
            }
        }
    }
}

class DrawingApplication : Application() {
    override fun createDocument() = DrawingDocument()
}

class WordApplication : Application() {
    override fun createDocument() = WordDocument()
}

enum class DocumentType { Drawing, Word }

fun main() {
    val drawingApplication = Application.getApplicationDocument(DocumentType.Drawing)
    val drawingDocument = drawingApplication.createDocument()
    drawingDocument.showDocumentInfo()

    val wordApplication = Application.getApplicationDocument(DocumentType.Word)
    val wordDocument = wordApplication.createDocument()
    wordDocument.showDocumentInfo()
}

 

The output will be as follows:

This is a drawing document
This is a word document

 

Factory Method related patterns are:

  • Abstract Factory which is often implemented with factory method as the createScrollBar() and  createButton() from Abstract Factory example.
  • Factory methods are usually called within Template Methods. We will talk about Template Methods in a future post.
  • Prototypes don’t require subclassing Creator but they often require an Initialize operation on the Product class which the Creator uses to initialize the object. Factory Method doesn’t require such an operation.

Here we reach to the end of this post but there is much more to this pattern and you can refer to the book to know more about when to use this pattern, pros and cons to using it, and more about the implementation of this pattern and more.

So that’s all I have for this week and I always appreciate feedback and If you have any question please put your questions in the comments.

Till the next week.

Leave a Reply

Your email address will not be published. Required fields are marked *