Dart, the programming language powering Flutter, is an object-oriented language. Object-oriented programming (OOP) is a programming paradigm that revolves around objects and classes, allowing developers to model real-world entities efficiently. Understanding classes and objects is fundamental to writing clean, maintainable, and scalable Dart code. This article explores classes, objects, and how to create your first class in Dart.
1. Introduction to Object-Oriented Programming in Dart
Object-oriented programming focuses on representing real-world entities as objects, which are instances of classes. A class is like a blueprint, while an object is the actual entity created using that blueprint.
- Class: Defines properties (attributes) and methods (functions) of an entity.
- Object: A concrete instance of a class with real values assigned to properties.
OOP in Dart allows you to write code that is modular, reusable, and easy to maintain.
2. Why Use Classes and Objects?
Using classes and objects provides several advantages:
- Encapsulation: Keep data and behavior together.
- Reusability: Create multiple objects from the same class.
- Modularity: Organize code into logical units.
- Abstraction: Hide internal details and expose only necessary functionality.
- Inheritance & Polymorphism: Extend and customize classes easily.
These principles help developers write robust and scalable applications, especially when working with Flutter apps that rely heavily on OOP concepts.
3. Creating Your First Class in Dart
Let’s start with the basics. Suppose we want to represent a simple Car entity. A car has attributes like make, model, and year, and behaviors like start() and stop().
Example: Basic Class
class Car {
String make;
String model;
int year;
void start() {
print('$make $model is starting...');
}
void stop() {
print('$make $model is stopping...');
}
}
void main() {
var myCar = Car();
myCar.make = 'Toyota';
myCar.model = 'Corolla';
myCar.year = 2020;
myCar.start(); // Output: Toyota Corolla is starting...
myCar.stop(); // Output: Toyota Corolla is stopping...
}
Here’s what’s happening:
Caris the class blueprint.myCaris an object created from theCarclass.- Properties
make,model, andyeardefine the state. - Methods
start()andstop()define behaviors.
4. Constructors in Dart
Constructors are special methods used to initialize objects when they are created. Dart provides several ways to define constructors.
Default Constructor
class Car {
String make;
String model;
int year;
// Default constructor
Car() {
print('A new car object is created');
}
}
void main() {
var myCar = Car(); // Output: A new car object is created
}
Parameterized Constructor
class Car {
String make;
String model;
int year;
// Parameterized constructor
Car(this.make, this.model, this.year);
void display() {
print('$make $model, Year: $year');
}
}
void main() {
var myCar = Car('Honda', 'Civic', 2022);
myCar.display(); // Output: Honda Civic, Year: 2022
}
Here, this.make refers to the property of the class and is initialized via the constructor parameters.
5. Named Constructors
Dart allows named constructors to create multiple ways to initialize an object. This is useful when objects need to be created in different contexts.
class Car {
String make;
String model;
int year;
// Default constructor
Car(this.make, this.model, this.year);
// Named constructor
Car.oldModel(String make, String model) {
this.make = make;
this.model = model;
this.year = 2000;
}
void display() {
print('$make $model, Year: $year');
}
}
void main() {
var car1 = Car('Ford', 'Mustang', 2023);
var car2 = Car.oldModel('Chevrolet', 'Impala');
car1.display(); // Output: Ford Mustang, Year: 2023
car2.display(); // Output: Chevrolet Impala, Year: 2000
}
Named constructors make object creation more flexible and expressive.
6. Getters and Setters in Dart
Getters and setters allow you to control access to the properties of a class. They are useful for validation, computation, or encapsulation.
class Car {
String _make; // private property
String _model;
int _year;
Car(this._make, this._model, this._year);
// Getter
String get make => _make;
// Setter with validation
set year(int y) {
if (y > 1885) {
_year = y;
} else {
print('Invalid year');
}
}
void display() {
print('$_make $_model, Year: $_year');
}
}
void main() {
var myCar = Car('BMW', 'X5', 2021);
myCar.display(); // Output: BMW X5, Year: 2021
myCar.year = 1800; // Output: Invalid year
}
The underscore _ makes a property private to the library. Using getters and setters provides controlled access to class fields.
7. Static Properties and Methods
Static members belong to the class rather than an instance. They are shared across all objects of the class.
class Car {
static int numberOfCars = 0;
Car() {
numberOfCars++;
}
static void displayCount() {
print('Total cars: $numberOfCars');
}
}
void main() {
var car1 = Car();
var car2 = Car();
Car.displayCount(); // Output: Total cars: 2
}
Static methods cannot access instance variables directly but are useful for utility methods or counters.
8. Inheritance in Dart
Inheritance allows one class to acquire properties and methods from another class. This promotes code reuse.
class Vehicle {
void start() {
print('Vehicle is starting...');
}
}
class Car extends Vehicle {
void honk() {
print('Car is honking...');
}
}
void main() {
var myCar = Car();
myCar.start(); // Inherited from Vehicle
myCar.honk(); // Own method
}
Car inherits from Vehicle, so it can access start() without redefining it.
9. Abstract Classes
Abstract classes cannot be instantiated directly. They are used as base classes for other classes. They can have abstract methods that must be implemented by subclasses.
abstract class Vehicle {
void start(); // Abstract method
}
class Car extends Vehicle {
@override
void start() {
print('Car is starting...');
}
}
void main() {
var myCar = Car();
myCar.start(); // Output: Car is starting...
}
Abstract classes define contracts for subclasses while providing shared behavior.
10. Interfaces in Dart
Every class in Dart can act as an interface. You can implement an interface using the implements keyword.
class Engine {
void startEngine() {
print('Engine started');
}
}
class Car implements Engine {
@override
void startEngine() {
print('Car engine started');
}
}
void main() {
var myCar = Car();
myCar.startEngine(); // Output: Car engine started
}
Interfaces allow you to define expected behavior without inheritance.
11. Mixins in Dart
Mixins are a way to reuse code in multiple class hierarchies without inheritance. They are defined using the mixin keyword.
mixin Electric {
void charge() {
print('Charging...');
}
}
class Car {
void drive() {
print('Driving...');
}
}
class ElectricCar extends Car with Electric {}
void main() {
var tesla = ElectricCar();
tesla.drive(); // Output: Driving...
tesla.charge(); // Output: Charging...
}
Mixins are ideal for adding reusable functionality to multiple unrelated classes.
12. Practical Example – Employee Management System
class Employee {
String name;
int id;
double salary;
Employee(this.name, this.id, this.salary);
void display() {
print('Employee: $name, ID: $id, Salary: \$${salary}');
}
}
class Manager extends Employee {
double bonus;
Manager(String name, int id, double salary, this.bonus) : super(name, id, salary);
@override
void display() {
super.display();
print('Bonus: \$${bonus}');
}
}
void main() {
var emp = Employee('Alice', 101, 50000);
var mgr = Manager('Bob', 102, 80000, 10000);
emp.display();
mgr.display();
}
This example demonstrates constructors, inheritance, method overriding, and object-oriented design.
13. Best Practices for Classes and Objects
- Use private fields and getters/setters to protect data.
- Keep classes focused on a single responsibility.
- Use named constructors for flexible object creation.
- Favor composition over inheritance when possible.
- Leverage abstract classes and interfaces for modular design.
- Avoid long classes; break them into smaller reusable components.
Leave a Reply