🏭 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
| Concept | Use Case | Example |
| Private Constructor | Hide creation or enforce static-only | ClassName._() |
| Factory Constructor | Smart or conditional object creation | factory ClassName() |
| Private Functions | Hide internal helper logic | _methodName() |
| Static Utility Class | Group static functions/constants | Utils._() + static methods |
| Naming Factories | Alternate 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.” 🧠✨



