Sitemap

Factory Method in Flutter

5 min readSep 7, 2024

--

The Factory Method, one of the well-known “Gang of Four” design patterns.

Press enter or click to view image in full size

🚫 The Limitation of Using Constructors for Object Creation

When we learn programming, we usually create objects using constructors. But is this always the best idea? 🤔

Not really. The big issue? You must know the exact class type to instantiate. 🏗️

⚠️ Why Is This a Problem?

While constructors seem convenient, what if your app needs to handle objects you haven’t thought of yet? 🤯

🎮 Example: Tower Defense Game

Let’s say you’re building a tower defense game 🏰, where users choose different towers. You might not know which towers they’ll prefer — or which new ones you’ll add in the future. 💡

🔧 Traditional Approach

  • You create a Splash Tower 💦 using one constructor.
  • A Long-Range Tower 🎯 has another constructor.
  • Same for a Slow Tower 🐢.

Seems fine, right? ✅ But when you want to add more towers later, it becomes problematic. 🆕

❓ The Problem

This method makes your code rigid. 🛠️ You’ll need to constantly update constructors for new types, leading to tight coupling and maintenance headaches. 🚧

🛠️ The Solution: Factory Method

The Factory Method is a creational design pattern that abstracts the process of creating instances of specific classes, allowing flexibility in deciding which class to instantiate. So it provides a way to create objects without revealing the instantiation process to the client, ensuring better encapsulation and flexibility.

There are two main and crucial points in understanding this pattern.

  1. Objects are created by calling a factory method instead of calling a constructor.
  2. Objects are created through an abstraction, not a concretion.

So in the factory method design pattern, we create objects without exposing the creation logic to the caller. And the caller refers to the newly created object through a common interface.

Points to use Factory Method Pattern in the code:

  • Complex Object Creation: When object creation involves complex logic that you don’t want to repeat throughout your codebase.
  • Code Decoupling: When you want to decouple the code that uses objects from the concrete classes that create them.
  • Flexibility: When you anticipate needing to add new types of objects without changing the code that uses them.
  • SOLID Principles Violation: When your code violates the SOLID principles, particularly the Open/Closed Principle (O), which states that a class should be open for extension but closed for modification.

Example

Let’s consider a simple example using a factory method pattern:

import 'dart:collection';

abstract class Shape {
double calculateArea();
}

class Circle implements Shape {
final double radius;

Circle(this.radius);

@override
double calculateArea() {
return 3.14 * radius * radius;
}
}

class Square implements Shape {
final double side;

Square(this.side);

@override
double calculateArea() {
return side * side;
}
}

class ShapeFactory {
// Cache to store created objects
static final HashMap<String, Shape> _cache = HashMap();

// Factory method with caching
static Shape getShape(String type, double dimension) {
if (_cache.containsKey(type)) {
print('Returning cached $type');
return _cache[type]!;
} else {
Shape shape;
switch (type) {
case 'circle':
shape = Circle(dimension);
break;
case 'square':
shape = Square(dimension);
break;
default:
throw Exception('Unknown shape type');
}
_cache[type] = shape; // Cache the created object
print('Creating and caching new $type');
return shape;
}
}
}

void main() {
// First time creation - not cached yet
Shape circle1 = ShapeFactory.getShape('circle', 5);
print('Circle 1 Area: ${circle1.calculateArea()}');

// Second time - should return the cached object
Shape circle2 = ShapeFactory.getShape('circle', 5);
print('Circle 2 Area: ${circle2.calculateArea()}');

// First time creation for square - not cached yet
Shape square1 = ShapeFactory.getShape('square', 4);
print('Square 1 Area: ${square1.calculateArea()}');
}

Explanation of the Example:

  1. Cache Mechanism: A HashMap is used to store cached objects. The key is the type of shape (e.g., "circle" or "square"), and the value is the Shape object.
  2. Factory Method with Caching: The getShape method first checks if an object of the requested type is already in the cache:
    . If it is, the cached object is returned, avoiding the need to create a new instance.
    . If it isn’t, a new object is created, added to the cache, and then returned.
  3. Efficiency Gain: When you request the same type of shape multiple times, the factory returns the cached object instead of creating a new one. This can save time and resources, especially in cases where object creation is costly.
  4. Output of the Example:
    .
    The first call to ShapeFactory.getShape('circle', 5) creates a new Circle object and caches it.
    . The second call to ShapeFactory.getShape('circle', 5) returns the cached Circle object.
    . When ShapeFactory.getShape('square', 4) is called for the first time, a new Square object is created and cached.

Caching objects in the factory can be particularly useful when:

  • Object Creation is Expensive: If creating an object is resource-intensive (e.g., involves complex initialization or accessing external resources), caching allows you to reuse an already created instance.
  • Memory Management: By reusing objects, you reduce memory usage and potential overhead associated with creating and destroying objects frequently.
  • Consistency: Ensures that the same instance is used in multiple places, which can be important for managing state or shared resources.

Benefits of Using the Factory Pattern:

  1. Decoupling Object Creation: The code that uses the shapes (main function) is decoupled from the concrete classes like Circle, Square, or Rectangle. It only interacts with the Shape interface.
  2. Centralized Object Creation: All the logic for creating different types of shapes is centralized in the ShapeFactory, making it easy to modify or extend.
  3. Extensibility: If you need to add a new shape (e.g., Triangle), you just need to:
    . Implement the Shape interface in the new class.
    . Update the ShapeFactory with the new creation logic.
  4. Simplified Code Maintenance: Changes to object creation logic are isolated within the factory, making the code easier to maintain and extend.
  5. Single Responsibility Principle. You can move the creation code into one place in the program, making the code easier to support.
  6. Open/Closed Principle. You can introduce new subtypes into the program without breaking existing client code. This results in Clean code.

🚫 When not to use:

There are no specific restrictions as to when NOT to use it. 🛠️ Feel free to apply it based on your needs!

In Simple Words: The Factory pattern and polymorphism, you would indeed use Shape as a type. By defining an abstract class or interface Shape, you create a common contract that all concrete shape classes (like Circle, Square, Rectangle) must follow. This allows you to treat all these different shapes as a Shape type, enabling polymorphism.

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

Did you know you can clap for an article up to 50 times? Give it a try!

I welcome your feedback in the comments. I would also appreciate the opportunity to connect with you on LinkedIn!

--

--

Syed Abdul Basit
Syed Abdul Basit

Written by Syed Abdul Basit

📍 Moved to a new account → medium.com/@umairsyedahmed282 Flutter + AI content · Follow me there for 2026 updates