情報隠蔽
CoddyのDartジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 34/110。
情報隠蔽はカプセル化の背後にある原則です。それは、クラスが内部の仕組みを隠し、必要なものだけを公開すべきであるという考え方です。これまでにその仕組み(private fields、getters、setters)を学びましたが、本当の目標は、使いやすく、かつ誤用しにくいクラスを設計することにあります。
ユーザーのパスワードを管理するクラスを考えてみましょう。内部の保存およびハッシュ化のロジックは、完全に隠蔽されるべきです:
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;
}外部のコードがパスワードハッシュを参照したり、それがどのように保存されているかを知ることはありません。単に verifyPassword() を呼び出すだけです。この分離により、このクラスを使用するコードに影響を与えることなく、ハッシュアルゴリズムを完全に変更することができます。
重要な洞察は、クラスを外側から内側へと考えることです。
自分自身に問いかけてみてください:「このクラスを使用する人は、実際に何を必要としているのか?」それだけを公開し、それ以外はすべて private に保ちます。これにより、クラスが何をするか(パブリックインターフェース)と、それをどのように行うか(プライベートな実装)の間に明確な境界が生まれます。
チャレンジ
簡単情報の隠蔽(Information Hiding)を実際に示す、安全なウォレットシステムを構築しましょう。ユーザーの残高と取引履歴を管理するデジタルウォレットを作成します。機密性の高い内部の詳細は、外部のコードから完全に隠した状態に保ちます。
コードを2つのファイルに分けて構成します:
wallet.dart: お金を安全に管理するWalletクラスを作成します。ウォレットは所有者の名前を公開(public)して保持しますが、それ以外はすべてクリーンな公開インターフェースの背後に隠す必要があります:- 内部の残高と取引ログは非公開(private)にする必要があります。外部のコードは、お金がどのように保存または追跡されているかを知ることはできません。
- 内部で取引を記録するための非公開メソッドを用意します(
"Deposit: $50.0"や"Withdrawal: $30.0"のようなエントリを保存します)。 - 公開メソッド
deposit(double amount)を提供します。これは(金額が正の場合のみ)お金を追加し、内部で取引を記録します。 - 公開メソッド
withdraw(double amount)を提供します。これは十分な資金があり、かつ金額が正の場合のみお金を引き出し、内部で取引を記録します。成功した場合はtrueを、そうでない場合はfalseを返します。 - 現在の残高を返す公開ゲッター
balanceを提供します(読み取り専用アクセス)。 - 記録された取引の数を返す公開メソッド
getTransactionCount()を提供します(実際の取引リストは公開しません)。 - 最新の取引文字列を返す公開メソッド
getLastTransaction()を提供します。取引がない場合は"No transactions"を返します。 - ウォレットの状態を表示する
printSummary()メソッドを含めます。
main.dart: ウォレットをインポートし、情報の隠蔽がいかにクリーンで安全なインターフェースを作成するかを示します:- 所有者
"Sarah"のウォレットを作成します。 100.0を入金(deposit)します。50.0を入金(deposit)します。200.0の引き出し(withdraw)を試み、結果に基づいて"Withdrawal of 200.0: [success/failed]"と出力します。30.0を引き出し、"Withdrawal of 30.0: [success/failed]"と出力します。printSummary()を呼び出します。"Transaction count: [count]"と出力します。"Last transaction: [transaction]"と出力します。
- 所有者
printSummary() メソッドは以下のように出力する必要があります:
=== Wallet Summary ===
Owner: [ownerName]
Balance: $[balance]外部のコードが、残高がどのように保存されているか、あるいは取引が内部でどのように追跡されているかを知ることなく、明確で目的を持ったメソッドを通じてウォレットと対話できることに注目してください。実装の詳細は完全に隠されています。ウォレットを使用するコードに影響を与えることなく、取引の保存方法を変更することも可能です。
期待される出力:
Withdrawal of 200.0: failed
Withdrawal of 30.0: success
=== Wallet Summary ===
Owner: Sarah
Balance: $120.0
Transaction count: 3
Last transaction: Withdrawal: $30.0チートシート
情報隠蔽はカプセル化の背後にある原則であり、クラスの内部動作を隠し、必要なものだけを公開することです。
クラスを外側から内側へと設計しましょう。ユーザーが必要なものだけを公開し、それ以外はすべてプライベートに保ちます。これにより、公開インターフェース(クラスが何をするか)とプライベートな実装(それをどのように行うか)の間に明確な境界が生まれます。
パスワードマネージャーを使用した情報隠蔽の例:
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;
}外部のコードがパスワードのハッシュや保存メカニズムを見ることはありません。利用するのは verifyPassword() だけです。これにより、外部のコードに影響を与えることなく、内部の実装を変更できるようになります。
自分で試してみよう
import 'wallet.dart';
void main() {
// TODO: 所有者 "Sarah" のウォレットを作成する
// TODO: 100.0 を入金する
// TODO: 50.0 を入金する
// TODO: 200.0 の引き出しを試行し、結果を表示する
// 形式: "Withdrawal of 200.0: [success/failed]"
// TODO: 30.0 を引き出し、結果を表示する
// 形式: "Withdrawal of 30.0: [success/failed]"
// TODO: printSummary() を呼び出す
// TODO: 取引件数を表示する
// 形式: "Transaction count: [count]"
// TODO: 最後の取引を表示する
// 形式: "Last transaction: [transaction]"
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4Null Safety
Null Safety 入門Nullable と Non-Nullable? 演算子と ! 演算子late キーワードと Null SafetyNull-Aware 演算子クラスにおける Null Safetyまとめ:ユーザープロフィールシステム10コレクションとジェネリクス
List, Set, Map の概要型安全なコレクションジェネリッククラスジェネリックメソッドジェネリクスの制約Iterable と Iteratorまとめ:ジェネリックなストレージ