Question #5MediumOOP ConceptsImportant

Oops concept

#oops

Answer

Overview

Object-Oriented Programming (OOP) is a programming paradigm based on the concept of objects — which bundle data (properties) and behavior (methods) together. Dart is fully object-oriented — everything is an object, even numbers and functions.

OOP has 4 core pillars:


1. Encapsulation

Hiding the internal state of an object and only exposing what is necessary through a public interface.

dart
class BankAccount {
  // Private field — hidden from outside (only accessible within this file)
  double _balance = 0;

  // Public getter — controlled read access
  double get balance => _balance;

  // Public methods — controlled modification
  void deposit(double amount) {
    if (amount > 0) _balance += amount;
  }

  void withdraw(double amount) {
    if (amount > 0 && amount <= _balance) _balance -= amount;
  }
}

void main() {
  final account = BankAccount();
  account.deposit(500);
  print(account.balance); // 500.0

  // account._balance = 9999; // ❌ Error — private field
}

Key Idea: Use

text
_
prefix in Dart to make fields/methods private. Note: private in Dart means library-private (file-level), not class-private.


2. Inheritance (Detailed)

A class (child/subclass) can inherit properties and methods from another class (parent/superclass), enabling code reuse.

Dart provides 4 ways to achieve inheritance-like behavior:

2.1
text
extends
— True Inheritance ("is-a" relationship)

The child class inherits everything from the parent — fields, methods, and constructors are chained.

dart
class Animal {
  String name;

  Animal(this.name) {
    print('Animal constructor: $name');
  }

  void eat() => print('$name is eating');
  void speak() => print('...');
}

class Dog extends Animal {
  String breed;

  Dog(String name, this.breed) : super(name) {
    print('Dog constructor: $name, $breed');
  }

  
  void speak() => print('$name says Woof!');
  // eat() is inherited — no need to override
}

void main() {
  final dog = Dog('Buddy', 'Labrador');
  // Output:
  // Animal constructor: Buddy   ← Parent constructor called FIRST
  // Dog constructor: Buddy, Labrador  ← Then child constructor

  dog.eat();   // Buddy is eating  ← Inherited from Animal
  dog.speak(); // Buddy says Woof! ← Overridden in Dog
}

Key behaviors of

text
extends
:

  • Parent constructor is called automatically (via
    text
    super
    )
  • Child inherits all methods and fields
  • Child only needs to
    text
    @override
    methods it wants to change
  • Single inheritance only
    text
    extends
    one class max

2.2
text
implements
— Interface Contract ("can-do" relationship)

The class promises to provide the same API but must write every method from scratch. No code is inherited.

dart
class Printer {
  String brand;

  Printer(this.brand) {
    print('Printer constructor: $brand');
  }

  void printDoc(String doc) => print('Printing: $doc on $brand');
  void scan() => print('Scanning on $brand');
}

class SmartPrinter implements Printer {
  
  String brand;  // ✅ Must declare field — not inherited

  SmartPrinter(this.brand) {
    print('SmartPrinter constructor: $brand');
  }

  
  void printDoc(String doc) => print('Smart printing: $doc');  // ✅ Must override

  
  void scan() => print('Smart scanning');  // ✅ Must override — even though Printer has a body
}

void main() {
  final sp = SmartPrinter('HP');
  // Output:
  // SmartPrinter constructor: HP
  // ❌ "Printer constructor" is NEVER printed — implements does NOT call parent constructor

  sp.printDoc('Resume');  // Smart printing: Resume
}

Key behaviors of

text
implements
:

  • Parent constructor is NOT called
  • Nothing is inherited — you must override ALL methods and declare ALL fields
  • Multiple implements allowed
    text
    implements A, B, C
  • The parent class is treated as an interface (API contract only)

2.3
text
with
— Mixin ("has-ability" relationship)

Mixins let you reuse code from multiple sources without inheritance hierarchy. Think of them as "plug-in abilities".

dart
mixin Flyable {
  void fly() => print('Flying!');
}

mixin Swimmable {
  void swim() => print('Swimming!');
}

mixin Runnable {
  void run() => print('Running!');
}

// Duck can fly, swim, and run — all abilities mixed in
class Duck with Flyable, Swimmable, Runnable {
  String name;
  Duck(this.name);
}

// Fish can only swim
class Fish with Swimmable {
  String name;
  Fish(this.name);
}

void main() {
  final duck = Duck('Donald');
  duck.fly();   // Flying!   ← From Flyable mixin
  duck.swim();  // Swimming! ← From Swimmable mixin
  duck.run();   // Running!  ← From Runnable mixin

  final fish = Fish('Nemo');
  fish.swim();  // Swimming!
  // fish.fly(); // ❌ Error — Fish doesn't have Flyable mixin
}

Key behaviors of

text
with
(mixin):

  • Code is reused (methods are inherited, not just the contract)
  • Multiple mixins allowed
    text
    with A, B, C
  • No constructor in mixins (use
    text
    mixin
    keyword, not
    text
    class
    )
  • Can combine with
    text
    extends
    text
    class Duck extends Bird with Flyable, Swimmable

2.4
text
abstract class
— Cannot Be Instantiated

An abstract class defines a blueprint — some methods can have bodies, others are abstract (no body).

dart
abstract class PaymentGateway {
  // Abstract — no body, MUST be implemented by child
  Future<bool> processPayment(double amount);
  void refund(double amount);

  // Concrete — has body, child inherits this for FREE
  void showReceipt(double amount) {
    print('Receipt: ₹$amount processed successfully');
  }
}

class RazorpayGateway extends PaymentGateway {
  
  Future<bool> processPayment(double amount) async {
    print('Processing ₹$amount via Razorpay');
    return true;
  }

  
  void refund(double amount) => print('Refunding ₹$amount via Razorpay');

  // showReceipt() inherited for free — no override needed
}

void main() {
  // PaymentGateway(); // ❌ Error — cannot instantiate abstract class
  final gateway = RazorpayGateway();
  gateway.processPayment(500);    // Processing ₹500 via Razorpay
  gateway.showReceipt(500);       // Receipt: ₹500 processed successfully
}

Constructor Behavior — The Critical Difference

This is what most interviews test. Understanding constructor chaining is essential.

dart
class A {
  A() { print('Constructor A'); }
  void method() => print('Method from A');
}

class B extends A {
  B() { print('Constructor B'); }
}

class C implements A {
  C() { print('Constructor C'); }

  
  void method() => print('Method from C');  // Must override
}

class D extends A {
  D() { print('Constructor D'); }
  // method() inherited — no override needed
}

void main() {
  print('--- extends ---');
  B b = B();
  // Output:
  // Constructor A  ← Parent called first
  // Constructor B

  print('--- implements ---');
  C c = C();
  // Output:
  // Constructor C  ← ONLY child constructor, A's constructor NOT called

  print('--- extends (no override) ---');
  D d = D();
  d.method();  // Method from A  ← Inherited without override
}

When to Choose Which — Decision Guide

Use
text
extends
when:

dart
// ✅ "is-a" relationship — Dog IS an Animal
class Dog extends Animal {}

// ✅ You want to REUSE parent code (constructors + methods)
class AdminUser extends User {
  // Inherits login(), profile, etc.
  void deleteUser(String id) {}  // Extra admin ability
}

// ✅ You only need to override SOME methods
class PremiumButton extends ElevatedButton {
  // Inherits all button behavior, just customize style
}

Use
text
implements
when:

dart
// ✅ "can-do" contract — guarantee API compatibility
class MockUserRepository implements UserRepository {
  // For testing — write completely different implementation
  
  Future<User> getUser(String id) async => User(id: id, name: 'Mock');
}

// ✅ Multiple interface compliance
class SmartDevice implements Printable, Scannable, Faxable {
   void print_() {}
   void scan() {}
   void fax() {}
}

// ✅ You want NO parent code — clean implementation from scratch
class CustomList implements List<int> {
  // Must implement EVERY List method yourself
}

Use
text
with
(mixin) when:

dart
// ✅ Sharing abilities across unrelated classes
mixin Logger {
  void log(String msg) => print('[LOG] $msg');
}

mixin Cacheable {
  final Map<String, dynamic> _cache = {};
  void cache(String key, dynamic value) => _cache[key] = value;
  dynamic getCache(String key) => _cache[key];
}

// UserService and ProductService are unrelated
// but both need logging and caching
class UserService with Logger, Cacheable {
  void fetchUser() {
    log('Fetching user');
    cache('user', {'name': 'Alice'});
  }
}

class ProductService with Logger, Cacheable {
  void fetchProducts() {
    log('Fetching products');
    cache('products', []);
  }
}

Use
text
abstract class
when:

dart
// ✅ Define a contract with SOME shared code
abstract class BaseRepository {
  // Shared implementation — all repos get this
  void logQuery(String query) => print('[DB] $query');

  // Abstract — each repo must implement
  Future<List<Map>> fetchAll();
  Future<void> insert(Map data);
}

class UserRepository extends BaseRepository {
  
  Future<List<Map>> fetchAll() async {
    logQuery('SELECT * FROM users');  // Inherited
    return [];
  }

  
  Future<void> insert(Map data) async {
    logQuery('INSERT INTO users');
  }
}

Combining extends + implements + with

Dart allows all three together in a single class:

dart
abstract class Animal {
  String name;
  Animal(this.name);
  void eat() => print('$name eating');
  void breathe() => print('$name breathing');
}

abstract class Pet {
  void play();
  void cuddle();
}

mixin Trainable {
  void train(String command) => print('Learning: $command');
}

mixin Vaccinated {
  bool isVaccinated = false;
  void vaccinate() {
    isVaccinated = true;
    print('Vaccinated!');
  }
}

// Dog IS an Animal, CAN-DO Pet things, HAS Trainable + Vaccinated abilities
class Dog extends Animal with Trainable, Vaccinated implements Pet {
  Dog(String name) : super(name);

  
  void play() => print('$name playing fetch');

  
  void cuddle() => print('$name cuddling');
}

void main() {
  final dog = Dog('Buddy');
  dog.eat();          // Buddy eating         ← from extends Animal
  dog.breathe();      // Buddy breathing      ← from extends Animal
  dog.play();         // Buddy playing fetch  ← from implements Pet
  dog.cuddle();       // Buddy cuddling       ← from implements Pet
  dog.train('sit');   // Learning: sit        ← from with Trainable
  dog.vaccinate();    // Vaccinated!          ← from with Vaccinated
}

extends vs implements vs with — Master Comparison

Feature
text
extends
text
implements
text
with
(mixin)
Relationship"is-a""can-do" (contract)"has-ability"
Parent constructor called?Yes (via
text
super
)
NoNo constructor allowed
Inherits method bodies?YesNo (must override ALL)Yes
Inherits fields?YesNo (must redeclare)Yes
Must override methods?Only abstract onesALL methodsOnly abstract ones
Multiple allowed?No (single only)Yes (
text
implements A, B, C
)
Yes (
text
with A, B, C
)
Can have constructor?YesYes (own only)No
Code reuse?FullNone (contract only)Full
Real-world analogyChild inherits from parentEmployee signs a job contractPerson learns a new skill

3. Polymorphism

The ability for different classes to be treated as instances of the same parent class, while behaving differently.

dart
abstract class Shape {
  double area(); // Abstract — must be implemented by subclasses
}

class Circle extends Shape {
  double radius;
  Circle(this.radius);

  
  double area() => 3.14159 * radius * radius;
}

class Rectangle extends Shape {
  double width, height;
  Rectangle(this.width, this.height);

  
  double area() => width * height;
}

void printArea(Shape shape) {
  // Same method call — different behavior based on actual type
  print('Area: ${shape.area()}');
}

void main() {
  printArea(Circle(5));         // Area: 78.54
  printArea(Rectangle(4, 6));  // Area: 24.0
  // Same function, different results — that's polymorphism
}

Two types:

  • Compile-time (Method Overloading) — not directly supported in Dart
  • Runtime (Method Overriding) — fully supported via
    text
    @override

4. Abstraction

Hiding complex implementation details and only showing the essential features. Achieved via abstract classes and interfaces.

dart
// User of PaymentGateway doesn't care HOW payment works internally
abstract class PaymentGateway {
  Future<bool> processPayment(double amount);
  void showReceipt();
}

// Different implementations — user doesn't need to know the details
class RazorpayGateway extends PaymentGateway {
  
  Future<bool> processPayment(double amount) async {
    // Complex Razorpay SDK logic hidden behind simple interface
    print('Processing ₹$amount via Razorpay');
    return true;
  }

  
  void showReceipt() => print('Razorpay Receipt');
}

class StripeGateway extends PaymentGateway {
  
  Future<bool> processPayment(double amount) async {
    print('Processing \$$amount via Stripe');
    return true;
  }

  
  void showReceipt() => print('Stripe Receipt');
}

// Usage — doesn't matter which gateway, same API
void checkout(PaymentGateway gateway, double amount) async {
  final success = await gateway.processPayment(amount);
  if (success) gateway.showReceipt();
}

4 Pillars Summary

PillarConceptDart FeatureReal-World Analogy
EncapsulationHide internal data
text
_
prefix for private members
ATM hides cash mechanism, exposes buttons
InheritanceReuse parent class
text
extends
,
text
implements
,
text
with
Child inherits traits from parents
PolymorphismSame interface, different behavior
text
@override
, abstract classes
Same "drive" action — car vs bike vs truck
AbstractionShow only essentials
text
abstract class
, interfaces
TV remote hides circuit board, shows buttons

Dart 3.0+ Class Modifiers

Dart 3.0 introduced new keywords to control how classes can be used:

dart
// abstract — cannot instantiate, can extend/implement
abstract class Shape { }

// interface — can ONLY be implemented, not extended
interface class Printable {
  void print_() => print('Printing');
}
// class A extends Printable {} // ❌ Error
class A implements Printable {   // ✅ OK
  
  void print_() => print('Custom print');
}

// final — cannot be extended OR implemented outside its library
final class Config {
  final String apiUrl;
  Config(this.apiUrl);
}
// class MyConfig extends Config {} // ❌ Error outside library
// class MyConfig implements Config {} // ❌ Error outside library

// sealed — like abstract + final, used for exhaustive pattern matching
sealed class Result {}
class Success extends Result { final String data; Success(this.data); }
class Failure extends Result { final String error; Failure(this.error); }

// Exhaustive switch — compiler ensures all cases covered
String handleResult(Result result) => switch (result) {
  Success(data: var d) => 'Got: $d',
  Failure(error: var e) => 'Error: $e',
  // No default needed — compiler knows all subtypes
};

// base — can be extended but not implemented outside its library
base class BaseService {
  void init() => print('Service initialized');
}

Class Modifiers Comparison

ModifierCan instantiate?Can
text
extends
?
Can
text
implements
?
Can use outside library?
(none)YesYesYesYes
text
abstract
NoYesYesYes
text
interface
YesNoYesYes
text
final
YesNoNoYes (use only)
text
sealed
NoYes (same library)NoYes (use only)
text
base
YesYesNoYes

5. Getters & Setters

Getters and setters provide controlled access to an object's fields. They are a core tool for encapsulation.

dart
class Temperature {
  double _celsius;  // Private backing field

  Temperature(this._celsius);

  // Getter — read access with logic
  double get celsius => _celsius;
  double get fahrenheit => (_celsius * 9 / 5) + 32;  // Computed property
  String get status {
    if (_celsius > 37) return 'Fever';
    if (_celsius < 35) return 'Hypothermia';
    return 'Normal';
  }

  // Setter — write access with validation
  set celsius(double value) {
    if (value < -273.15) {
      throw ArgumentError('Temperature cannot be below absolute zero');
    }
    _celsius = value;
  }

  set fahrenheit(double value) {
    celsius = (value - 32) * 5 / 9;  // Uses the setter with validation
  }
}

void main() {
  final temp = Temperature(36.6);

  // Getter usage — looks like a field, but runs logic
  print(temp.celsius);     // 36.6
  print(temp.fahrenheit);  // 97.88
  print(temp.status);      // Normal

  // Setter usage — looks like assignment, but runs validation
  temp.celsius = 38.5;
  print(temp.status);      // Fever

  temp.fahrenheit = 98.6;  // Sets via conversion
  print(temp.celsius);     // 37.0

  // temp.celsius = -300;  // ❌ Throws ArgumentError
}

Getter-Only (Read-Only Property)

dart
class Circle {
  final double radius;
  Circle(this.radius);

  // Read-only computed properties — no setter
  double get area => 3.14159 * radius * radius;
  double get circumference => 2 * 3.14159 * radius;

  // circle.area = 50;  // ❌ Error — no setter defined
}

Rule: Use getters for computed/derived values. Use setters for validated writes. If a field needs no logic, just use a regular

text
final
field.


6. Constructor Types

Dart supports 5 types of constructors — each serves a different purpose.

6.1 Default Constructor

dart
class User {
  String name;
  int age;

  // Default constructor with shorthand
  User(this.name, this.age);
}

final user = User('Alice', 25);

6.2 Named Constructor

Create multiple constructors with different names for different use cases.

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

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

  // Named constructors — different ways to create a User
  User.guest()
      : name = 'Guest',
        age = 0,
        role = 'guest';

  User.admin(String name)
      : this.name = name,
        age = 0,
        role = 'admin';

  User.fromJson(Map<String, dynamic> json)
      : name = json['name'],
        age = json['age'],
        role = json['role'] ?? 'user';
}

void main() {
  final user1 = User('Alice', 25, 'user');
  final user2 = User.guest();
  final user3 = User.admin('Bob');
  final user4 = User.fromJson({'name': 'Charlie', 'age': 30});
}

6.3 Factory Constructor

A constructor that doesn't always create a new instance. Can return cached instances, subtypes, or null.

dart
class Database {
  static Database? _instance;  // Cached singleton
  final String connectionString;

  // Private constructor — cannot be called from outside
  Database._internal(this.connectionString);

  // Factory — returns existing instance if available
  factory Database(String connStr) {
    _instance ??= Database._internal(connStr);
    return _instance!;
  }
}

void main() {
  final db1 = Database('mysql://localhost');
  final db2 = Database('postgres://localhost');  // Returns same instance!

  print(identical(db1, db2));  // true — same object (singleton)
}

Factory can also return subtypes:

dart
abstract class Shape {
  factory Shape(String type) {
    switch (type) {
      case 'circle': return Circle();
      case 'square': return Square();
      default: throw ArgumentError('Unknown shape: $type');
    }
  }
  double area();
}

class Circle implements Shape {
   double area() => 3.14 * 5 * 5;
}

class Square implements Shape {
   double area() => 10 * 10;
}

void main() {
  Shape s = Shape('circle');  // Returns Circle instance
  print(s.area());  // 78.5
}

6.4 Const Constructor

Creates compile-time constants — identical const objects share the same memory.

dart
class Point {
  final double x;
  final double y;

  const Point(this.x, this.y);  // All fields must be final
}

void main() {
  const p1 = Point(1, 2);
  const p2 = Point(1, 2);

  print(identical(p1, p2));  // true — same object in memory!

  // Non-const — different objects
  final p3 = Point(1, 2);
  final p4 = Point(1, 2);
  print(identical(p3, p4));  // false — different objects
}

Flutter uses const extensively

text
const Text('Hello')
is cached and reused, improving performance.

6.5 Redirecting Constructor

One constructor delegates to another constructor in the same class.

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

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

  // Redirecting — delegates to the main constructor
  User.defaultUser() : this('Guest', 0, 'guest');
  User.admin(String name) : this(name, 0, 'admin');
}

Constructor Types Comparison

ConstructorPurposeReturns New Instance?Example
DefaultStandard creationAlways
text
User('Alice', 25)
NamedMultiple creation patternsAlways
text
User.fromJson(json)
FactoryConditional creation, singletonsNot always
text
Database('url')
ConstCompile-time constantsShared if identical
text
const Point(1, 2)
RedirectingDelegate to another constructorAlways
text
User.admin('Bob')

7.
text
super
and
text
this
Keywords

text
this
— Reference to Current Instance

dart
class User {
  String name;
  int age;

  // this.name refers to the FIELD, name refers to the PARAMETER
  User(String name, int age) {
    this.name = name;  // Disambiguate field vs parameter
    this.age = age;
  }

  // Shorthand — same thing
  // User(this.name, this.age);

  void printInfo() {
    print('Name: ${this.name}');  // this is optional here
    print('Name: $name');         // Same thing — no ambiguity
  }

  // Returning this for method chaining (Builder pattern)
  User setName(String name) {
    this.name = name;
    return this;  // Return current instance for chaining
  }

  User setAge(int age) {
    this.age = age;
    return this;
  }
}

void main() {
  // Method chaining using this
  final user = User('', 0)
      .setName('Alice')
      .setAge(25);
  print(user.name);  // Alice
}

text
super
— Reference to Parent Class

dart
class Animal {
  String name;
  Animal(this.name);

  void makeSound() => print('$name makes a sound');
  void eat() => print('$name is eating');
}

class Dog extends Animal {
  String breed;

  // super() — call parent constructor
  Dog(String name, this.breed) : super(name);

  // super.method() — call parent's version of a method
  
  void makeSound() {
    super.makeSound();  // Call parent's makeSound first
    print('$name says Woof!');  // Then add own behavior
  }

  
  void eat() {
    print('$name eats dog food');  // Completely replace parent behavior
    // super.eat() NOT called — parent's eat() is ignored
  }
}

void main() {
  final dog = Dog('Buddy', 'Lab');

  dog.makeSound();
  // Output:
  // Buddy makes a sound  ← super.makeSound()
  // Buddy says Woof!     ← own behavior added

  dog.eat();
  // Output:
  // Buddy eats dog food  ← parent's eat() NOT called
}

Dart 3.0+
text
super
Parameters

dart
// Old way
class Dog extends Animal {
  String breed;
  Dog(String name, this.breed) : super(name);
}

// Dart 3.0+ — super parameters (shorter)
class Dog extends Animal {
  String breed;
  Dog(super.name, this.breed);  // super.name passes directly to parent
}

8. Method Overriding Rules

Method overriding lets a child class provide its own implementation of a parent method.

Rules

dart
class Parent {
  void greet() => print('Hello from Parent');
  int calculate(int x) => x * 2;
  void process(num value) => print('Processing: $value');
}

class Child extends Parent {
  // Rule 1: Same method name
  // Rule 2: Same or compatible return type (can be narrower)
  // Rule 3: Same parameters
  // Rule 4: Use @override annotation (recommended, not required)

  
  void greet() => print('Hello from Child');  // ✅ Same signature

  
  int calculate(int x) => x * 3;  // ✅ Same return type

  // ❌ Cannot change parameter type (without covariant)
  // void process(int value) {}  // Error — num ≠ int
}

@override Annotation — Why Use It?

dart
class Parent {
  void greet() => print('Hello');
}

class Child extends Parent {
  // Without @override — works but risky
  void greet() => print('Hi');  // ⚠️ No compile-time check

  // With @override — compiler verifies parent has this method
  
  void greet() => print('Hi');  // ✅ Compiler confirms Parent.greet() exists

  // If parent renames greet() to sayHello():
  // Without @override → silent bug (Child.greet never called polymorphically)
  // With @override → compile error (catches the problem immediately)
}

Always use

text
@override
— it catches bugs when parent methods are renamed or removed.

text
covariant
— Override Parameter Types

dart
class Animal {
  void chase(Animal other) => print('Chasing animal');
}

class Dog extends Animal {
  // ❌ Without covariant:
  // void chase(Dog other) {}  // Error — Dog is not Animal

  // ✅ With covariant — tells Dart "I guarantee only Dogs will be passed"
  
  void chase(covariant Dog other) => print('Dog chasing dog: ${other}');
}

9. Operator Overloading

Dart lets you redefine operators (

text
+
,
text
-
,
text
==
,
text
[]
, etc.) for your custom classes.

dart
class Vector {
  final double x;
  final double y;

  const Vector(this.x, this.y);

  // Overload + operator
  Vector operator +(Vector other) {
    return Vector(x + other.x, y + other.y);
  }

  // Overload - operator
  Vector operator -(Vector other) {
    return Vector(x - other.x, y - other.y);
  }

  // Overload * operator (scalar multiplication)
  Vector operator *(double scalar) {
    return Vector(x * scalar, y * scalar);
  }

  // Overload == operator
  
  bool operator ==(Object other) {
    if (other is! Vector) return false;
    return x == other.x && y == other.y;
  }

  
  int get hashCode => Object.hash(x, y);

  // Overload [] operator (index access)
  double operator [](int index) {
    if (index == 0) return x;
    if (index == 1) return y;
    throw RangeError('Index must be 0 or 1');
  }

  
  String toString() => 'Vector($x, $y)';
}

void main() {
  const a = Vector(1, 2);
  const b = Vector(3, 4);

  print(a + b);     // Vector(4.0, 6.0)   ← + operator
  print(a - b);     // Vector(-2.0, -2.0) ← - operator
  print(a * 3);     // Vector(3.0, 6.0)   ← * operator
  print(a == b);    // false              ← == operator
  print(a[0]);      // 1.0               ← [] operator
}

Overloadable Operators in Dart

OperatorExamplePurpose
text
+
text
-
text
*
text
/
text
a + b
Arithmetic
text
==
text
a == b
Equality (always override
text
hashCode
too)
text
<
text
>
text
<=
text
>=
text
a > b
Comparison
text
[]
text
[]=
text
a[0]
,
text
a[0] = x
Index access
text
~
text
~a
Bitwise NOT
text
unary-
text
-a
Negation

Important: Always override

text
hashCode
when you override
text
==
. Dart uses both in
text
Map
,
text
Set
, and collections.


10. Static vs Instance Members

Static members belong to the class itself, not to any instance. Instance members belong to each individual object.

dart
class Counter {
  // Static — shared across ALL instances, belongs to class
  static int totalCount = 0;
  static const int maxLimit = 100;

  // Instance — unique to EACH object
  String name;
  int count = 0;

  Counter(this.name) {
    totalCount++;  // Track how many Counter objects created
  }

  // Instance method — can access both instance AND static members
  void increment() {
    count++;
    print('$name count: $count (total instances: $totalCount)');
  }

  // Static method — can ONLY access static members
  static void printTotal() {
    print('Total Counter instances: $totalCount');
    // print(name);  // ❌ Error — cannot access instance member from static
    // print(count); // ❌ Error — cannot access instance member from static
  }

  // Static factory pattern
  static Counter createDefault() {
    return Counter('Default');
  }
}

void main() {
  final a = Counter('A');
  final b = Counter('B');
  final c = Counter('C');

  a.increment();  // A count: 1 (total instances: 3)
  b.increment();  // B count: 1 (total instances: 3)

  // Static accessed via CLASS name, not instance
  Counter.printTotal();  // Total Counter instances: 3
  print(Counter.maxLimit);  // 100

  // a.printTotal();  // ⚠️ Works but bad practice — use Counter.printTotal()
}

Static vs Instance Comparison

FeatureStaticInstance
Belongs toClassObject
Accessed via
text
ClassName.member
text
object.member
MemoryOne copy (shared)One copy per object
Can access instance members?NoYes
Can access static members?YesYes
Use caseConstants, counters, factories, utilitiesObject-specific data and behavior
Dart example
text
DateTime.now()
,
text
Colors.blue
text
widget.title
,
text
state.count

Common Static Patterns in Flutter

dart
// 1. Route names
class AppRoutes {
  static const String home = '/';
  static const String profile = '/profile';
  static const String settings = '/settings';
}

// 2. API constants
class ApiConstants {
  static const String baseUrl = 'https://api.example.com';
  static const Duration timeout = Duration(seconds: 30);
}

// 3. Helper/Utility methods
class Validators {
  static bool isEmail(String value) => value.contains('@');
  static bool isPhone(String value) => value.length == 10;
}

// Usage
Navigator.pushNamed(context, AppRoutes.profile);
if (Validators.isEmail(input)) { ... }

Real-World Flutter Example — Clean Architecture

dart
// DOMAIN LAYER — abstract class (contract)
abstract class UserRepository {
  Future<User> getUser(String id);
  Future<void> saveUser(User user);
}

// DATA LAYER — implements (fulfills the contract)
class UserRepositoryImpl implements UserRepository {
  final ApiClient api;
  final LocalDatabase db;
  UserRepositoryImpl(this.api, this.db);

  
  Future<User> getUser(String id) async {
    try {
      return await api.fetchUser(id);
    } catch (_) {
      return await db.getCachedUser(id);
    }
  }

  
  Future<void> saveUser(User user) async {
    await api.updateUser(user);
    await db.cacheUser(user);
  }
}

// TESTING — implements (mock for unit tests)
class MockUserRepository implements UserRepository {
  
  Future<User> getUser(String id) async => User(id: id, name: 'Mock User');

  
  Future<void> saveUser(User user) async => print('Mock save');
}

// PRESENTATION — uses abstract type, doesn't care about implementation
class UserViewModel {
  final UserRepository repo;  // Could be real or mock
  UserViewModel(this.repo);

  Future<User> loadUser(String id) => repo.getUser(id);
}

Quick Decision Flowchart

text
Do you need to REUSE parent code?
├── YES → Does the child "is-a" parent?
│         ├── YES → Use extends
│         └── NO  → Use mixin (with)
└── NO  → Do you need a contract/API guarantee?
          ├── YES → Use implements
          └── NO  → Use a regular class

Multiple sources of code needed?
├── YES → Use mixins (with) — can mix multiple
└── NO  → Use extends (single parent)

Need exhaustive pattern matching? → Use sealed class
Need to prevent others from extending? → Use final class
Need to force implements-only? → Use interface class

Key Takeaway:

text
extends
= inherit code + constructor chain.
text
implements
= contract only, no inheritance, no parent constructor.
text
with
(mixin) = plug-in reusable abilities from multiple sources. Understanding constructor behavior (
text
extends
calls parent,
text
implements
doesn't) is the #1 interview question in this topic.