Ruby on Rails/ActiveRecord/聚合
Active Record 透過類似宏的類方法實現聚合,稱為composed_of用於將屬性表示為值物件。它表達了諸如“帳戶[由]金錢[以及其他東西]組成”或“人[由][一個]地址組成”之類的關係。每次呼叫宏都會新增對如何從實體物件的屬性建立值物件(當實體作為新物件或從查詢現有物件初始化時)以及如何將其轉換回屬性(當實體儲存到資料庫時)的描述。
例子
class Customer < ActiveRecord::Base
composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
end
客戶類現在具有以下方法來操作值物件
* Customer#balance, Customer#balance=(money) * Customer#address, Customer#address=(address)
這些方法將使用下面描述的值物件進行操作
class Money
include Comparable
attr_reader :amount, :currency
EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
def initialize(amount, currency = "USD")
@amount, @currency = amount, currency
end
def exchange_to(other_currency)
exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
Money.new(exchanged_amount, other_currency)
end
def ==(other_money)
amount == other_money.amount && currency == other_money.currency
end
def <=>(other_money)
if currency == other_money.currency
amount <=> amount
else
amount <=> other_money.exchange_to(currency).amount
end
end
end
class Address
attr_reader :street, :city
def initialize(street, city)
@street, @city = street, city
end
def close_to?(other_address)
city == other_address.city
end
def ==(other_address)
city == other_address.city && street == other_address.street
end
end
現在可以透過值物件而不是屬性訪問資料庫中的屬性。如果您選擇將組合命名為與屬性名稱相同,這將是訪問該屬性的唯一方法。這適用於我們的餘額屬性。您可以像對待任何其他屬性一樣與值物件進行互動,儘管
customer.balance = Money.new(20) # sets the Money value object and the attribute
customer.balance # => Money value object
customer.balance.exchanged_to("DKK") # => Money.new(120, "DKK")
customer.balance > Money.new(10) # => true
customer.balance == Money.new(20) # => true
customer.balance < Money.new(5) # => false
值物件也可以由多個屬性組成,例如地址的情況。對映的順序將決定引數的順序。例子
customer.address_street = "Hyancintvej"
customer.address_city = "Copenhagen"
customer.address # => Address.new("Hyancintvej", "Copenhagen")
customer.address = Address.new("May Street", "Chicago")
customer.address_street # => "May Street"
customer.address_city # => "Chicago"
值物件是不可變的,可以互換的物件,它們表示給定的值,例如表示 5 美元的 Money 物件。兩個都表示 5 美元的 Money 物件應該相等(透過方法,例如 == 和 <=> 來自 Comparable 如果排名有意義)。這與實體物件不同,實體物件的相等性由標識決定。例如 Customer 這樣的實體類可以很容易地擁有兩個不同的物件,它們都具有在 Hyancintvej 上的地址。實體標識由物件或關係唯一識別符號(例如主鍵)決定。正常ActiveRecord::Base類是實體物件。
將值物件視為不可變也很重要。不要允許 Money 物件在建立後更改其金額。建立一個具有新值的新的貨幣物件。Money#exchanged_to 方法就是一個例子,該方法返回一個新的值物件,而不是更改其自己的值。Active Record 不會持久化透過除編寫器方法以外的其他方式更改的值物件。
Active Record 透過凍結任何分配為值物件的來強制執行不可變要求。之後嘗試更改它會導致 TypeError。