Skip to main content

Command Palette

Search for a command to run...

🏭 Dart’s Secret Factory: Understanding Private Constructors, Factory Methods & Private Functions

Updated
6 min read
🏭 Dart’s Secret Factory: Understanding Private Constructors, Factory Methods & Private Functions

✨ The Story of DartLand — Where Classes Guard Their Secrets

Once upon a time in DartLand, there was a clever engineer named Darty 🧑‍💻.
He loved building things — boxes, shapes, utilities, you name it.

But chaos soon spread.
People were misusing his classes — calling internal methods, creating random objects, and breaking stuff. 😩

So Darty locked some doors 🔒 using private constructors and private functions,
and built an organized factory 🏭 to decide what to create and when.

That’s the story we’re diving into today — a story of control, clarity, and clever design.


🧱 Step 1: Constructors — The Builders of Objects

A constructor in Dart is like a recipe for creating an object.

class ToyBox {
  ToyBox() {
    print('A new ToyBox is created!');
  }
}

void main() {
  var box = ToyBox();
}

Every time you call ToyBox(), a new toy box pops out 🎁.
But sometimes, you don’t want people to create objects freely — you want control.


🔒 Step 2: The Secret Door — Private Constructors

A private constructor (written as ClassName._()) hides the class’s creation logic from outsiders.

class ToyBox {
  ToyBox._(); // private constructor

  static final ToyBox _instance = ToyBox._();

  factory ToyBox() => _instance;
}

Now, no one outside the file can write ToyBox._().
Only the class itself can do that — perfect for Singletons, utility classes, or restricted instantiation.


🧩 Step 3: Private Constructors for Utility or Static-Only Classes

This is one of the most practical uses of private constructors —
when you want a class to be a namespace for static methods or constants, and never instantiated.

Let’s say you have a math utility:

class MathUtils {
  MathUtils._(); // Prevents instantiation

  static const double pi = 3.14159;

  static double areaOfCircle(double radius) => pi * radius * radius;
}

void main() {
  print(MathUtils.areaOfCircle(5)); // ✅ Works
  // var m = MathUtils(); ❌ Error: Constructor is private
}

🧠 Why do this?

Because:

  • You don’t want someone doing MathUtils() — that makes no sense.

  • The class acts as a static container, grouping related utilities together.

  • The private constructor says:

    “Hey, I’m not meant to be instantiated — just use my static stuff!”

✅ You’ll see this pattern everywhere — Colors, Icons, ThemeData, etc. in Flutter do the same thing.


🏭 Step 4: The Factory Constructor — Smart Object Creation

Now, let’s talk about the factory constructor — a “smart” way to create or return objects.

It’s like having a “factory gate” that decides what happens when someone calls your class.

class ToyBox {
  static final ToyBox _instance = ToyBox._();

  ToyBox._(); // private

  factory ToyBox() {
    print('Welcome to the Toy Factory!');
    return _instance; // same box every time
  }
}

void main() {
  var box1 = ToyBox();
  var box2 = ToyBox();

  print(box1 == box2); // true ✅
}

💡 Factory constructors don’t have to return a new instance every time.
They can:

  • Return the same instance (Singleton)

  • Return a subclass

  • Or even return something completely different


🧙 Step 5: Factory Constructor Returning Different Classes

A factory constructor can behave like a smart switchboard.

abstract class Shape {
  factory Shape(String type) {
    if (type == 'circle') return Circle();
    if (type == 'square') return Square();
    throw 'Unknown shape type';
  }
}

class Circle implements Shape {}
class Square implements Shape {}

void main() {
  var s = Shape('circle');
  print(s.runtimeType); // Circle ✅
}

You call Shape('circle'), but the factory decides which type of shape you actually get.
This is a great example of encapsulation — hiding logic and returning clean results.


🧰 Step 6: Private Functions — The Hidden Helpers

Private functions are internal workers of your class.
They’re not meant to be accessed from outside — they keep your logic modular and safe.

class PasswordValidator {
  bool isValid(String password) {
    return _hasMinLength(password) && _hasUppercase(password);
  }

  bool _hasMinLength(String password) => password.length >= 8;
  bool _hasUppercase(String password) => password.contains(RegExp(r'[A-Z]'));
}

You expose a single clear method — isValid()
while _hasMinLength and _hasUppercase do the behind-the-scenes work.

Outside code can’t call these private helpers.
They’re like the backstage crew of your app — invisible, but essential 🎬.


⚡ Real-World Use Cases

Let’s connect all this to real Flutter and Dart examples 👇

1️⃣ Singleton for Services or Configs

You want one single instance across the app.

class DatabaseManager {
  static final DatabaseManager _instance = DatabaseManager._();

  DatabaseManager._(); // private

  factory DatabaseManager() => _instance;

  void connect() => print('Connected to DB!');
}

Every call to DatabaseManager() returns the same instance.


2️⃣ Factory for fromJson Objects

Factories are perfect for data models that parse API responses.

class User {
  final String name;
  final int age;

  User._(this.name, this.age);

  factory User.fromJson(Map<String, dynamic> json) {
    return User._(json['name'], json['age']);
  }
}

Later, if you add caching or subclassing, your users’ code doesn’t break —
the factory hides all complexity.


3️⃣ Private Constructor for Utility Classes

Static-only utilities shouldn’t be instantiated:

class DateUtils {
  DateUtils._(); // prevents instantiation

  static String today() => DateTime.now().toIso8601String();
}

Calling DateUtils() makes no sense — and this pattern enforces that cleanly.


4️⃣ Private Functions for Internal Logic

class EmailValidator {
  bool isValid(String email) => _containsAtSymbol(email) && _isProperLength(email);

  bool _containsAtSymbol(String email) => email.contains('@');
  bool _isProperLength(String email) => email.length >= 6;
}

Your public API stays tidy. The details remain hidden — encapsulation at its best.


🧭 Best Practices

ConceptUse CaseExample
Private ConstructorHide creation or enforce static-onlyClassName._()
Factory ConstructorSmart or conditional object creationfactory ClassName()
Private FunctionsHide internal helper logic_methodName()
Static Utility ClassGroup static functions/constantsUtils._() + static methods
Naming FactoriesAlternate constructors.fromJson(), .cached()

🍩 Real-Life Analogy

Imagine a bakery 🍰:

  • The private constructor is the secret recipe — only the baker knows it.

  • The factory constructor is the counter — it decides whether to give you a fresh cake, a pre-made one, or none at all.

  • The private functions are the kitchen helpers — they do the heavy work, but customers never see them.

  • The static-only class is like the menu board — you don’t buy the board, you just read it.


✨ Key Takeaways

  • Private constructors control how (or if) a class can be instantiated.
    → Perfect for Singletons and utility classes.

  • Factory constructors let you control what instance is returned.
    → Great for caching, type selection, and lazy creation.

  • Private functions keep your internal logic hidden and APIs clean.
    → The heart of encapsulation.

  • Static-only classes use private constructors to prevent misuse.
    → You just use their static methods or constants.


💬 Final Thought

Private constructors and factory methods aren’t just “syntax tricks.”
They’re design patterns that give your code discipline, clarity, and power
ensuring your Dart classes behave exactly how you intend.

“Good code hides what should be hidden,
and exposes only what’s meant to be used.” 🧠✨