前回は、可変長のパスワード探索において「再帰呼び出し」が必要になり、コードが複雑化する問題に直面しました。
今回は、その複雑さを「オブジェクト」の中に閉じ込め、使う側からは非常にシンプルに見えるようにリファクタリングします。ここで登場するのが「Iterator(反復子)」の概念です。
目標:nextメソッドを持つオブジェクト
目指すのは、以下のように使えるクラス BruteForceIterator です。
| |
この $iterator->next を呼ぶたびに、新しいパスワード候補が1つ返ってきます。最後まで出し尽くしたら undef を返してループが終了します。
これなら、使う側は「どうやって組み合わせを作っているか(for文なのか再帰なのか)」を知る必要がありません。
Client(使う側)とIteratorの関係は以下のようになります。Clientはひたすら next を呼び続け、Iteratorは内部で計算した値を1つずつ返します。
sequenceDiagram
participant Client
participant Iterator
participant Lock
Note over Client, Iterator: 準備フェーズ
Client->>Iterator: new(length => 4)
Iterator-->>Client: インスタンス生成
Note over Client, Iterator: 反復フェーズ
loop undefが返るまで
Client->>Iterator: next()
Iterator->>Iterator: 次の値を計算 (例: "0000")
Iterator-->>Client: "0000"
Client->>Lock: unlock("0000")
Client->>Iterator: next()
Iterator->>Iterator: 次の値を計算 (例: "0001")
Iterator-->>Client: "0001"
Client->>Lock: unlock("0001")
end
Client->>Iterator: next()
Iterator-->>Client: undef (終了)
実装:MooでIteratorを作る
では、BruteForceIterator.pm を実装してみましょう。
内部的には、現在の数値を覚えておき、next が呼ばれるたびに1つ増やして返す、という単純な仕組みで実現できます(数字のパスワードならではの簡略化です)。
| |
見てください。再帰も多重ループも使っていません。単に「今の数字」を覚えておいて、呼ばれるたびにインクリメントしているだけです。
使う側のコード(メインスクリプト)
これを使う cracker_iterator.pl は以下のようになります。
| |
非常にスッキリしました!
再帰ロジックの複雑さは消え去り、直感的な while ループになりました。
Iteratorの利点:遅延評価(Lazy Evaluation)
このアプローチの最大の利点は、「全ての組み合わせをメモリに展開しなくて良い」 という点です。
もし、8桁のパスワード(1億通り)を全て配列に入れてから処理しようとすると、巨大なメモリを消費します。しかし、このIterator方式なら、メモリ上には「現在のカウント(1つの整数)」しか保持していません。
next が呼ばれた瞬間に、必要な値を1つだけ計算して(作り出して)返しています。これを遅延評価と呼びます。無限に近いリストでも、これなら扱うことができます。
次回予告
今回は数字だけのパスワードだったので、単純なカウンターで実装できました。しかし、もし「英字」が含まれていたら? 「辞書にある単語」を使いたかったら?
単純なインクリメントでは対応できません。
次回は、この next メソッドという共通インターフェースを守りながら、内部の実装を差し替えることで、「辞書攻撃」 にも対応できる柔軟なツールへと進化させます。ポリモーフィズム(多態性) の威力を体験しましょう。
