Has-A関係
CoddyのLuaジャーニー「Object Oriented Programming」セクションの一部 — レッスン 49/70。
このコースを通じて、クラス間で振る舞いを共有するために継承を使用してきました。DogはAnimalである("is an"関係)というようにです。しかし、継承が常に適切なツールであるとは限りません。
時として、オブジェクトは家系図を共有しません。代わりに、あるオブジェクトが単に別のオブジェクトを含んでいることがあります。これはコンポジションと呼ばれ、「has-a」関係をモデル化します。
車を考えてみましょう。車はエンジンの種類ではありません。車はエンジンを持っているのです。エンジンは、車の中に存在する独立したオブジェクトです:
local Engine = {}
Engine.__index = Engine
function Engine:new(horsepower)
local obj = {horsepower = horsepower}
setmetatable(obj, Engine)
return obj
end
local Car = {}
Car.__index = Car
function Car:new(model, engine)
local obj = {
model = model,
engine = engine -- Carの中にEngineオブジェクトを格納する
}
setmetatable(obj, Car)
return obj
end車を作成するとき、既存のエンジンオブジェクトをそのコンストラクタに渡します:
local myEngine = Engine:new(200)
local myCar = Car:new("Sedan", myEngine)
print(myCar.engine.horsepower) -- 200車はエンジンから継承するのではなく、単にエンジンへの参照を保持します。これにより、両方のクラスが独立した状態に保たれます。別のエンジンに入れ替えたり、同じエンジンクラスをオートバイで使用したりすることも可能です。
コンポジションは、継承にはない柔軟性をもたらします。オブジェクトを厳格な継承階層に固定するのではなく、部品から組み立てるように構成できるからです。
チャレンジ
簡単コンポジションを使ってコンピュータシステムを構築しましょう!継承の代わりに、内部に CPU を「持っている」 Computer を作成し、「has-a」関係を実証します。
コードは以下の3つのファイルに分けて構成します:
CPU.lua: ブランド名とGHz単位の速度を保存するコンストラクタ:new(brand, speed)を持つ CPU クラスを作成します。{brand} @ {speed}GHzという形式の文字列を返す:getInfo()メソッドを含めてください。Computer.lua: モデル名と CPU オブジェクトを受け取るコンストラクタ:new(model, cpu)を持つ Computer クラスを作成します。Computer はその内部に CPU オブジェクトを保持します(これがコンポジションです!)。コンピュータのモデルとその CPU の情報を表示する:describe()メソッドを含めてください。main.lua: すべてをまとめます!CPU ブランド、CPU 速度、およびコンピュータモデルの入力を読み取ります。まず CPU オブジェクトを作成し、それを Computer のコンストラクタに渡します。最後に、コンピュータの:describe()メソッドを呼び出します。
以下の3つの入力を受け取ります:
- CPU ブランド(文字列)
- GHz 単位の CPU 速度(数値)
- コンピュータのモデル名(文字列)
出力には、含まれている CPU の情報を含むコンピュータの説明を表示する必要があります:
Model: {model}
CPU: {brand} @ {speed}GHz例えば、入力が Intel、3.5、WorkStation の場合、出力は以下のようになります:
Model: WorkStation
CPU: Intel @ 3.5GHzComputer が CPU を継承しているのではなく、単に CPU への参照を保持していることに注目してください。Computer の :describe() メソッドは、保存されたオブジェクトを介して CPU のデータにアクセスします。これは、コンポジションによって単純なパーツから複雑なオブジェクトを構築する方法を示しています!
チートシート
オブジェクトは、"has-a"(持っている)関係をモデル化するコンポジション(合成)を通じて関連付けることができます。継承の代わりに、あるオブジェクトが別のオブジェクトをコンポーネントとして含みます。
エンジンを持つ車のコンポジションの例:
local Engine = {}
Engine.__index = Engine
function Engine:new(horsepower)
local obj = {horsepower = horsepower}
setmetatable(obj, Engine)
return obj
end
local Car = {}
Car.__index = Car
function Car:new(model, engine)
local obj = {
model = model,
engine = engine -- Store the Engine object inside Car
}
setmetatable(obj, Car)
return obj
end合成されたオブジェクトの作成と使用:
local myEngine = Engine:new(200)
local myCar = Car:new("Sedan", myEngine)
print(myCar.engine.horsepower) -- 200コンポジションは、厳格な継承階層を使用するのではなく、パーツからオブジェクトを組み立てることで柔軟性を提供します。コンポーネントは独立したままであり、異なるクラス間で再利用できます。
自分で試してみよう
-- main.lua
-- コンポジションを使用してすべてをまとめます
local CPU = require('CPU')
local Computer = require('Computer')
-- 入力を読み込む
local cpuBrand = io.read()
local cpuSpeed = tonumber(io.read())
local computerModel = io.read()
-- TODO: ブランドと速度を指定してCPUオブジェクトを作成する
-- TODO: モデルとCPUオブジェクトを指定してComputerオブジェクトを作成する
-- TODO: コンピュータのdescribe()メソッドを呼び出して出力を表示する
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。