Information Hiding
Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 34 of 110.
Information hiding is the principle behind encapsulation - the idea that a class should hide its internal workings and only expose what's necessary. You've learned the mechanics (private fields, getters, setters), but the real goal is designing classes that are easy to use and hard to misuse.
Consider a class that manages a user's password. The internal storage and hashing logic should be completely hidden:
class UserAccount {
String _username;
String _passwordHash;
UserAccount(this._username, String password)
: _passwordHash = _hashPassword(password);
static String _hashPassword(String password) {
return 'hashed_$password'; // Simplified example
}
bool verifyPassword(String password) {
return _passwordHash == _hashPassword(password);
}
String get username => _username;
}External code never sees the password hash or knows how it's stored. They simply call verifyPassword(). This separation means you could completely change the hashing algorithm without affecting any code that uses this class.
The key insight is to think about your class from the outside in.
Ask yourself: "What does someone using this class actually need?" Expose only that, and keep everything else private. This creates a clear boundary between what your class does (its public interface) and how it does it (its private implementation).
Challenge
EasyLet's build a secure wallet system that demonstrates information hiding in action. You'll create a digital wallet that manages a user's balance and transaction history, keeping all the sensitive internal details completely hidden from external code.
You'll organize your code into two files:
wallet.dart: Create aWalletclass that securely manages money. The wallet should store the owner's name publicly, but everything else should be hidden behind a clean public interface:- The internal balance and transaction log should be private - external code should never see how you store or track money
- A private method should handle recording transactions internally (storing entries like
"Deposit: $50.0"or"Withdrawal: $30.0") - Provide a public
deposit(double amount)method that adds money (only if the amount is positive) and records the transaction internally - Provide a public
withdraw(double amount)method that removes money only if there are sufficient funds and the amount is positive, recording the transaction internally. Returntrueif successful,falseotherwise - Provide a public getter
balancethat returns the current balance (read-only access) - Provide a public method
getTransactionCount()that returns how many transactions have been recorded (without exposing the actual transaction list) - Provide a public method
getLastTransaction()that returns the most recent transaction string, or"No transactions"if none exist - Include a
printSummary()method that displays the wallet status
main.dart: Import your wallet and demonstrate how information hiding creates a clean, safe interface:- Create a wallet for owner
"Sarah" - Deposit
100.0 - Deposit
50.0 - Attempt to withdraw
200.0and print"Withdrawal of 200.0: [success/failed]"based on the result - Withdraw
30.0and print"Withdrawal of 30.0: [success/failed]" - Call
printSummary() - Print
"Transaction count: [count]" - Print
"Last transaction: [transaction]"
- Create a wallet for owner
The printSummary() method should print:
=== Wallet Summary ===
Owner: [ownerName]
Balance: $[balance]Notice how external code can interact with the wallet through clear, purposeful methods without ever knowing how the balance is stored or how transactions are tracked internally. The implementation details are completely hidden - you could change how transactions are stored without affecting any code that uses the wallet.
Expected output:
Withdrawal of 200.0: failed
Withdrawal of 30.0: success
=== Wallet Summary ===
Owner: Sarah
Balance: $120.0
Transaction count: 3
Last transaction: Withdrawal: $30.0Cheat sheet
Information hiding is the principle behind encapsulation - hiding a class's internal workings and exposing only what's necessary.
Design classes from the outside in: expose only what users need, keep everything else private. This creates a clear boundary between the public interface (what the class does) and private implementation (how it does it).
Example of information hiding with a password manager:
class UserAccount {
String _username;
String _passwordHash;
UserAccount(this._username, String password)
: _passwordHash = _hashPassword(password);
static String _hashPassword(String password) {
return 'hashed_$password';
}
bool verifyPassword(String password) {
return _passwordHash == _hashPassword(password);
}
String get username => _username;
}External code never sees the password hash or storage mechanism - they only use verifyPassword(). This allows changing the internal implementation without affecting external code.
Try it yourself
import 'wallet.dart';
void main() {
// TODO: Create a wallet for owner "Sarah"
// TODO: Deposit 100.0
// TODO: Deposit 50.0
// TODO: Attempt to withdraw 200.0 and print result
// Format: "Withdrawal of 200.0: [success/failed]"
// TODO: Withdraw 30.0 and print result
// Format: "Withdrawal of 30.0: [success/failed]"
// TODO: Call printSummary()
// TODO: Print transaction count
// Format: "Transaction count: [count]"
// TODO: Print last transaction
// Format: "Last transaction: [transaction]"
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesLibraries & ImportsIntroduction to OOPClasses vs ObjectsThe this KeywordMethodsInstance VariablesConstructor BasicsRecap - Simple Calculator4Null Safety
Intro to Null SafetyNullable vs Non-NullableThe ? and ! OperatorsLate Keyword & Null SafetyNull-Aware OperatorsNull Safety in ClassesRecap - User Profile System7Abstract Classes & Interfaces
Abstract ClassesAbstract MethodsInterfaces in DartImplicit InterfacesImplementing vs ExtendingMultiple InterfacesRecap - Shape Calculator10Collections & Generics
List, Set, Map OverviewType-Safe CollectionsGeneric ClassesGeneric MethodsGeneric ConstraintsIterable & IteratorRecap - Generic Storage13Advanced OOP Concepts
Composition vs InheritanceExtension MethodsCallable ClassesSealed Classes (Dart 3)Records (Dart 3)Patterns & Matching (3.0)Enums with Methods16Project: Library Management
Project OverviewBook and User Classes2Constructors in Dart
Default ConstructorNamed ConstructorsInitializer ListsConstant ConstructorsFactory ConstructorsRedirecting ConstructorsRecap - Shape Builder5Encapsulation
Public vs Private MembersThe _ Prefix ConventionLibrary-Level PrivacyGetters & Setters DepthInformation HidingRecap - Student Records8Mixins
Introduction to MixinsCreating MixinsUsing Multiple Mixinson Keyword in MixinsMixin vs InheritanceMixin vs InterfaceRecap - Animal System11Special Methods
toString() OverridehashCode & == OverrideComparable Interfacecall() MethodnoSuchMethod OverrideRecap - Custom Collection14Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy Pattern3Class Properties
Instance vs Static MembersFinal & Const FieldsLate VariablesStatic Methods & FieldsGetters and SettersRecap - Bank Account Manager6Inheritance
Basic InheritanceThe super KeywordMethod OverridingThe @override AnnotationThe final Class KeywordConstructors & InheritanceRecap - Employee Hierarchy9Polymorphism
Polymorphism BasicsPolymorphism via InterfacesRuntime Type CheckingThe is & as OperatorsCovariant KeywordRecap - Payment Processor