Lesson 2
Understanding the Factory Method Pattern in Kotlin
Understanding the Factory Method

Welcome back! We have already covered the Singleton pattern and how it ensures a class has only one instance. Now, let's dive into another essential creational design pattern: the Factory Method pattern. This lesson will guide you through understanding the Factory Method pattern, a powerful tool used to define an interface for creating an object, but it allows subclasses to alter the type of objects that will be created.

What You'll Learn

In this lesson, you'll gain a solid understanding of the Factory Method pattern in Kotlin. We will focus on:

  • What the Factory Method pattern is and why it is used.
  • How to implement the Factory Method pattern using a factory class.
  • Creating different types of document classes (WordDocument and ExcelDocument) and generating their instances via the factory method.
The Factory Pattern

The Factory Method pattern is a creational design pattern that provides an interface for creating objects in a superclass, but it allows subclasses to alter the type of objects that will be created. Instead of calling a constructor directly to create an object, the client calls a factory method defined by an abstract class or interface, which delegates the process to derived classes. This approach promotes loose coupling and adheres to the Open/Closed Principle, allowing a system to be extended without modifying existing code.

We'll walk through a code example involving Document, WordDocument, and ExcelDocument classes, managed by a DocumentFactory class.

Step 1: Define the Document Interface

First, we define an interface for our documents.

Kotlin
1interface Document { 2 // Abstract method to be implemented by concrete document types 3 fun open() 4}

The Document interface declares a method open that all document types must implement. This provides a common contract for all documents.

Step 2: Implement Concrete Document Classes

We create two concrete implementations of the Document interface, WordDocument, and ExcelDocument.

Kotlin
1class WordDocument : Document { 2 override fun open() { 3 println("Opening Word Document.") 4 } 5} 6 7class ExcelDocument : Document { 8 override fun open() { 9 println("Opening Excel Document.") 10 } 11}

WordDocument and ExcelDocument classes implement the Document interface, thereby providing specific behavior for opening Word and Excel documents respectively.

Step 3: Create the Factory Class

Next, we create a factory class that provides a method to create instances of Document.

The factory class encapsulates the logic for creating various types of documents without exposing the creation logic to the client. This ensures that the client code doesn't need to know the details of how the documents are created. Instead, it simply calls the factory method and receives the appropriate document instance.

Kotlin
1class DocumentFactory { 2 // Enum to define supported document types 3 enum class DocumentType { 4 WORD, EXCEL 5 } 6 7 // Factory method to create documents based on the type 8 companion object { 9 fun getDocument(type: DocumentType): Document { 10 return when (type) { 11 DocumentType.WORD -> WordDocument() 12 DocumentType.EXCEL -> ExcelDocument() 13 else -> throw IllegalArgumentException("Unknown document type: $type") 14 } 15 } 16 } 17}
  • The DocumentType Enum:

    • This enum defines the supported types of documents (WORD and EXCEL). You can easily extend this enum to include additional document types in the future, making the system flexible and scalable.
  • The getDocument Function:

    • This is the factory function that takes a DocumentType as a parameter and returns an instance of the corresponding document class.
    • The when expression checks the provided document type and creates an instance of WordDocument or ExcelDocument accordingly.
    • If an unsupported or unknown document type is passed, the function throws an IllegalArgumentException. This guards against invalid input, ensuring robustness in your code.
Step 4: Object Creation Using The Factory

To complete our understanding, let's see how to use the Factory Method pattern via a main function. This will help you see how the abstract factory and concrete factory classes work together to create and manage different types of documents.

Kotlin
1fun main() { 2 // Create a Word document using the factory method 3 val wordDoc: Document = DocumentFactory.getDocument(DocumentFactory.DocumentType.WORD) 4 wordDoc.open() // Outputs: Opening Word Document. 5 6 // Create an Excel document using the factory method 7 val excelDoc: Document = DocumentFactory.getDocument(DocumentFactory.DocumentType.EXCEL) 8 excelDoc.open() // Outputs: Opening Excel Document. 9}

In the main function:

  1. We call the getDocument method of DocumentFactory with DocumentType.WORD to instantiate a WordDocument.
  2. We then call the open method on the WordDocument instance to demonstrate its functionality.

Similarly:

  1. We call the getDocument method of DocumentFactory with DocumentType.EXCEL to instantiate an ExcelDocument.
  2. We then call the open method on the ExcelDocument instance to demonstrate its functionality.

By following these steps and running the main function, you can observe how the Factory Method pattern allows the creation of document objects without needing to know their specific types at compile-time. This encapsulates the instantiation logic within the factory class and promotes flexibility and maintainability in your code.

Why It Matters

The Factory Method pattern is crucial for scenarios where the precise type of the object isn't known until runtime. By mastering this pattern, you'll have the ability to create code that is more modular and easier to extend in the future. For example, consider how you might need different document types in various contexts — using the Factory Method pattern ensures that your code can handle these variations efficiently and without unnecessary duplication.

This pattern offers several key advantages:

  • It encapsulates the object creation process, making the code more maintainable.
  • It decouples client code from specific classes it needs to instantiate, enhancing flexibility.
  • By adhering to the Single Responsibility Principle, it ensures factory classes focus solely on creating objects, promoting cleaner design.
  • It enhances reusability, allowing for the introduction of new object types without modifying existing code.
  • It allows for runtime decisions on class instantiation, enhancing adaptability.

Are you ready to see this in action? Let's begin the practice section and implement the Factory Method pattern together!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.