共通インターフェース
CoddyのLuaジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 40/70。
ダックタイピングによって、無関係なクラスでも同じメソッド名を共有していれば連携できることがわかりました。この考え方は、強力な設計概念である共通インターフェースにつながります。複数のクラスが同じメソッドシグネチャを持つよう意図的に設計すると、互換性のあるコンポーネントを作成できます。
ロギングシステムを構築していると想像してください。メッセージをさまざまな送信先(ファイル、画面、あるいはネットワーク)に送信したい場合があります。送信先ごとに別々のコードを書く代わりに、すべての output クラスが同じ :write() method を実装するように設計します。
local LogFile = {}
LogFile.__index = LogFile
function LogFile:new(filename)
local obj = {filename = filename}
setmetatable(obj, LogFile)
return obj
end
function LogFile:write(message)
print("[FILE:" .. self.filename .. "] " .. message)
end
local Screen = {}
Screen.__index = Screen
function Screen:new()
local obj = {}
setmetatable(obj, Screen)
return obj
end
function Screen:write(message)
print("[SCREEN] " .. message)
endこれで、メッセージをoutputする必要があるコードは、どちらの型でも受け取れるようになります。
local function logMessage(output, msg)
output:write(msg)
end
local file = LogFile:new("app.log")
local screen = Screen:new()
logMessage(file, "Starting up") -- [FILE:app.log] 起動しています
logMessage(screen, "Starting up") -- [SCREEN] 起動していますlogMessage関数は、どのclassを受け取るかを知る必要も気にする必要もありません。:write()メソッドだけを期待します。これにより、コードが柔軟になります。既存の関数を変更せずに、後から新しい出力タイプを追加できます。
チャレンジ
簡単異なる payment methods を相互に置き換えて使用できる payment processing system を構築しましょう!共通のインターフェースである :process(amount) method を共有する、互いにまったく関係のない2つの payment classes を作成します。これにより、payment を処理するコードは、違いを認識することなくどちらの種類でも動作できます。
コードを3つのファイルに整理します。
CreditCard.lua:card number を保存する:new(cardNumber)constructor を持つ payment class です。その:process(amount)method はCharging {amount} to card {cardNumber}を print する必要があります。BankTransfer.lua:完全に独立した payment class で、:new(accountId)constructor を持ちます。その:process(amount)method はTransferring {amount} from account {accountId}を print する必要があります。main.lua:任意の payment object と amount を受け取り、そのオブジェクトで:process(amount)を呼び出す、handlePayment(paymentMethod, amount)という名前の function を作成します。この function は、credit card を扱っているのか bank transfer を扱っているのかを知る必要はありません。オブジェクトが:process()method を持っていることだけを前提とします。
3つの入力を受け取ります。
- credit card number(例:
4532-1234-5678) - bank account ID(例:
ACC-9876) - payment amount(例:
150)
main file で、最初の2つの入力を使用して各 payment type の instance を1つずつ作成します。その後、handlePayment function を使用して、指定された amount を両方の payment methods で処理します。順番は、First credit card、次に bank transfer です。
たとえば、入力が 1111-2222-3333、ACC-5555、75 の場合、output は次のようになります。
Charging 75 to card 1111-2222-3333
Transferring 75 from account ACC-5555この設計の利点は、handlePayment function が :process() を Implement するあらゆる object で動作することです。明日 PayPal class を追加したとしても、:process(amount) method があれば、既存の function を一切変更せずに動作します。
自分で試してみよう
-- 支払いクラスをインポート
local CreditCard = require('CreditCard')
local BankTransfer = require('BankTransfer')
-- 入力を読み取る
local cardNumber = io.read()
local accountId = io.read()
local amount = tonumber(io.read())
-- TODO: 任意の支払いオブジェクトを受け取る handlePayment 関数を作成する
-- と金額を受け取り、それに対して :process(amount) を呼び出す
-- TODO: 入力を使って CreditCard と BankTransfer のインスタンスを作成する
-- TODO: handlePayment を使って両方の支払い方法で金額を処理する
-- 最初にクレジットカード、次に銀行振込
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Luaオンラインコンパイラ