Question #272EasyDart BasicsImportant

What is the difference between normal constructor vs Factory constructor proviide me with some real world example ?

Answer

Overview

A normal (generative) constructor always creates a new instance of the class. A factory constructor can return an existing instance, return a cache, or return a subtype.


Normal Constructor

Always creates a NEW instance. Cannot return an existing object or subtype.

dart
class Logger {
  final String name;

  Logger(this.name); // Normal constructor -- always new object
}

// Every call creates a NEW instance
final log1 = Logger('auth');  // New object
final log2 = Logger('auth');  // Another NEW object -- different instance!
print(identical(log1, log2)); // false -- different objects

Factory Constructor

Can control what is returned -- existing instance, subtype, or new object with complex logic.

Use Case 1: Singleton Pattern

dart
class AppDatabase {
  static AppDatabase? _instance;

  AppDatabase._(); // Private constructor

  // Factory returns the same instance every time
  factory AppDatabase.getInstance() {
    _instance ??= AppDatabase._();
    return _instance!;
  }

  Future<void> query(String sql) async { ... }
}

// Usage
final db1 = AppDatabase.getInstance();
final db2 = AppDatabase.getInstance();
print(identical(db1, db2)); // true -- same instance

Use Case 2: fromJson (Most Common)

dart
class User {
  final String name;
  final int age;
  final String role;

  // Normal constructor
  User({required this.name, required this.age, required this.role});

  // Factory -- creates instance from JSON with validation/mapping
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      name: json['name'] as String,
      age: (json['age'] as num).toInt(),
      role: json['role'] as String? ?? 'user',
    );
  }
}

// Usage
final json = {'name': 'Alice', 'age': 28};
final user = User.fromJson(json);

Use Case 3: Return Subtype

dart
abstract class Animal {
  String get sound;

  // Factory returns different subtypes based on input
  factory Animal.create(String type) {
    switch (type) {
      case 'dog': return Dog();
      case 'cat': return Cat();
      default: throw ArgumentError('Unknown animal: $type');
    }
  }
}

class Dog extends Animal {
   String get sound => 'Woof';
}

class Cat extends Animal {
   String get sound => 'Meow';
}

// Returns Dog or Cat -- not Animal (abstract)
final animal = Animal.create('dog');
print(animal.sound); // Woof

Use Case 4: Caching

dart
class ColorPalette {
  static final Map<String, ColorPalette> _cache = {};
  final String name;

  ColorPalette._(this.name);

  factory ColorPalette(String name) {
    // Return cached instance if exists
    return _cache.putIfAbsent(name, () => ColorPalette._(name));
  }
}

final a = ColorPalette('dark');
final b = ColorPalette('dark');
print(identical(a, b)); // true -- returned from cache

Key Differences

FeatureNormal ConstructorFactory Constructor
Always new instanceYesNot necessarily
Can return subtypeNoYes
Can return cachedNoYes
Uses
text
this
YesNo
Late final fieldsAllowedNot allowed
Keyword
text
ClassName(...)
text
factory ClassName(...)

Common Real-World Pattern:

text
factory User.fromJson(json)
is the most common factory in Flutter -- it parses JSON into a Dart object.