🚀 Lesson 7: Advanced OOP Features

⏱ 30-45 min 📊 Advanced 📖 In-depth tutorial 🔗 Official Source
0

Beyond Basic OOP: Four Power Features

🧠 These features make Dart uniquely powerful for building complex applications

You already know classes, inheritance, and polymorphism. Now learn the advanced tools that professional Dart developers use every day.

🎨

Enhanced Enums

Enums with properties, methods, constructors, and even interfaces — far beyond simple named values.

🔌

Extensions

Add new methods and getters to any existing class — even classes you don't own or can't modify.

đŸ§Ŧ

Mixins

Compose classes from reusable pieces of behavior. Like multiple inheritance, but safe and controlled.

🏭

Factory Constructors

Create objects conditionally — return cached instances, subtypes, or even null from a constructor.

1

Enhanced Enums — Enums with Superpowers

🧠 Before Dart 2.17, enums were just named constants. Now they can have properties, methods, and constructors.

This transforms enums from simple labels into full-featured types that can carry data and logic.

👴 Old Enums (Pre-2.17)

enum LogLevel {
  debug, info, warning, error
}
// That's it — just names.
// Need severity? Use a switch.
// Need icons? Use a separate map.

😰 Data and logic scattered across switch statements and maps

✨ Enhanced Enums (Dart 2.17+)

enum LogLevel {
  debug(0, '🔍'),
  info(1, 'â„šī¸'),
  warning(2, 'âš ī¸'),
  error(3, '❌');
  
  final int severity;
  final String icon;
  
  const LogLevel(this.severity, this.icon);
  
  bool get isError => severity >= 3;
}

😍 Self-contained: data, logic, and values all in one place

Complete Enhanced Enum with Line-by-Line Explanation

enum HttpStatus {
  // Values with constructor arguments
  ok(200, 'OK'),
  notFound(404, 'Not Found'),
  serverError(500, 'Internal Server Error');
  
  // Properties (one per value)
  final int code;
  final String message;
  
  // Constructor (must be const)
  const HttpStatus(this.code, this.message);
  
  // Computed property — logic inside the enum!
  bool get isSuccess => code >= 200 && code < 300;
  
  // Static method to look up by code
  static HttpStatus? fromCode(int code) {
    return values.cast<HttpStatus?>().firstWhere(
      (s) => s?.code == code,
      orElse: () => null,
    );
  }
}

📝 Breakdown

1 enum declares an enhanced enum
2-4 Each value calls the constructor with args
7-8 Properties — one per value, final required
11 Constructor must be const
14 Getter adds behavior without storing data
17-21 Static method — utility on the enum itself
18 values is a built-in list of all enum values

đŸ—ī¸ Build Your Own Enhanced Enum

📄 Generated Code

enum HttpStatus {
  ok(200, 'OK'),
  notFound(404, 'Not Found'),
  serverError(500, 'Internal Server Error');
  
  final int code;
  final String message;
  
  const HttpStatus(this.code, this.message);
  
  bool get isSuccess => code >= 200 && code < 300;
  bool get isError => code >= 400;
  
  static HttpStatus? fromCode(int code) {
    return values.cast<HttpStatus?>().firstWhere(
      (s) => s?.code == code,
      orElse: () => null,
    );
  }
}
2

Extensions — Adding Functionality to Existing Types

🧠 Extensions let you add methods to any class — even built-in types like String, int, or List

You don't need to subclass or modify the original class. Define an extension and use the new methods as if they were always there. This is perfect for adding utility methods without cluttering the original API.

Complete Extension Example with Line-by-Line Explanation

// Define an extension on the String class
extension StringExtensions on String {
  
  // Capitalize the first letter
  String get capitalize {
    if (isEmpty) return this;
    return this[0].toUpperCase() + substring(1);
  }
  
  // Check if the string is a valid email
  bool get isValidEmail {
    return contains('@') && contains('.');
  }
  
  // Convert "Hello World" → "hello-world"
  String get toSlug {
    return toLowerCase()
        .replaceAll(RegExp(r'\s+'), '-')
        .replaceAll(RegExp(r'[^a-z0-9-]'), '');
  }
}

// Usage — these look like built-in methods!
void main() {
  print('hello'.capitalize);     // Hello
  print('a@b.c'.isValidEmail);    // true
  print('Hello World!'.toSlug);   // hello-world
}

📝 Breakdown

2 extension ... on String adds to String
5-8 Getter: accessed like a property, no ()
5 get creates a computed property
11-13 Another getter returning bool
16-20 Getter with RegExp transformations
24-28 Used exactly like built-in methods
25 'hello'.capitalize — clean syntax!

Real-World Extension Use Cases

📅 DateTime Extensions

extension DateHelpers on DateTime {
  String get timeAgo {
    final diff = DateTime.now().difference(this);
    if (diff.inDays > 365) return '\${(diff.inDays/365).floor()}y ago';
    if (diff.inDays > 0) return '\${diff.inDays}d ago';
    if (diff.inHours > 0) return '\${diff.inHours}h ago';
    return 'just now';
  }
}

đŸ”ĸ Number Formatting

extension NumberFormat on num {
  String get toFileSize {
    if (this < 1024) return '\$this B';
    if (this < 1048576) return '\${(this/1024).toStringAsFixed(1)} KB';
    return '\${(this/1048576).toStringAsFixed(1)} MB';
  }
}

📋 List Utilities

extension ListUtils<T> on List<T> {
  T? secondOrNull() => length > 1 ? this[1] : null;
  bool get isNotEmptyAndNotNull => isNotEmpty;
}

âœī¸ Try It: Test String Extensions

Results will appear here...
3

Mixins — Composing Classes from Reusable Behaviors

🧠 A mixin is a class that provides methods to other classes — without being a parent class

Unlike inheritance (which creates an "is-a" relationship), mixins provide a "has-capability" relationship. Use the with keyword to mix in one or more mixins. A single class can use multiple mixins, composing behavior from many sources.

🔑 Mixin vs Inheritance:
â€ĸ Inheritance (extends): A Dog is an Animal. Single parent only.
â€ĸ Mixins (with): A Dog has logging capability, validation capability. Multiple mixins allowed.
â€ĸ Mixins cannot be instantiated on their own — they only work when mixed into a class.

Defining and Using Mixins

// Define mixins (use 'mixin' keyword instead of 'class')
mixin Logger {
  void log(String message) => print('[LOG] \$message');
  void error(String message) => print('[ERROR] \$message');
}

mixin Validator {
  bool isValidEmail(String email) => email.contains('@');
  bool isNotEmpty(String? value) => value != null && value.isNotEmpty;
}

// Use mixins with the 'with' keyword
class UserService with Logger, Validator {
  void createUser(String email) {
    if (!isValidEmail(email)) {
      error('Invalid email: \$email');
      return;
    }
    log('User created with email: \$email');
  }
}

void main() {
  var service = UserService();
  service.createUser('test@example.com'); // [LOG] User created...
  service.createUser('bad-email');      // [ERROR] Invalid email...
}

đŸ§Ŧ Available Mixins

đŸ—ī¸ Your Composed Class

class UserService {
  // Click mixins to add capabilities
}
Available Methods:
No mixins selected

đŸŒŗ Class Hierarchy

Object → (select mixins) → UserService
4

Factory Constructors — Flexible Object Creation

🧠 A factory constructor doesn't always create a new instance

Unlike regular constructors, factory constructors can return an existing cached instance, a subtype, or even null. Use them when object creation needs logic beyond simple field assignment.

🔑 Common Factory Use Cases:
â€ĸ Caching/Singletons: Return the same instance every time
â€ĸ Subtype selection: Return different subclasses based on parameters
â€ĸ Validation: Check conditions before creating the object
â€ĸ Async initialization: Factory can return a Future (rare)

Factory Constructor Patterns

class User {
  final String name;
  final String role;
  final List<String> permissions;
  
  // Private constructor — only factories can call it
  User._(this.name, this.role, this.permissions);
  
  // Factory 1: Create admin with full permissions
  factory User.admin(String name) {
    return User._(name, 'Admin', ['read', 'write', 'delete']);
  }
  
  // Factory 2: Create regular user with limited permissions
  factory User.regular(String name) {
    return User._(name, 'User', ['read', 'write']);
  }
  
  // Factory 3: Create guest with read-only access
  factory User.guest() {
    return User._('Guest', 'Guest', ['read']);
  }
}

// Usage — each factory creates a differently configured User
void main() {
  var admin = User.admin('Alice');   // Admin with full permissions
  var user = User.regular('Bob');   // Regular user
  var guest = User.guest();          // Guest — no name needed
  
  print(admin.permissions); // [read, write, delete]
  print(guest.permissions); // [read]
}

🎮 Simulate Factory Creation

Select a user type to see the factory in action...

đŸŽ¯ Advanced OOP Quiz

Score: 0/5 | Hints: 3

Loading question...

🚀

Practice Projects

Apply what you've learned by building these hands-on projects.

đŸ“Ļ Project 1 đŸ“Ļ Project 2