ダックタイピング
CoddyのLuaジャーニー「Object Oriented Programming」セクションの一部 — レッスン 39/70。
Luaはオブジェクトのクラスを気にしません。オブジェクトが何ができるかだけを気にします。この概念はダックタイピングと呼ばれます。「もしそれがアヒルのように歩き、アヒルのように鳴くなら、それはアヒルである」というものです。プログラミングの用語で言えば、呼び出そうとしているメソッドをオブジェクトが持っていれば、それは動作します。
これは、同じメソッド名を共有している限り、全く無関係なクラスを組み合わせて使用できることを意味します。
local Circle = {}
Circle.__index = Circle
function Circle:new()
local obj = {}
setmetatable(obj, Circle)
return obj
end
function Circle:draw()
print("Drawing a circle")
end
local Square = {}
Square.__index = Square
function Square:new()
local obj = {}
setmetatable(obj, Square)
return obj
end
function Square:draw()
print("Drawing a square")
endこれらのクラスには継承関係はなく、完全に独立しています。それでも、これらを同様に扱うことができます:
local shapes = {Circle:new(), Square:new(), Circle:new()}
for _, shape in ipairs(shapes) do
shape:draw()
end
-- 出力:
-- 円を描画しています
-- 正方形を描画しています
-- 円を描画していますループは各オブジェクトがどの型であるかを確認しません。単に :draw() を呼び出し、そのメソッドが存在することを前提として動作します。この柔軟性は非常に強力です。ループのコードを一切変更することなく、新しい図形の型を追加できます。新しいクラスが :draw() を実装している限り、問題なく動作します。
チャレンジ
簡単全く無関係なクラスをすべて同じ方法で処理できる通知システムを構築することで、ダックタイピングの力を実証しましょう!
3つの独立した通知クラスを作成します。これらはお互いに継承関係はありませんが、すべて :send() メソッドを実装しています。つまり、実際の型を気にすることなく、混在したコレクションをループして、それぞれに対して :send() を呼び出すことができます。
コードは4つのファイルに分けて構成します:
Email.lua: メールの宛先を保存する:new(recipient)コンストラクタを持つクラス。その:send()メソッドはEmailing {recipient}と出力する必要があります。SMS.lua::new(phoneNumber)コンストラクタを持つ、完全に独立したクラス。その:send()メソッドはTexting {phoneNumber}と出力する必要があります。PushNotification.lua::new(deviceId)コンストラクタを持つ、もう一つの独立したクラス。その:send()メソッドはPushing to {deviceId}と出力する必要があります。main.lua: すべてをまとめます!3つの入力を読み取り、各通知タイプのインスタンスを1つずつ作成し、それらすべてを1つのテーブルに入れ、ループしてそれぞれに対して:send()を呼び出します。
3つの入力を受け取ります:
- メールの受信者 (例:
alice@example.com) - 電話番号 (例:
555-1234) - デバイスID (例:
device_99)
メインファイルで、3つの通知オブジェクトすべてを作成し、それらをテーブルに格納し、テーブルを反復処理してそれぞれに対して :send() を呼び出します。順序は、最初に Email、次に SMS、最後に PushNotification としてください。
例えば、入力が bob@mail.com、555-9876、phone_42 の場合、出力は以下のようになります:
Emailing bob@mail.com
Texting 555-9876
Pushing to phone_42ループの中で各オブジェクトの型を確認する必要がないことに注目してください。コレクション内のすべてのオブジェクトが :send() メソッドを持っていることを信頼しているだけです。これがダックタイピングの実践です!もし :send() ができるなら、それは通知なのです。
チートシート
Luaはダックタイピングを使用します。オブジェクトが呼び出しているメソッドを持っているなら、そのクラスに関係なく動作します。その原則は、「もしそれがアヒルのように歩き、アヒルのように鳴くなら、それはアヒルである」というものです。
全く無関係なクラスであっても、同じメソッド名を共有している限り、互換性を持って使用することができます。
local Circle = {}
Circle.__index = Circle
function Circle:new()
local obj = {}
setmetatable(obj, Circle)
return obj
end
function Circle:draw()
print("Drawing a circle")
end
local Square = {}
Square.__index = Square
function Square:new()
local obj = {}
setmetatable(obj, Square)
return obj
end
function Square:draw()
print("Drawing a square")
endこれらのクラスには継承関係はありませんが、同一に扱うことができます。
local shapes = {Circle:new(), Square:new(), Circle:new()}
for _, shape in ipairs(shapes) do
shape:draw()
end
-- 出力:
-- Drawing a circle
-- Drawing a square
-- Drawing a circleループはオブジェクトの型をチェックしません。単にメソッドを呼び出し、それが存在することを信頼します。必要なメソッドを実装している限り、既存のコードを修正することなく新しい型を追加できます。
自分で試してみよう
-- main.lua
-- すべての通知タイプをまとめ、ダックタイピングを実演します
-- 通知クラスをインポートします
local Email = require('Email')
local SMS = require('SMS')
local PushNotification = require('PushNotification')
-- 入力を読み取ります
local recipient = io.read()
local phoneNumber = io.read()
local deviceId = io.read()
-- TODO: 各通知タイプのインスタンスを1つずつ作成します
-- TODO: すべての通知を1つのテーブルに格納します (Email、SMS、PushNotificationの順)
-- TODO: テーブルをループで回し、各通知に対して :send() を呼び出します
-- これはダックタイピングの実演です。型を確認せず、単に :send() を呼び出します
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。