Methods
Understand how to define and use methods in Dart classes.
What is it?
Methods are functions that belong to a class and define the behavior of objects. They can access and modify the object's state (fields) and perform operations. Methods are essential for implementing the functionality of classes in object-oriented programming.
Why does it exist?
Methods exist to:
- Define object behavior
- Encapsulate operations on data
- Provide interfaces for interacting with objects
- Implement business logic
- Organize code into reusable units
- Enable polymorphism
Instance Methods
Basic Methods
class Calculator {
// Instance methods
int add(int a, int b) => a + b;
int subtract(int a, int b) => a - b;
int multiply(int a, int b) => a * b;
double divide(int a, int b) {
if (b == 0) {
throw ArgumentError('Cannot divide by zero');
}
return a / b;
}
// Method with side effects
void printResult(int a, int b, String operation) {
var result = switch (operation) {
'add' => add(a, b),
'subtract' => subtract(a, b),
'multiply' => multiply(a, b),
_ => throw ArgumentError('Unknown operation'),
};
print('$a $operation $b = $result');
}
}
// Usage
var calc = Calculator();
print(calc.add(5, 3)); // 8
calc.printResult(5, 3, 'multiply'); // 5 multiply 3 = 15
Methods Accessing Fields
class Person {
String name;
int age;
List<String> hobbies;
Person(this.name, this.age, this.hobbies);
// Method accessing instance fields
String get introduction {
return 'Hi, I am $name, $age years old';
}
void addHobby(String hobby) {
hobbies.add(hobby);
}
bool hasHobby(String hobby) {
return hobbies.contains(hobby);
}
void celebrateBirthday() {
age++;
print('Happy birthday $name! You are now $age');
}
String getHobbiesList() {
return hobbies.isEmpty
? 'No hobbies yet'
: 'Hobbies: ${hobbies.join(', ')}';
}
}
// Usage
var person = Person('Alice', 25, ['reading', 'swimming']);
print(person.introduction); // Hi, I am Alice, 25 years old
person.addHobby('coding');
print(person.getHobbiesList()); // Hobbies: reading, swimming, coding
person.celebrateBirthday(); // Happy birthday Alice! You are now 26
Static Methods
Utility Methods
class MathUtils {
// Static methods (can be called without instance)
static int add(int a, int b) => a + b;
static int subtract(int a, int b) => a - b;
static int multiply(int a, int b) => a * b;
static double divide(int a, int b) => a / b;
static int sum(List<int> numbers) {
return numbers.reduce((a, b) => a + b);
}
static double average(List<int> numbers) {
if (numbers.isEmpty) return 0;
return sum(numbers) / numbers.length;
}
static int max(List<int> numbers) {
if (numbers.isEmpty) {
throw ArgumentError('List cannot be empty');
}
return numbers.reduce((a, b) => a > b ? a : b);
}
static int min(List<int> numbers) {
if (numbers.isEmpty) {
throw ArgumentError('List cannot be empty');
}
return numbers.reduce((a, b) => a < b ? a : b);
}
static int factorial(int n) {
if (n < 0) throw ArgumentError('Cannot factorial negative');
return n <= 1 ? 1 : n * factorial(n - 1);
}
}
// Usage (no instance needed)
var numbers = [1, 2, 3, 4, 5];
print(MathUtils.sum(numbers)); // 15
print(MathUtils.average(numbers)); // 3.0
print(MathUtils.max(numbers)); // 5
print(MathUtils.factorial(5)); // 120
Private Methods
Internal Helper Methods
class UserValidator {
// Public methods
bool validateUser(String email, String password) {
if (!_validateEmail(email)) {
return false;
}
if (!_validatePassword(password)) {
return false;
}
return true;
}
// Private methods (starts with _)
bool _validateEmail(String email) {
return email.contains('@') && email.contains('.');
}
bool _validatePassword(String password) {
if (password.length < 8) {
return false;
}
if (!_hasUpperCase(password)) {
return false;
}
if (!_hasLowerCase(password)) {
return false;
}
if (!_hasDigit(password)) {
return false;
}
return true;
}
bool _hasUpperCase(String password) {
return RegExp(r'[A-Z]').hasMatch(password);
}
bool _hasLowerCase(String password) {
return RegExp(r'[a-z]').hasMatch(password);
}
bool _hasDigit(String password) {
return RegExp(r'\d').hasMatch(password);
}
}
// Usage
var validator = UserValidator();
print(validator.validateUser('user@example.com', 'Password123')); // true
print(validator.validateUser('invalid', 'short')); // false
// Can't access private methods
// validator._validateEmail('test'); // Error: Private method
Method Overriding
Inherited Methods
class Animal {
String name;
Animal(this.name);
// Method to override
void speak() {
print('$name makes a sound');
}
String get type => 'Animal';
void move() {
print('$name moves');
}
}
class Dog extends Animal {
String breed;
Dog(String name, this.breed) : super(name);
// Override method
@override
void speak() {
print('$name barks');
}
@override
String get type => 'Dog';
// Additional method
void fetch() {
print('$name fetches the ball');
}
// Call parent method
void introduce() {
super.move(); // Calls parent method
print('I am a $breed dog named $name');
}
}
class Cat extends Animal {
Cat(String name) : super(name);
@override
void speak() {
print('$name meows');
}
@override
String get type => 'Cat';
void purr() {
print('$name purrs');
}
}
// Usage
var dog = Dog('Rex', 'German Shepherd');
dog.speak(); // Rex barks
dog.fetch(); // Rex fetches the ball
print(dog.type); // Dog
dog.introduce(); // Rex moves \n I am a German Shepherd dog named Rex
var cat = Cat('Whiskers');
cat.speak(); // Whiskers meows
cat.purr(); // Whiskers purrs
Polymorphic Methods
Using Methods Polymorphically
abstract class Shape {
// Abstract method (must be overridden)
double get area;
double get perimeter;
// Concrete method
String get description => 'Shape with area $area and perimeter $perimeter';
// Overridable method
void scale(double factor);
}
class Circle extends Shape {
double radius;
Circle(this.radius);
@override
double get area => math.pi * radius * radius;
@override
double get perimeter => 2 * math.pi * radius;
@override
void scale(double factor) {
radius *= factor;
}
@override
String get description => 'Circle with radius $radius';
}
class Rectangle extends Shape {
double width;
double height;
Rectangle(this.width, this.height);
@override
double get area => width * height;
@override
double get perimeter => 2 * (width + height);
@override
void scale(double factor) {
width *= factor;
height *= factor;
}
@override
String get description => 'Rectangle $width x $height';
}
// Polymorphic usage
void processShapes(List<Shape> shapes) {
for (var shape in shapes) {
print(shape.description);
shape.scale(2);
print('After scaling: ${shape.description}');
print('---');
}
}
// Usage
var shapes = [
Circle(5),
Rectangle(10, 20),
];
processShapes(shapes);
// Circle with radius 5
// After scaling: Circle with radius 10
// Rectangle 10 x 20
// After scaling: Rectangle 20 x 40
Method Chaining
Returning this
class Builder {
String _name = '';
int _age = 0;
String _email = '';
bool _isActive = false;
// Each method returns this for chaining
Builder setName(String name) {
_name = name;
return this;
}
Builder setAge(int age) {
_age = age;
return this;
}
Builder setEmail(String email) {
_email = email;
return this;
}
Builder setActive(bool active) {
_isActive = active;
return this;
}
// Build method
User build() {
return User(
name: _name,
age: _age,
email: _email,
isActive: _isActive,
);
}
}
class User {
final String name;
final int age;
final String email;
final bool isActive;
User({
required this.name,
required this.age,
required this.email,
required this.isActive,
});
@override
String toString() => 'User($name, $age, $email, $isActive)';
}
// Usage - Method chaining
var user = Builder()
.setName('Alice')
.setAge(25)
.setEmail('alice@example.com')
.setActive(true)
.build();
print(user); // User(Alice, 25, alice@example.com, true)
Best Practices
Keep Methods Focused
// Good: Single responsibility
class UserService {
void validateUser(User user) { /* ... */ }
void saveUser(User user) { /* ... */ }
void sendWelcomeEmail(User user) { /* ... */ }
}
// Bad: Too many responsibilities
class BadUserService {
void processUser(User user) {
validateUser(user);
saveUser(user);
sendWelcomeEmail(user);
updateAnalytics(user);
}
// All methods in one class
}
Use Descriptive Names
// Good: Descriptive names
class Calculator {
int add(int a, int b) => a + b;
int subtract(int a, int b) => a - b;
double divide(int a, int b) => a / b;
}
// Bad: Vague names
class BadCalc {
int calc1(int a, int b) => a + b;
int calc2(int a, int b) => a - b;
}
Use @override Annotation
// Good: Using @override
class Dog extends Animal {
@override
void speak() {
print('Woof!');
}
}
// Bad: Missing @override
class Cat extends Animal {
void speak() {
print('Meow!');
}
}
Common Mistakes
Static Method Accessing Instance
Wrong:
class Person {
String name;
static void setName(String name) {
// this.name = name; // Error: Can't use this in static method
}
}
Correct:
class Person {
String name;
void setName(String name) {
this.name = name; // Instance method
}
}
Method Name Conflicts
Wrong:
class Animal {
void speak() {}
}
class Dog extends Animal {
void speak() {} // Missing @override
}
Correct:
class Animal {
void speak() {}
}
class Dog extends Animal {
@override
void speak() {}
}
Summary
Methods define the behavior of objects. Understanding instance methods, static methods, private methods, method overriding, and polymorphic usage is essential for effective object-oriented programming.
Next Steps
Now that you understand methods, continue to:
Did You Know?
- Methods are functions that belong to a class
- Static methods can be called without an instance
- Private methods start with
_ - @override annotates overridden methods
- Methods can return
thisfor chaining - Abstract methods have no implementation
- Method overloading is not supported in Dart (use different names)