Home
Dart
OOP
Flutter
Contact

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:

bank_account.dart
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;
}
ℹ️ Note Dart's privacy is file-based, not class-based. Private members are accessible within the same file, not from other files.

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:

computed_getters.dart
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
📐 Real-World Pattern: Validation Chains Setters with validation are the foundation of form validation in Dart. Each setter enforces its own rule, and if any setter throws, you know exactly which field is invalid.

Custom Getters

Getters provide read-only computed properties without parentheses:

temperature.dart
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:

validated_setter.dart
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

digital_wallet.dart
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
🚀 Next Step Continue to Inheritance & Method Overriding.
K
Kesavaraja Murugesan
Flutter Developer & Educator

Flutter developer with 3 years of experience building production-grade mobile applications. Passionate about teaching clean architecture patterns and helping developers write maintainable, scalable code.

← Classes & Objects Next: Inheritance →