Table of Contents
Download Dart OOP Cheat Sheet PDF Free
Subscribe to our newsletter to download the PDF for free! We’ll send the PDF straight to your inbox.
1. Classes and Objects
Defining a Class
class Animal {
String name;
int age;
// Constructor
Animal(this.name, this.age);
// Method
void makeSound() {
print('$name makes a sound.');
}
}
Creating an Object
An object is an instance of a class, representing a specific entity with defined attributes and behaviors. You create an object using the new keyword or by calling the class constructor directly.
void main() {
Animal cat = Animal('Cat', 2);
cat.makeSound(); // Output: Cat makes a sound.
}
2. Inheritance
Extending a class
Inheritance allows a class (subclass) to inherit properties and methods from another class (superclass). This promotes code reuse and establishes a natural hierarchy.
class Dog extends Animal {
String breed;
Dog(String name, int age, this.breed) : super(name, age);
@override
void makeSound() {
print('$name barks.');
}
}
Using the Subclass
Create an object of the subclass to use its properties and methods, including those inherited from the superclass. The subclass can also override superclass methods.
void main() {
Dog dog = Dog('Dog', 3, 'Labrador');
dog.makeSound(); // Output: Dog barks.
}
3. Abstract Classes
Defining an Abstract Class
An abstract class cannot be instantiated and is meant to be subclassed. It can include abstract methods that must be implemented by subclasses.
abstract class Shape {
void draw();
}
Implementing an Abstract Class
Subclasses must implement the abstract methods of the abstract class. This enforces a contract for what methods the subclass should provide.
class Circle extends Shape {
@override
void draw() {
print('Drawing a circle.');
}
}
class Square extends Shape {
@override
void draw() {
print('Drawing a square.');
}
}
Using Abstract Class Implementation
Create objects of the subclasses to use the implemented methods, fulfilling the contract defined by the abstract class.
void main() {
Shape circle = Circle();
Shape square = Square();
circle.draw(); // Output: Drawing a circle.
square.draw(); // Output: Drawing a square.
}
4. Interfaces
Defining an Interface
Any class can be used as an interface by implementing its methods in another class. Interfaces define a set of methods that a class must implement.
class Flyable {
void fly() {
print('Flying');
}
}
class Bird implements Flyable {
@override
void fly() {
print('Bird is flying.');
}
}
Using the Interface
Implement the interface in a class and provide definitions for its methods. This allows different classes to share common behavior.
void main() {
Bird bird = Bird();
bird.fly(); // Output: Bird is flying.
}
5. Mixins
Defining and Using a Mixin
A mixin allows a class to inherit methods from multiple classes. This is used for code reuse in a way that is more flexible than single inheritance.
Use the mixin in a class to include its methods. Mixins can be used to add behavior to a class without affecting its inheritance hierarchy.
mixin Swimmable {
void swim() {
print('Swimming');
}
}
class Fish with Swimmable {}
void main() {
Fish fish = Fish();
fish.swim(); // Output: Swimming
}
6. Encapsulation
Private Members
Use an underscore (_
) to make a class member private. This restricts access to the member, enforcing a controlled access mechanism.
class Encapsulated {
String _privateVariable = 'This is private';
String get privateVariable => _privateVariable;
set privateVariable(String value) => _privateVariable = value;
}
Using Encapsulation
Access private members via getters and setters. This provides a way to control how values are set or retrieved from class members.
void main() {
Encapsulated obj = Encapsulated();
print(obj.privateVariable); // Output: This is private
obj.privateVariable = 'New Value';
print(obj.privateVariable); // Output: New Value
}
7. Static Members
Defining Static Members
Static members belong to the class, not instances of the class. They can be accessed using the class name and are shared among all instances.
class StaticExample {
static int counter = 0;
static void incrementCounter() {
counter++;
}
}
Using Static Members
Access static members using the class name. This is useful for values or methods that should be consistent across all instances of a class.
void main() {
print(StaticExample.counter); // Output: 0
StaticExample.incrementCounter();
print(StaticExample.counter); // Output: 1
}
8. Constructors
Default Constructor
A default constructor initializes an object when it’s created. It can be defined explicitly or provided by default if no other constructors are present.
class DefaultConstructor {
DefaultConstructor() {
print('Default constructor called.');
}
}
Named Constructor
A named constructor provides multiple ways to initialize an object. This is useful for creating different types of constructors within the same class.
class NamedConstructor {
NamedConstructor.named() {
print('Named constructor called.');
}
}
Using Constructor
void main() {
DefaultConstructor obj1 = DefaultConstructor(); // Output: Default constructor called.
NamedConstructor obj2 = NamedConstructor.named(); // Output: Named constructor called.
}
Redirecting Constructor
A redirecting constructor calls another constructor in the same class. This avoids code duplication by reusing existing constructor logic.
class RedirectingConstructor {
RedirectingConstructor() : this.named();
RedirectingConstructor.named() {
print('Redirecting to named constructor.');
}
}
void main() {
RedirectingConstructor obj = RedirectingConstructor(); // Output: Redirecting to named constructor.
}
9. Getters and Setters
Custom Getters and Setters
Define custom logic for getting and setting private variables. This provides a way to add validation or other logic when accessing or modifying variables.
class CustomGetterSetter {
String _name;
String get name => 'Name: $_name';
set name(String value) => _name = value;
}
Using Getters and Setters
void main() {
CustomGetterSetter obj = CustomGetterSetter();
obj.name = 'Alice';
print(obj.name); // Output: Name: Alice
}
10. Overriding Operators
Overloading Operators
Overload operators to perform custom operations with class objects. This allows instances of your class to use operators like (+) in a natural way.
class Vector {
int x, y;
Vector(this.x, this.y);
Vector operator +(Vector other) {
return Vector(x + other.x, y + other.y);
}
}
Using Operator Overloading
Use overloaded operators to combine objects in a meaningful way. This can make your class instances behave more like built-in types.
void main() {
Vector v1 = Vector(1, 2);
Vector v2 = Vector(3, 4);
Vector v3 = v1 + v2;
print('v3: (${v3.x}, ${v3.y})'); // Output: v3: (4, 6)
}
11. Polymorphism
Using Polymorphism
Polymorphism allows you to use a subclass object where a superclass object is expected, enabling flexible and reusable code.
class Animal {
void makeSound() {
print('Some sound');
}
}
class Dog extends Animal {
@override
void makeSound() {
print('Bark');
}
}
void main() {
Animal myDog = Dog();
myDog.makeSound(); // Output: Bark
}
12. Covariant and Contravariant
Covariant Parameters
Covariant parameters allow overriding methods to accept more specific types than the original method.
class Animal {
void feed(Covariant Animal animal) {}
}
class Dog extends Animal {}
class Caretaker {
void feed(Animal animal) {}
}
class DogCaretaker extends Caretaker {
@override
void feed(Dog dog) {}
}
13. Late Initialization
Using late Keyword
The late keyword allows you to initialize variables at a later point, ensuring they are non-null when accessed.
class LazyInit {
late String description;
void initialize() {
description = 'Initialized';
}
}
14. Singleton Pattern
Implementing Singleton Pattern
The Singleton pattern restricts a class to a single instance and provides a global point of access to it.
class Singleton {
Singleton._privateConstructor();
static final Singleton instance = Singleton._privateConstructor();
}
15. Type Aliases
Using Type Aliases
Type aliases allow you to create a new name for an existing type, improving code readability.
typedef IntList = List;
IntList numbers = [1, 2, 3]; // This is equivalent to List numbers
16. Callable Classes
Creating Callable Classes
A class with a call method can be used like a function, enabling a more functional programming style.
class Adder {
int call(int a, int b) => a + b;
}
void main() {
var add = Adder();
print(add(2, 3)); // Output: 5
}
17. Built-in Mixins
Using Built-in Mixins
Dart provides built-in mixins like Comparable and Iterable to enhance class functionality.
class Person with Comparable {
String name;
int age;
Person(this.name, this.age);
@override
int compareTo(Person other) => age - other.age;
}
18. Deep Copy
Implementing Deep Copy
Deep copy creates a new instance of an object with the same values, avoiding references to the original object.
class Person {
String name;
int age;
Person(this.name, this.age);
Person copy() => Person(name, age);
}
19. NoSuchMethod
Overriding noSuchMethod
Override noSuchMethod to handle method calls that don’t exist, enabling dynamic behavior in your classes.
class DynamicClass {
@override
dynamic noSuchMethod(Invocation invocation) => 'Method not found';
}
20. Static Initialization
Static Initialization Block
Use a static initialization block to execute code when the class is first loaded.
class Configuration {
static final Configuration instance = Configuration._privateConstructor();
Configuration._privateConstructor();
static void _initialize() {
print('Class loaded');
}
}
21. Custom Exceptions
Defining Custom Exceptions
Create custom exception classes to handle specific error conditions in your code.
class InvalidInputException implements Exception {
final String message;
InvalidInputException(this.message);
@override
String toString() => 'InvalidInputException: $message';
}
22. Final and Const
Using final and const
final and const are used for variables whose values won’t change, with const being compile-time constant.
class Constants {
final String name = 'John';
static const int age = 30;
}
23. Interfaces and Abstract Classes
Combining Interfaces and Abstract Classes
Use both interfaces and abstract classes for a more flexible design.
abstract class Drawable {
void draw();
}
class Circle implements Drawable {
@override
void draw() {
print('Drawing circle');
}
}
24. Method Cascades
Using Method Cascades
Method cascades allow you to chain multiple method calls on the same object.
class Builder {
String content = '';
Builder add(String part) {
content += part;
return this;
}
}
void main() {
var builder = Builder()
..add('Hello ')
..add('World');
print(builder.content); // Output: Hello World
}
25. Generics with Constraints
Using Generics with Constraints
Generics can be constrained to specific types using the extends keyword.
class Repository {
void save(T entity) {
print('Saving $entity');
}
}
26. Utility Methods
Adding Utility Methods
Add utility methods to your classes for common operations, improving code organization.
class MathUtils {
static int add(int a, int b) => a + b;
static int multiply(int a, int b) => a * b;
}
27. Fluent Interfaces
Implementing Fluent Interfaces
Fluent interfaces return this to allow method chaining.
class QueryBuilder {
String query = '';
QueryBuilder select(String field) {
query += 'SELECT $field ';
return this;
}
QueryBuilder from(String table) {
query += 'FROM $table ';
return this;
}
}
28. Immutable Classes
Creating Immutable Classes
Make classes immutable to prevent modification after creation.
class ImmutablePoint {
final int x, y;
const ImmutablePoint(this.x, this.y);
}
29. Proxy Pattern
Implementing Proxy Pattern
The Proxy pattern provides a surrogate or placeholder for another object.
class RealSubject {
void request() {
print('Real request');
}
}
class Proxy {
final RealSubject _realSubject = RealSubject();
void request() {
print('Proxy request');
_realSubject.request();
}
}
30. Composite Pattern
Implementing Composite Pattern
The Composite pattern allows you to compose objects into tree structures to represent part-whole hierarchies.
abstract class Component {
void operation();
}
class Leaf extends Component {
@override
void operation() {
print('Leaf operation');
}
}
class Composite extends Component {
List children = [];
@override
void operation() {
for (var child in children) {
child.operation();
}
}
}