我应该将这个 Rails 模型子类化吗?
Should I should subclass this Rails model?
我有一个名为 Coupon
的模型,可以将其设置为具有 money_off
或 percent_off
属性(一次只能设置一组)。
也根据 Coupon
是 money_off
还是 percent_off
改变使用的方法。
我想知道我是否应该使用单一 table 继承来本质上子 class Coupon
并有一个处理 % 的子 class off coupons 和另一个处理 money off coupons?
然后我想知道用户如何能够从视图中select这个。
你能做的就是在内部维护策略,提供price
、discounted?
、discounted_price
等方法。此外,无论管理员选择输入百分比还是固定单位,您仍然可以提供这两种方法:discount_pct
、discount_units
,这将在内部实现如何计算它们的 return 值。
这样原始的 class 仍然支持概念(与数据模型相同),但也足够灵活,允许以各种方式为其提供必要的输入。无论您是想向客户显示 pct off 还是固定价格单位,您都可以独立于管理员的首选输入方法。
甚至内部方法也可以使用这些抽象。如果事实证明你在内部到处都是 if/elsing,你可以为策略创建嵌套的 classes,并在你从数据库中获取记录后实例化正确的策略。
最好的方法是确定每个 class 需要哪些功能。如果您只需要少量更改,则坚持使用 class 和 enum
:
#app/models/coupon.rb
class Coupon < ActiveRecord::Base
enum type: [:percent, :money]
def value
if type.percent?
# ...
elsif type.money?
# ...
end
end
end
这将允许您在实例方法中使用 type
,这应该不会导致这样的问题如果您没有做太多更改在 class.
内制作
这样您就可以调用:
@coupon = Coupon.find x
@coupon.value #-> returns value based on the type
--
备选方案 (STI) 更像是一种结构化更改,并且只有在您明确引用每个 class 时才会起作用:
#app/models/coupon.rb
class Coupon < ActiveRecord::Base
end
#app/models/percent.rb
class Percent < Coupon
def amount
# ...
end
end
#app/models/money.rb
class Money < Coupon
def takeout
# ...
end
end
这里的一个重要因素是如何称呼它们。
对于上述classes,你必须自己参考subclassed
classes:
@percentage_coupon = Percent.find x
@money_coupon = Money.find y
这显然会比较麻烦,甚至可能导致你的路由和控制器等出现问题
.....所以最好使用单个 class :)
这是一个说明策略用法的示例(Yam 发布了更详细的答案):
class Coupon < Struct.new(:original_price, :amount_off, :type)
def price_after_discount
discount_strategy.call(self)
end
private
def discount_strategy
# note: no hardcoding here
klass = type.to_s.camelize # :money_off to 'MoneyOff'
"Coupon::#{klass}".constantize.new
end
class MoneyOff
def call(coupon)
coupon.original_price - coupon.amount_off
end
end
class PercentOff
def call(coupon)
coupon.original_price * (1.0 - coupon.amount_off / 100.0)
end
end
end
Coupon.new(150, 10, :money_off).price_after_discount # => 140
Coupon.new(150, 10, :percent_off).price_after_discount # => 135.0
现在,我们可以在构造函数中接受它,而不是在内部创建策略,从而使策略成为 "injectable"。
我有一个名为 Coupon
的模型,可以将其设置为具有 money_off
或 percent_off
属性(一次只能设置一组)。
也根据 Coupon
是 money_off
还是 percent_off
改变使用的方法。
我想知道我是否应该使用单一 table 继承来本质上子 class Coupon
并有一个处理 % 的子 class off coupons 和另一个处理 money off coupons?
然后我想知道用户如何能够从视图中select这个。
你能做的就是在内部维护策略,提供price
、discounted?
、discounted_price
等方法。此外,无论管理员选择输入百分比还是固定单位,您仍然可以提供这两种方法:discount_pct
、discount_units
,这将在内部实现如何计算它们的 return 值。
这样原始的 class 仍然支持概念(与数据模型相同),但也足够灵活,允许以各种方式为其提供必要的输入。无论您是想向客户显示 pct off 还是固定价格单位,您都可以独立于管理员的首选输入方法。
甚至内部方法也可以使用这些抽象。如果事实证明你在内部到处都是 if/elsing,你可以为策略创建嵌套的 classes,并在你从数据库中获取记录后实例化正确的策略。
最好的方法是确定每个 class 需要哪些功能。如果您只需要少量更改,则坚持使用 class 和 enum
:
#app/models/coupon.rb
class Coupon < ActiveRecord::Base
enum type: [:percent, :money]
def value
if type.percent?
# ...
elsif type.money?
# ...
end
end
end
这将允许您在实例方法中使用 type
,这应该不会导致这样的问题如果您没有做太多更改在 class.
这样您就可以调用:
@coupon = Coupon.find x
@coupon.value #-> returns value based on the type
--
备选方案 (STI) 更像是一种结构化更改,并且只有在您明确引用每个 class 时才会起作用:
#app/models/coupon.rb
class Coupon < ActiveRecord::Base
end
#app/models/percent.rb
class Percent < Coupon
def amount
# ...
end
end
#app/models/money.rb
class Money < Coupon
def takeout
# ...
end
end
这里的一个重要因素是如何称呼它们。
对于上述classes,你必须自己参考subclassed
classes:
@percentage_coupon = Percent.find x
@money_coupon = Money.find y
这显然会比较麻烦,甚至可能导致你的路由和控制器等出现问题
.....所以最好使用单个 class :)
这是一个说明策略用法的示例(Yam 发布了更详细的答案):
class Coupon < Struct.new(:original_price, :amount_off, :type)
def price_after_discount
discount_strategy.call(self)
end
private
def discount_strategy
# note: no hardcoding here
klass = type.to_s.camelize # :money_off to 'MoneyOff'
"Coupon::#{klass}".constantize.new
end
class MoneyOff
def call(coupon)
coupon.original_price - coupon.amount_off
end
end
class PercentOff
def call(coupon)
coupon.original_price * (1.0 - coupon.amount_off / 100.0)
end
end
end
Coupon.new(150, 10, :money_off).price_after_discount # => 140
Coupon.new(150, 10, :percent_off).price_after_discount # => 135.0
现在,我们可以在构造函数中接受它,而不是在内部创建策略,从而使策略成为 "injectable"。