OOP: Encapsulation & Setters/Getters
Protect your data with private variables, custom getters, and validated setters.
Private Variables
Encapsulation is the practice of hiding internal state and requiring all interaction through a controlled public interface. In Dart, prefix a variable name with _ to make it private to its library (file). This is Dart's mechanism for encapsulation — it prevents external code from directly modifying an object's internal data, which helps maintain consistency and prevents bugs.
Without encapsulation, any code could set a bank account's balance to a negative number or bypass withdrawal limits. With private fields and controlled setters, the object itself enforces its own invariants. Here's how it works:
class BankAccount {
String _accountHolder;
double _balance;
List<double> _transactionHistory = [];
// Private fields — cannot be accessed from outside this file
// _balance = -1000; // ❌ Would fail from another file
BankAccount(this._accountHolder, this._balance);
String get accountHolder => _accountHolder;
double get balance => _balance;
}
Custom Getters
Getters provide read-only access to an object's state. They let you expose computed values without allowing external code to modify them directly. This is essential for maintaining data integrity — you control what information is visible and how it's calculated.
Getters use the get keyword and are accessed like properties (without parentheses), making your API feel natural and clean.
Computed Properties
Getters can compute values on the fly. This is especially useful for derived data that shouldn't be stored separately:
class Product {
String name;
double price;
double discountPercent;
Product({required this.name, required this.price, this.discountPercent = 0});
// Computed property — no storage needed
double get finalPrice => price * (1 - discountPercent / 100);
// Computed boolean
bool get isOnSale => discountPercent > 0;
// Computed display string
String get displayPrice => '\$${finalPrice.toStringAsFixed(2)}';
}
var product = Product(name: 'Widget', price: 29.99, discountPercent: 20);
print(product.isOnSale); // true
print(product.displayPrice); // $23.99
Custom Getters
Getters provide read-only computed properties without parentheses:
class Temperature {
double _celsius;
Temperature(this._celsius);
// Getter: computed Fahrenheit
double get fahrenheit => _celsius * 9 / 5 + 32;
// Getter: formatted string
String get display => '${_celsius.toStringAsFixed(1)}°C / ${fahrenheit.toStringAsFixed(1)}°F';
// Getter: classification
String get comfortLevel {
if (_celsius < 10) return 'Cold ❄️';
if (_celsius < 25) return 'Comfortable 😊';
return 'Hot 🔥';
}
}
Setters with Validation
Setters give you control over how data enters your object. Instead of allowing direct assignment, a setter can validate input, enforce business rules, trigger side effects, or transform data before storing it. This is one of the most practical benefits of encapsulation — your object becomes self-protecting.
Here's a setter that enforces daily spending limits on a digital wallet:
class Wallet {
double _balance = 0;
double _dailyLimit = 1000;
double _todaySpent = 0;
double get balance => _balance;
set dailyLimit(double limit) {
if (limit <= 0) {
throw ArgumentError('Daily limit must be positive');
}
_dailyLimit = limit;
}
void deposit(double amount) {
if (amount <= 0) throw ArgumentError('Deposit must be positive');
_balance += amount;
}
bool withdraw(double amount) {
if (amount <= 0) return false;
if (amount > _balance) return false;
if (_todaySpent + amount > _dailyLimit) return false;
_balance -= amount;
_todaySpent += amount;
return true;
}
}
Real-World Example: Digital Wallet
void main() {
var wallet = Wallet();
wallet.deposit(500);
print('Balance: \$${wallet.balance}'); // 500.0
wallet.withdraw(200);
print('Balance: \$${wallet.balance}'); // 300.0
var success = wallet.withdraw(2000);
print('Over-limit withdrawal: $success'); // false
}
Summary
- Prefix with
_for file-private members - Getters provide computed read-only properties
- Setters enable validation logic on property assignment
- Encapsulation ensures objects manage their own state safely