Bird
Raised Fist0
Kotlinprogramming~15 mins

Factory pattern with companion objects in Kotlin - Deep Dive

Choose your learning style10 modes available
Overview - Factory pattern with companion objects
What is it?
The factory pattern is a way to create objects without specifying the exact class of the object to create. In Kotlin, companion objects are special objects inside a class that can hold functions and properties shared by all instances. Combining these, the factory pattern with companion objects means using a companion object to create and return instances of a class. This helps keep object creation organized and flexible.
Why it matters
Without this pattern, code that creates objects can become messy and hard to change, especially when many types of objects are involved. Using companion objects for factories centralizes creation logic, making it easier to update or extend without changing many parts of the program. This leads to cleaner, more maintainable code that adapts well as programs grow.
Where it fits
Before learning this, you should understand basic Kotlin classes, objects, and functions. After this, you can explore more advanced design patterns, dependency injection, and Kotlin's sealed classes or interfaces for better type safety in factories.
Mental Model
Core Idea
A companion object acts like a factory inside a class, creating instances without exposing the details of how they are made.
Think of it like...
Imagine a cookie cutter (the companion object) that shapes dough (object creation) into cookies (instances). You don't need to know how the dough is prepared, just use the cutter to get the cookie shape you want.
┌─────────────┐
│   Class     │
│  (Product)  │
│             │
│ companion   │
│ object      │
│  Factory()  │
└─────┬───────┘
      │ creates
      ▼
┌─────────────┐
│  Instance   │
│ (Product)   │
└─────────────┘
Build-Up - 6 Steps
1
FoundationUnderstanding Kotlin Companion Objects
🤔
Concept: Learn what companion objects are and how they belong to a class, not instances.
In Kotlin, a companion object is a special object inside a class declared with the keyword 'companion object'. It can hold functions and properties that are shared across all instances of the class. You can call these functions using the class name without creating an instance. Example: class Example { companion object { fun greet() = "Hello" } } Usage: println(Example.greet()) // prints Hello
Result
You can call functions inside the companion object directly from the class without creating an object.
Understanding companion objects is key because they provide a place to put factory methods that create instances without needing an object first.
2
FoundationBasics of the Factory Pattern
🤔
Concept: The factory pattern creates objects through a method instead of direct constructors.
Normally, you create an object by calling its constructor directly, like 'val obj = MyClass()'. The factory pattern uses a method to create objects, hiding the details of which exact class or how the object is made. Example: interface Animal { fun sound(): String } class Dog : Animal { override fun sound() = "Woof" } class Cat : Animal { override fun sound() = "Meow" } class AnimalFactory { fun create(type: String): Animal = when(type) { "dog" -> Dog() "cat" -> Cat() else -> throw IllegalArgumentException("Unknown animal") } }
Result
You can create different animals by calling the factory's create method with a type string.
Using a factory method separates object creation from usage, making code easier to change and extend.
3
IntermediateImplementing Factory in Companion Object
🤔Before reading on: Do you think companion objects can hold state or just functions? Commit to your answer.
Concept: Use the companion object inside a class to hold the factory method that creates instances of that class or its subclasses.
Instead of a separate factory class, Kotlin allows putting the factory method inside the companion object of the class itself. Example: interface Animal { fun sound(): String } class Dog : Animal { override fun sound() = "Woof" companion object { fun create() = Dog() } } // Usage val dog = Dog.create() println(dog.sound()) // Woof
Result
You create a Dog instance by calling Dog.create() without needing a separate factory class.
Knowing companion objects can hold factory methods lets you keep creation logic close to the class, improving code organization.
4
IntermediateFactory Pattern with Multiple Subtypes
🤔Before reading on: Can a single companion object create different subclasses? Yes or no? Commit to your answer.
Concept: A companion object factory can decide which subclass instance to create based on input parameters.
You can write a factory method inside a companion object that returns different subclasses depending on input. Example: interface Animal { fun sound(): String } class Dog : Animal { override fun sound() = "Woof" } class Cat : Animal { override fun sound() = "Meow" } open class Animal { companion object { fun create(type: String): Animal = when(type) { "dog" -> Dog() "cat" -> Cat() else -> throw IllegalArgumentException("Unknown animal") } } } // Usage val animal = Animal.create("cat") println(animal.sound()) // Meow
Result
The factory method returns the correct subclass instance based on the input string.
This pattern allows flexible creation of different types while keeping the factory logic centralized.
5
AdvancedUsing Factory Pattern for Encapsulation
🤔Before reading on: Does using a companion object factory help hide constructor details? Yes or no? Commit to your answer.
Concept: The factory method can hide complex or multiple constructors, exposing only simple creation methods.
Sometimes constructors are complex or you want to prevent direct creation. By making constructors private and using a companion object factory, you control how instances are made. Example: class User private constructor(val name: String) { companion object { fun create(name: String): User { // Add validation or setup here if (name.isBlank()) throw IllegalArgumentException("Name required") return User(name) } } } // Usage val user = User.create("Alice") println(user.name) // Alice
Result
Users can only be created through the factory method, ensuring validation and control.
Encapsulating creation logic prevents misuse and enforces rules, improving code safety.
6
ExpertCompanion Object Factories and Inheritance Challenges
🤔Before reading on: Can companion object factories be inherited or overridden in subclasses? Commit to your answer.
Concept: Companion objects are not inherited, so factory methods in companion objects do not participate in polymorphism, which can cause design challenges.
In Kotlin, companion objects belong to the class but are not part of the class inheritance chain. This means if you have a base class with a companion object factory, subclasses do not inherit or override that companion object. Example: open class Base { companion object { fun create() = Base() } } class Derived : Base() { companion object { fun create() = Derived() } } // Usage val base = Base.create() // Base instance val derived = Derived.create() // Derived instance // But you cannot do polymorphic calls like Base.create() returning Derived automatically. This requires careful design to avoid confusion or duplicated factory code.
Result
Companion object factories do not support polymorphic factory methods, limiting inheritance use.
Understanding this limitation helps avoid subtle bugs and guides better design choices when using factories with inheritance.
Under the Hood
Companion objects in Kotlin are singleton objects tied to a class. They are initialized when the class is first accessed. Factory methods inside companion objects are just regular functions called on this singleton. When you call a factory method, it runs the code inside and returns a new instance created by constructors or other logic. The companion object itself does not hold instance state but can hold shared data or helper functions.
Why designed this way?
Kotlin introduced companion objects to provide a clean way to define static-like members without Java's static keyword. This design allows factory methods to live inside the class namespace, improving code organization and readability. The choice to make companion objects non-inheritable keeps the language simpler and avoids complex inheritance issues with static members.
┌───────────────┐
│   Class Foo   │
│ ┌───────────┐ │
│ │ Companion │ │
│ │ Object    │ │
│ │ Factory() │ │
│ └────┬──────┘ │
└──────┼────────┘
       │ calls
       ▼
┌───────────────┐
│  New Instance │
│  of Foo or    │
│  subclass     │
└───────────────┘
Myth Busters - 3 Common Misconceptions
Quick: Does a companion object factory method automatically support polymorphism through inheritance? Commit yes or no.
Common Belief:Companion object factory methods are inherited and can be overridden like normal instance methods.
Tap to reveal reality
Reality:Companion objects are not inherited, so their factory methods do not participate in polymorphism or overriding.
Why it matters:Assuming inheritance works here can lead to bugs where the wrong factory method is called, causing unexpected object types or duplicated code.
Quick: Can companion objects hold instance-specific data? Commit yes or no.
Common Belief:Companion objects can hold data unique to each instance of the class.
Tap to reveal reality
Reality:Companion objects are singletons shared by the class, so they cannot hold data unique to individual instances.
Why it matters:Misusing companion objects for instance data can cause shared state bugs and unexpected behavior.
Quick: Does using a factory method always improve code clarity? Commit yes or no.
Common Belief:Factory methods always make code clearer and simpler.
Tap to reveal reality
Reality:In simple cases, factory methods can add unnecessary complexity and indirection.
Why it matters:Overusing factories can make code harder to read and maintain when direct constructors would suffice.
Expert Zone
1
Companion object factories can be combined with Kotlin's sealed classes to enforce exhaustive type creation in a controlled hierarchy.
2
Using inline functions inside companion object factories can optimize performance by reducing overhead in object creation.
3
Companion object factories can leverage Kotlin's named and default parameters to provide flexible creation APIs without multiple constructors.
When NOT to use
Avoid companion object factories when you need polymorphic factory behavior across inheritance hierarchies; instead, use separate factory classes or dependency injection frameworks. Also, if object creation is trivial, direct constructors are simpler and clearer.
Production Patterns
In production Kotlin code, companion object factories are often used for creating data model instances with validation, parsing from external data, or managing complex initialization. They are also common in libraries to provide convenient static-like creation methods while keeping code idiomatic.
Connections
Static methods in Java
Companion object factory methods serve a similar role to static factory methods in Java classes.
Understanding companion objects helps Kotlin developers transition from Java static methods, seeing how Kotlin improves organization and safety.
Dependency Injection
Factory patterns are a foundational concept that dependency injection frameworks build upon to provide flexible object creation.
Knowing factory patterns clarifies how DI frameworks manage object lifecycles and dependencies behind the scenes.
Biological Cell Differentiation
Factory pattern resembles how stem cells differentiate into specific cell types based on signals, similar to how a factory creates different objects based on input.
This cross-domain link shows how controlled creation and specialization are universal concepts in both programming and biology.
Common Pitfalls
#1Trying to override companion object factory methods in subclasses expecting polymorphism.
Wrong approach:open class Base { companion object { open fun create() = Base() } } class Derived : Base() { companion object { override fun create() = Derived() // Error: cannot override } }
Correct approach:open class Base { companion object { fun create() = Base() } } class Derived : Base() { companion object { fun create() = Derived() } }
Root cause:Misunderstanding that companion objects are not part of the inheritance chain and their members cannot be overridden.
#2Using companion object to store instance-specific data leading to shared state bugs.
Wrong approach:class User { companion object { var currentUserName: String = "" } } fun main() { User.currentUserName = "Alice" val user1 = User() User.currentUserName = "Bob" val user2 = User() // Both user1 and user2 see currentUserName as "Bob" }
Correct approach:class User(val name: String) { companion object { fun create(name: String) = User(name) } } fun main() { val user1 = User.create("Alice") val user2 = User.create("Bob") // Each user has its own name }
Root cause:Confusing companion object as a place for instance data instead of shared class-level data.
#3Using factory pattern unnecessarily for simple classes with no complex creation logic.
Wrong approach:class Point(val x: Int, val y: Int) { companion object { fun create(x: Int, y: Int) = Point(x, y) } } // Usage val p = Point.create(1, 2)
Correct approach:class Point(val x: Int, val y: Int) // Usage val p = Point(1, 2)
Root cause:Overengineering by adding factory methods where direct constructors are simpler and clearer.
Key Takeaways
Companion objects in Kotlin provide a natural place to put factory methods that create instances of a class.
The factory pattern separates object creation from usage, improving code flexibility and maintainability.
Companion object factories cannot be inherited or overridden, so they do not support polymorphic factory behavior.
Using companion object factories helps encapsulate complex creation logic and enforce validation rules.
Avoid overusing factories for simple cases and never store instance-specific data in companion objects.

Practice

(1/5)
1. What is the main purpose of using a companion object in Kotlin's factory pattern?
easy
A. To provide a place for factory methods that create instances
B. To allow multiple constructors in a class
C. To make the class inherit from another class
D. To store instance variables for each object

Solution

  1. Step 1: Understand companion objects

    Companion objects in Kotlin are used to hold methods and properties related to the class but not tied to an instance.
  2. Step 2: Role in factory pattern

    In the factory pattern, companion objects provide factory methods to create instances, especially when constructors are private.
  3. Final Answer:

    To provide a place for factory methods that create instances -> Option A
  4. Quick Check:

    Companion object = factory methods [OK]
Hint: Companion objects hold factory methods for instance creation [OK]
Common Mistakes:
  • Thinking companion objects store instance variables
  • Confusing companion objects with inheritance
  • Believing companion objects allow multiple constructors
2. Which of the following is the correct syntax to declare a private constructor in a Kotlin class?
easy
A. private class MyClass constructor() {}
B. class MyClass private constructor() {}
C. class MyClass constructor private() {}
D. class MyClass constructor() private {}

Solution

  1. Step 1: Recall Kotlin constructor syntax

    In Kotlin, the constructor keyword follows the class name, and modifiers like private come before constructor.
  2. Step 2: Identify correct placement

    The correct syntax places 'private' before 'constructor()' right after the class name.
  3. Final Answer:

    class MyClass private constructor() {} -> Option B
  4. Quick Check:

    Private constructor syntax = class MyClass private constructor() {} [OK]
Hint: Put 'private' before 'constructor()' after class name [OK]
Common Mistakes:
  • Placing 'private' after constructor parentheses
  • Putting 'private' before class keyword
  • Misordering 'constructor' and 'private'
3. What will be the output of the following Kotlin code?
class Product private constructor(val name: String) {
    companion object {
        fun create(name: String): Product {
            return Product(name)
        }
    }
}

fun main() {
    val p = Product.create("Book")
    println(p.name)
}
medium
A. Book
B. null
C. Compilation error
D. Runtime exception

Solution

  1. Step 1: Understand private constructor usage

    The class Product has a private constructor, so instances cannot be created directly outside the class.
  2. Step 2: Use of companion object factory method

    The companion object provides a create() method that calls the private constructor and returns a Product instance.
  3. Step 3: Output of main function

    In main(), Product.create("Book") creates an instance with name "Book". Printing p.name outputs "Book".
  4. Final Answer:

    Book -> Option A
  5. Quick Check:

    Factory method returns instance with name "Book" [OK]
Hint: Factory method returns instance; print property directly [OK]
Common Mistakes:
  • Expecting compilation error due to private constructor
  • Thinking create() returns null
  • Confusing runtime exception with private constructor
4. Identify the error in this Kotlin factory pattern code:
class User private constructor(val id: Int) {
    companion object {
        fun create(id: Int): User {
            User(id)
        }
    }
}
medium
A. Companion object cannot access private constructor
B. Constructor should not be private
C. Function create() should be outside companion object
D. Missing return statement in create() method

Solution

  1. Step 1: Check create() method body

    The create() method calls User(id) but does not return the created object.
  2. Step 2: Understand Kotlin function return rules

    Functions must explicitly return a value unless they are Unit (void). Here, create() should return the new User instance.
  3. Final Answer:

    Missing return statement in create() method -> Option D
  4. Quick Check:

    Factory method must return created object [OK]
Hint: Always return the new instance in factory methods [OK]
Common Mistakes:
  • Assuming constructor can't be private for companion access
  • Placing factory method outside companion object
  • Forgetting return keyword in factory method
5. Given a Kotlin class with a private constructor and a companion object factory, how can you modify the factory method to return a default instance when input is invalid (e.g., empty string)?
class Device private constructor(val model: String) {
    companion object {
        fun create(model: String): Device {
            return Device(model)
        }
    }
}
hard
A. Throw an exception inside create() if model is empty
B. Make constructor public and handle validation outside
C. Add a condition in create() to return Device("Unknown") if model is empty
D. Remove companion object and use secondary constructor

Solution

  1. Step 1: Understand the problem

    The factory method should handle invalid input by returning a default instance instead of failing.
  2. Step 2: Modify create() method

    Add a check: if model is empty, return Device("Unknown"), else return Device(model).
  3. Step 3: Why this works

    This approach keeps constructor private, centralizes instance creation, and handles invalid input gracefully.
  4. Final Answer:

    Add a condition in create() to return Device("Unknown") if model is empty -> Option C
  5. Quick Check:

    Factory method handles invalid input with default instance [OK]
Hint: Use if-check in factory to return default instance on invalid input [OK]
Common Mistakes:
  • Making constructor public defeats factory pattern
  • Throwing exception instead of default instance
  • Removing companion object loses factory benefits
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard