委譲
CoddyのLuaジャーニー「Object Oriented Programming」セクションの一部 — レッスン 50/70。
オブジェクトを別のオブジェクトの中に格納することは、最初の一歩に過ぎません。コンポジションの真の力は、委譲(delegation)、つまり外側のオブジェクトがメソッド呼び出しを内部のコンポーネントに転送することから生まれます。
車のエンジンをかけるときのことを考えてみてください。ボンネットの中に手を入れて、手動でエンジンを回転させることはありません。その代わりに、キーを回せば、あとは車が内部で処理してくれます。
コードでは、これは Car クラスがシンプルな :start() メソッドを提供し、実際の作業をエンジンに委譲することを意味します:
function Car:start()
print("Turning the key...")
self.engine:run() -- 内部オブジェクトに委譲する
end車はエンジンがどのように動くかを知る必要はありません。単にエンジンにその仕事をこなすよう指示するだけです。これにより、責任が分離された状態が保たれます。Engineクラスはエンジンのロジックを処理し、Carは各パーツを調整します:
function Engine:run()
print("Engine running at " .. self.horsepower .. " HP")
end
local engine = Engine:new(250)
local car = Car:new("Coupe", engine)
car:start()
-- 出力:
-- キーを回しています...
-- Engine running at 250 HPデリゲーション(委譲)は、クラスのユーザーに対してクリーンなインターフェースを提供します。ユーザーは内部のパーツではなく、車(car)を操作します。後で別のエンジン(engine)の実装に入れ替えたとしても、車の公開インターフェースは変わらず、内部のデリゲーションだけが変更されます。
チャレンジ
簡単デリゲーション(委譲)を実演する音楽プレーヤーシステムを構築しましょう!Speakerコンポーネントと、それを含むMusicPlayerを作成します。プレーヤーは音声出力を直接処理するのではなく、その責任を内部のスピーカーに委譲します。
コードは3つのファイルに分けて構成します:
Speaker.lua: 音量を保存するコンストラクタ:new(volume)を持つ Speaker クラスを作成します。Playing "{trackName}" at volume {volume}と出力する:playSound(trackName)メソッドを含めてください。MusicPlayer.lua: ブランド名と Speaker オブジェクトを受け取るコンストラクタ:new(brand, speaker)を持つ MusicPlayer クラスを作成します。プレーヤーはスピーカーを内部に保持します。以下のメソッドを含めてください::play(trackName)—{brand} player starting...と出力し、その後スピーカーの:playSound()メソッドに委譲します。:stop()—{brand} player stoppedと出力します。
main.lua: すべてをまとめます!スピーカーの音量、プレーヤーのブランド、トラック名の入力を読み取ります。まず Speaker を作成し、それを MusicPlayer のコンストラクタに渡します。トラック名を指定してプレーヤーの:play()メソッドを呼び出し、次に:stop()を呼び出します。
3つの入力を受け取ります:
- スピーカーの音量(数値)
- 音楽プレーヤーのブランド(文字列)
- 再生するトラック名(文字列)
出力は、プレーヤーがスピーカーと連携している様子を示す必要があります:
{brand} player starting...
Playing "{trackName}" at volume {volume}
{brand} player stopped例えば、入力が 75、SoundMax、Summer Vibes の場合、出力は以下のようになります:
SoundMax player starting...
Playing "Summer Vibes" at volume 75
SoundMax player stoppedMusicPlayer がどのように音を出すかを知らず、単にスピーカーにその部分を処理するように指示していることに注目してください。これがデリゲーションの実際です。外部オブジェクトがクリーンなインターフェースを提供し、内部コンポーネントが専門的な作業を行います!
チートシート
デリゲーション(委譲)により、外部オブジェクトはメソッド呼び出しを内部コンポーネントに転送できるようになり、責任を分離し、クリーンなインターフェースを作成できます。
外部オブジェクトは、内部オブジェクトに作業を委譲するシンプルなメソッドを提供します。
function Car:start()
print("Turning the key...")
self.engine:run() -- 内部オブジェクトに委譲する
end内部オブジェクトは、自身の特化したロジックを処理します。
function Engine:run()
print("Engine running at " .. self.horsepower .. " HP")
end
local engine = Engine:new(250)
local car = Car:new("Coupe", engine)
car:start()
-- 出力:
-- Turning the key...
-- Engine running at 250 HPデリゲーションの利点:
- 外部クラスは、内部オブジェクトがどのように動作するかを知る必要がありません
- ユーザーはクリーンなパブリックインターフェースを介して操作します
- パブリックインターフェースを変更することなく、内部の実装を入れ替えることができます
自分で試してみよう
-- main.lua
-- すべてをまとめます!
local Speaker = require('Speaker')
local MusicPlayer = require('MusicPlayer')
-- 入力を読み込む
local volume = tonumber(io.read())
local brand = io.read()
local trackName = io.read()
-- TODO: 指定された音量でSpeakerを作成する
-- TODO: ブランドとスピーカーを指定してMusicPlayerを作成する
-- TODO: トラック名を使用してプレイヤーのplayメソッドを呼び出す
-- TODO: プレイヤーのstopメソッドを呼び出す
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。