Dart: Dart Classes and Objects — Core of Object-Oriented

Last updated: 2026-08-26

A class is a blueprint for objects — a good blueprint makes good objects, and good objects build good systems.

1. What You Will Learn


2. A Developer's Real Story

(1) Pain Point: Dispersed Logic Due to Chaotic Data Models

In the early days of Bob's DataPipeline, all data was handled using Maps, with no type safety. order['amout'] (a typo) returned null at runtime instead of throwing an error, causing the amount statistics for 100,000 orders to be completely zero. Worse still, the logic for creating orders was scattered across 5 files, with no unified validation.

(2) Solution with Classes

Using classes to define data models, constructors to centralize validation logic, and the type system to catch typos and type mismatches at compile time.

DART
class Order {
  final String id;
  final double amount;
  final DateTime date;

  Order({required this.id, required this.amount, required this.date})
      : assert(amount > 0, 'Amount must be positive');

  double calcTax(double rate) => amount * rate;
}
TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.

(3) Benefits


3. Class Definition Basics

(1) Structure of a Class

▶ Example

TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.

: Basic Class Definition

DART
class Product {
  // Fields
  final String name;
  final double price;
  String category;

  // Constructor
  Product({required this.name, required this.price, this.category = 'General'});

  // Method
  String formatPrice() => '\$${price.toStringAsFixed(2)} USD';

  // Getter
  bool get isExpensive => price >= 1000;

  // Override toString
  @override
  String toString() => 'Product($name, ${formatPrice()}, $category)';
}

void main() {
  final product = Product(name: 'Laptop', price: 1299.99, category: 'Electronics');
  print(product);                  // Product(Laptop, $1299.99 USD, Electronics)
  print(product.isExpensive);      // true
  print(product.formatPrice());    // $1299.99 USD
}
TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.
Class Member Declaration Syntax Description
Field Type name Instance variable
Constructor ClassName() Creates an object
Method returnType name() {} Instance method
Getter Type get name => expr Computed property
Setter set name(Type value) Assignment interception

4. Five Types of Constructors

(1) DataPipeline Domain Model

100%
classDiagram
  class Order {
    +String id
    +double amount
    +DateTime date
    +calcTax(double rate) double
  }
  class Product {
    +String name
    +double price
    +formatUSD() String
  }
  class Customer {
    +String name
    +List~Order~ orders
  }
  Customer "1" --> "*" Order : has
  Order "1" --> "*" Product : contains
TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.

▶ Example

TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.

: Default Constructor and Named Constructors

DART
class Order {
  final String id;
  final double amount;
  final DateTime date;
  String status;

  // Default constructor
  Order({required this.id, required this.amount, required this.date, this.status = 'pending'});

  // Named constructor - create from CSV row
  Order.fromCsv(String csvLine)
      : id = csvLine.split(',')[0],
        amount = double.parse(csvLine.split(',')[1]),
        date = DateTime.parse(csvLine.split(',')[2]),
        status = 'pending';

  // Named constructor - create with default date
  Order.now({required this.id, required this.amount})
      : date = DateTime.now(),
        status = 'pending';

  double calcTax(double rate) => amount * rate;

  @override
  String toString() => 'Order($id, \$${amount.toStringAsFixed(2)}, $status)';
}
TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.

▶ Example

TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.

: Factory Constructor

DART
class DataSource {
  final String type;
  final String connection;

  // Private constructor
  DataSource._internal(this.type, this.connection);

  // Factory constructor - returns cached or custom instance
  factory DataSource.create({required String type, required String connection}) {
    return DataSource._internal(type, connection);
  }

  // Factory with default configurations
  factory DataSource.api(String endpoint) =>
      DataSource._internal('api', endpoint);

  factory DataSource.file(String path) =>
      DataSource._internal('file', path);

  factory DataSource.database(String connectionString) =>
      DataSource._internal('database', connectionString);
}

void main() {
  final api = DataSource.api('https://api.example.com/orders');
  final file = DataSource.file('/data/orders.csv');
  print('${api.type}: ${api.connection}');
  print('${file.type}: ${file.connection}');
}
TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.

▶ Example

TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.

: Redirecting Constructor

DART
class Report {
  final String title;
  final String format;
  final int recordCount;

  // Main constructor
  Report({required this.title, this.format = 'json', this.recordCount = 0});

  // Redirecting constructors
  Report.json(String title, int count) : this(title: title, format: 'json', recordCount: count);
  Report.csv(String title, int count) : this(title: title, format: 'csv', recordCount: count);
  Report.html(String title, int count) : this(title: title, format: 'html', recordCount: count);
}
TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.

▶ Example

TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.

: Constant Constructor

DART
class Config {
  final String appName;
  final int maxRecords;

  // const constructor - all fields must be final
  const Config({this.appName = 'DataPipeline', this.maxRecords = 1000000});
}

void main() {
  const config1 = Config();                              // Compile-time constant
  const config2 = Config(maxRecords: 500000);
  print(identical(config1, config2));  // false (different values)
  print(config1.maxRecords);           // 1000000
}
TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.
Constructor Type Syntax Features Scenarios
Default ClassName() Auto-generated or customized Standard creation
Named ClassName.name() Multiple creation methods fromCsv, now
Factory factory ClassName() Can return subclasses or cached instances Singleton, caching
Redirecting : this() Delegates to the main constructor Convenient creation
Constant const ClassName() Created at compile time, immutable Configuration constants

5. Inheritance and Method Overriding

▶ Example

TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.

: Inheritance Hierarchy

DART
// Base class
class DataProcessor {
  final String name;

  DataProcessor(this.name);

  // Method to be overridden
  String process(String input) => 'Processing $input with $name';

  // Non-overridable method
  String getStatus() => 'Ready';

  @override
  String toString() => '$name Processor';
}

// Subclass
class OrderProcessor extends DataProcessor {
  final double taxRate;

  OrderProcessor(this.taxRate) : super('Order');

  @override
  String process(String input) {
    final amount = double.tryParse(input) ?? 0;
    final taxed = amount * (1 + taxRate);
    return 'Order processed: \$${taxed.toStringAsFixed(2)} USD';
  }
}

// Another subclass
class ProductProcessor extends DataProcessor {
  ProductProcessor() : super('Product');

  @override
  String process(String input) => 'Product cataloged: $input';
}

void main() {
  final orderProc = OrderProcessor(0.08);
  print(orderProc.process('1500'));     // Order processed: $1620.00 USD
  print(orderProc.getStatus());         // Ready

  final productProc = ProductProcessor();
  print(productProc.process('Laptop')); // Product cataloged: Laptop
}
TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.

6. Abstract Classes and Interfaces

(1) Abstract Class

▶ Example

TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.

: Abstract Class and Implementation

DART
// Abstract class - cannot be instantiated
abstract class ReportGenerator {
  final String format;

  ReportGenerator(this.format);

  // Abstract method - must be implemented
  String generate(List<Map<String, dynamic>> data);

  // Concrete method - shared implementation
  String header(String title) => '=== $title ($format) ===';
}

class JsonReportGenerator extends ReportGenerator {
  JsonReportGenerator() : super('json');

  @override
  String generate(List<Map<String, dynamic>> data) {
    final buffer = StringBuffer();
    buffer.writeln(header('DataPipeline Report'));
    for (final entry in data) {
      buffer.writeln('  ${entry['id']}: ${entry['amount']}');
    }
    return buffer.toString();
  }
}

class CsvReportGenerator extends ReportGenerator {
  CsvReportGenerator() : super('csv');

  @override
  String generate(List<Map<String, dynamic>> data) {
    final lines = ``<String>``['id,amount'];
    for (final entry in data) {
      lines.add('${entry['id']},${entry['amount']}');
    }
    return lines.join('\n');
  }
}
TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.

(2) Implicit Interface

Every class in Dart implicitly defines an interface, containing all its instance methods and fields. Other classes can implements this interface.

▶ Example

TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.

: Implementing an Implicit Interface

DART
class FileDataSource {
  final String path;
  FileDataSource(this.path);

  List``<String>`` readLines() => ['line1', 'line2'];
  bool get exists => true;
}

// Implement the implicit interface of FileDataSource
class MockDataSource implements FileDataSource {
  @override
  String path = '/mock/data.csv';

  @override
  List``<String>`` readLines() => ['mock1', 'mock2', 'mock3'];

  @override
  bool get exists => true;
}
TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.
Aspect abstract class interface (implements) mixin
Instantiation No No
Method Implementation Optional Must re-implement all Optional
Multiple Inheritance Single inheritance Can implement multiple Can mix in multiple
Constructor Yes No No

7. Complete Example: DataPipeline Domain Model

DART
// ============================================
// DataPipeline Domain Model
// Order, Product, Customer with full OOP
// ============================================

class Product {
  final String id;
  final String name;
  final double price;
  final String category;

  const Product({
    required this.id,
    required this.name,
    required this.price,
    this.category = 'General',
  });

  String formatUSD() => '\$${price.toStringAsFixed(2)} USD';

  @override
  String toString() => 'Product($name, ${formatUSD()})';
}

class Order {
  final String id;
  final double amount;
  final DateTime date;
  String status;
  final List``<Product>`` products;

  Order({
    required this.id,
    required this.amount,
    required this.date,
    this.status = 'pending',
    this.products = const [],
  }) : assert(amount > 0, 'Amount must be positive');

  Order.now({required this.id, required this.amount, this.products = const []})
      : date = DateTime.now(),
        status = 'pending';

  double calcTax(double rate) => amount * rate;
  double calcTotal(double taxRate) => amount + calcTax(taxRate);

  @override
  String toString() => 'Order($id, \$${amount.toStringAsFixed(2)}, $status, ${products.length} items)';
}

class Customer {
  final String name;
  final String email;
  final List``<Order>`` orders;

  Customer({required this.name, required this.email, List``<Order>``? orders})
      : orders = orders ?? [];

  double get totalSpent => orders.fold(0.0, (sum, o) => sum + o.amount);
  int get orderCount => orders.length;
  double get averageOrderValue => orderCount > 0 ? totalSpent / orderCount : 0;

  void addOrder(Order order) => orders.add(order);

  String tier() => switch (totalSpent) {
    >= 50000 => 'Enterprise',
    >= 10000 => 'Premium',
    >= 1000 => 'Standard',
    _ => 'Free',
  };

  @override
  String toString() => 'Customer($name, ${tier()}, ${orderCount} orders, \$${totalSpent.toStringAsFixed(2)} total)';
}

void main() {
  final laptop = Product(id: 'P001', name: 'Laptop', price: 1299.99, category: 'Electronics');
  final mouse = Product(id: 'P002', name: 'Mouse', price: 29.99, category: 'Electronics');

  final alice = Customer(name: 'Alice', email: 'alice@example.com');
  final bob = Customer(name: 'Bob', email: 'bob@example.com');

  alice.addOrder(Order(id: 'ORD-001', amount: 1500.0, date: DateTime(2024, 1, 15), products: [laptop]));
  alice.addOrder(Order(id: 'ORD-002', amount: 3200.0, date: DateTime(2024, 2, 20)));
  bob.addOrder(Order(id: 'ORD-003', amount: 890.0, date: DateTime(2024, 3, 10), products: [mouse]));

  print('=== DataPipeline Customers ===');
  print(alice);
  print(bob);

  print('\n--- Alice Orders ---');
  for (final order in alice.orders) {
    print('  $order (tax: \$${order.calcTax(0.08).toStringAsFixed(2)} USD)');
  }

  print('\nTier Summary:');
  print('  Alice: ${alice.tier()} (\$${alice.totalSpent.toStringAsFixed(2)} total)');
  print('  Bob:   ${bob.tier()} (\$${bob.totalSpent.toStringAsFixed(2)} total)');
}
TEXT 📖 Display only
> **Output:** Run locally in DartPad or via `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x; results may vary slightly with different SDK versions.

Output:

TEXT 📖 Display only
=== DataPipeline Customers ===
Customer(Alice, Premium, 2 orders, $4700.00 total)
Customer(Bob, Standard, 1 orders, $890.00 total)

--- Alice Orders ---
  Order(ORD-001, $1500.00, pending, 1 items) (tax: $120.00 USD)
  Order(ORD-002, $3200.00, pending, 0 items) (tax: $256.00 USD)

Tier Summary:
  Alice: Premium ($4700.00 total)
  Bob:   Standard ($890.00 total)

❓ FAQ

Q: Does Dart have multiple inheritance? A: No. Dart only supports single inheritance (extends), but you can implement multiple interfaces with implements, or mix in multiple mixins with with.

Q: What's the difference between a factory constructor and a regular constructor? A: A regular constructor always creates a new instance. A factory constructor can return a cached instance, a subclass instance, or even execute complex logic before deciding what to return. It's declared with the factory keyword.

Q: What is a const constructor for? A: A const constructor creates a compile-time constant object. const constructors with the same arguments will return the same (identical) instance. It's suitable for immutable objects like configuration classes or enum values.

Q: How to choose between abstract class and interface? A: Use abstract class with extends when you need to share implementation code. Use interface (via implements) when you only need to define a contract. Dart has no dedicated interface keyword; every class itself is an interface.

Q: When should I use @override? A: You should add @override when overriding a parent class method. Although not strictly required, adding it allows the compiler to help you check if you are correctly overriding (it will report an error if the parent class doesn't have that method).

Q: Is Dart's assert effective in production? A: No. assert is only executed in debug mode and is ignored in release mode. It's used for development-time validation and cannot replace proper parameter validation.

Q: Can a named constructor be a factory? A: Yes. For example, factory Order.fromCsv(String line) is valid. It can return a default instance instead of throwing an exception if parsing fails.


📖 Summary


📝 Exercises

  1. Basic (Difficulty ⭐): Define a Product class with name, price, category fields. Add a formatUSD() method and an isExpensive getter. Create 3 product instances and print them.
  2. Intermediate (Difficulty ⭐⭐): Add a named constructor Order.fromMap (create from a Map) and a factory constructor Order.safe (return a default order if parsing fails) to the Order class. Test edge cases.
  3. Challenge (Difficulty ⭐⭐⭐): Design an abstract ReportGenerator class. Create two subclasses, JsonReport and CsvReport, to implement polymorphic report generation. Then create a MockReport that implements the same interface for testing.

← Previous Lesson | Next Lesson →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏