6. Mastering Object-Oriented Programming (OOP) in Dart
1. OOP Fundamentals in Dart
Dart treats everything as an object—even primitives like numbers and functions. This design makes Dart consistent and flexible in its object-oriented capabilities. The language supports the foundational OOP concepts:
Classes and Objects: Define blueprints (classes) and create instances (objects).
Inheritance: Extend existing classes for reuse and specialization.
Polymorphism: Implement common interfaces with different behaviors.
Encapsulation: Protect data using private fields and expose controlled access through getters and setters.
2. Defining a Class
A class is a blueprint for creating objects. It defines the properties (fields) and behaviors (methods) of an object.
Example: Basic Class
class Person {
String name;
int age;
// Constructor
Person(this.name, this.age);
// Method
void introduce() {
print("Hi, I'm $name and I'm $age years old.");
}
}
void main() {
var person = Person("Alice", 25);
person.introduce(); // Output: Hi, I'm Alice and I'm 25 years old.
}
Fields:
nameandagerepresent the properties of thePersonclass.Constructor:
Person(this.name, this.age)initializes the object.Method:
introduce()encapsulates a reusable behavior.
3. Key OOP Concepts in Dart
a) Inheritance
Inheritance enables a class (child) to inherit properties and methods from another class (parent), facilitating code reuse and hierarchical relationships.
Example: Inheritance
class Student extends Person {
String school;
Student(String name, int age, this.school) : super(name, age);
@override
void introduce() {
super.introduce();
print("I study at $school.");
}
}
void main() {
var student = Student("Bob", 20, "XYZ University");
student.introduce();
// Output:
// Hi, I'm Bob and I'm 20 years old.
// I study at XYZ University.
}
extendsKeyword: Indicates the child classStudentinherits fromPerson.superKeyword: Refers to the parent class, allowing access to its methods or constructors.Method Overriding: The child class can redefine methods to customize behavior.
b) Encapsulation
Encapsulation restricts direct access to an object’s internal state and provides controlled access through getters and setters. This principle promotes modularity and prevents unintended interference with data.
Example: Encapsulation
class BankAccount {
double _balance = 0; // Private field
double get balance => _balance; // Getter
void deposit(double amount) {
if (amount > 0) {
_balance += amount;
}
}
void withdraw(double amount) {
if (amount > 0 && amount <= _balance) {
_balance -= amount;
}
}
}
void main() {
var account = BankAccount();
account.deposit(100);
account.withdraw(50);
print("Current balance: \$${account.balance}");
// Output: Current balance: $50.0
}
Private Fields: Prefixed with
_to restrict access. In Dart, it is customary to use an underscore (_) at the beginning of a variable name to indicate that the variable is private to the library or class. This is a way of signaling that the variable is meant to be used only within the class or library and should not be accessed directly from outside.Getters and Setters: Provide controlled access to private fields.
Validation Logic: Included in methods like
depositandwithdrawfor additional security.
c) Polymorphism
Polymorphism allows objects to be treated as instances of their parent class or interface. It is achieved through method overriding and the use of abstract classes or interfaces.
Example: Polymorphism with Abstract Classes
abstract class Shape {
void draw(); // Abstract method
}
class Circle extends Shape {
@override
void draw() => print("Drawing a Circle");
}
class Rectangle extends Shape {
@override
void draw() => print("Drawing a Rectangle");
}
void main() {
List<Shape> shapes = [Circle(), Rectangle()];
for (var shape in shapes) {
shape.draw();
}
// Output:
// Drawing a Circle
// Drawing a Rectangle
}
Abstract Classes: Define a contract for child classes to implement specific methods.
Dynamic Behavior: The
drawmethod behaves differently for each subclass.
4. Other OOP Features in Dart
a) Mixins
Mixins allow a class to reuse properties and methods from multiple classes without inheritance.
Example: Using Mixins
mixin Flyable {
void fly() => print("Flying...");
}
class Bird with Flyable {}
void main() {
var bird = Bird();
bird.fly(); // Output: Flying...
}
b) Static Members
Static members belong to the class rather than any instance.
Example: Static Members
class MathUtils {
static const double pi = 3.14159;
static double square(double num) => num * num;
}
void main() {
print("Pi: ${MathUtils.pi}");
print("Square of 3: ${MathUtils.square(3)}");
}
5. Best Practices for OOP in Dart
Encapsulation: Always make fields private and use getters/setters for controlled access.
Avoid Deep Inheritance: Prefer composition over inheritance for better maintainability.
Leverage Mixins: Use mixins to share behavior without creating complex hierarchies.
Use Abstract Classes: Define shared contracts for subclasses to ensure consistent implementation.
Document Your Classes: Provide clear comments and documentation for better code readability.
Follow Dart Naming Conventions:
Use PascalCase for class names (
MyClass).Use camelCase for variable and method names (
myVariable,myMethod).