Решение на Трета задача от Пламен Стоев

Обратно към всички решения

Към профила на Пламен Стоев

Резултати

  • 6 точки от тестове
  • 0 бонус точки
  • 6 точки общо
  • 19 успешни тест(а)
  • 0 неуспешни тест(а)

Код

require 'bigdecimal'
require 'bigdecimal/util'
module InvoiceRow
def to_row
sprintf("| %-44s%2s | %8.2f |\n", @label, @quantity.to_s, calculate)
end
end
class CartTotal
include InvoiceRow
def initialize(cart)
@cart = cart
@label = 'TOTAL'
end
def calculate
(@cart.coupon) ? @cart.coupon.calc_price : @cart.without_coupon
end
end
class DiscountGetOneFree
include InvoiceRow
def initialize(required_quantity, product)
@required = required_quantity
@product = product
@label = " (buy #{ @required - 1 }, get 1 free)"
end
def calculate
-(@product.quantity / @required * @product.price)
end
end
class DiscountPackage
include InvoiceRow
def initialize(rule={}, product)
@packaged, @percent = rule.flatten
@product = product
@label = " (get #@percent% off for every #{ @packaged })"
end
def calculate
-(@product.quantity / @packaged) * @product.price * @packaged * @percent * '0.01'.to_d
end
end
class DiscountThreshold
include InvoiceRow
SUFFIXES = ['th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th']
def initialize(rule, product)
@required, @percent = rule.flatten
@product = product
suffix = SUFFIXES[@required % 10]
@label = " (#@percent% off of every after the #@required#{ suffix })"
end
def calculate
discounted = @product.quantity - @required
(discounted > 0) ? -discounted * @product.price * @percent * '0.01'.to_d : 0
end
end
class Product
include InvoiceRow
alias :original_row :to_row
DISCOUNTS = {
get_one_free: DiscountGetOneFree,
package: DiscountPackage,
threshold: DiscountThreshold,
}
attr_reader :quantity, :price
def initialize(name, price, discount={})
@label = name
@price = price.to_d
@quantity = 0
if discount != {}
discount_type, discount_rule = discount.flatten
@discount = DISCOUNTS[discount_type].new(discount_rule, self)
end
end
def calculate
@price * @quantity
end
def calc_price
calculate + (@discount ? @discount.calculate : 0)
end
def add(quantity)
@quantity += quantity
end
def to_row
original_row + (@discount ? @discount.to_row : '')
end
end
class Coupon
include InvoiceRow
def calc_price
@cart.without_coupon + calculate
end
end
class CouponPercent < Coupon
def initialize(name, value, cart)
@name = name
@value = value
@cart = cart
@label = "Coupon #@name - #@value% off"
end
def calculate
-@value * '0.01'.to_d * @cart.without_coupon
end
end
class CouponAmount < Coupon
def initialize(name, value, cart)
@name = name
@value = value.to_d
@cart = cart
@label = "Coupon #@name - #{ '%3.2f' % @value } off"
end
def calculate
(@value > @cart.without_coupon) ? -@cart.without_coupon : -@value
end
end
class Cart
COUPONS = {
percent: CouponPercent,
amount: CouponAmount,
}
attr_reader :coupon
def initialize(inventory)
@inventory = inventory
@contents = {}
@cart_total = CartTotal.new self
end
def add(product, quantity=1)
raise "Invalid arguments" unless in_constraints? product, quantity
unless @contents[product]
@contents[product] = Product.new(*@inventory.products[product])
end
@contents[product].add(quantity)
end
def total
@cart_total.calculate
end
def without_coupon
@contents.map { |label, product| product.calc_price }.inject(&:+)
end
def use(coupon)
raise "No such coupon #{ coupon }" unless @inventory.coupons[coupon]
raise "You already used coupon #{ @coupon.label }" if @coupon
coupon_type, coupon_value = *@inventory.coupons[coupon]
@coupon = COUPONS[coupon_type].new(coupon, coupon_value, self)
end
def invoice
h_line = "+------------------------------------------------+----------+\n"
title = "| Name qty | price |\n"
content = @contents.map { |label, product| product.to_row }
content += [@coupon.to_row] if @coupon
[h_line, title, h_line, *content, h_line, @cart_total.to_row, h_line].join ''
end
private
def in_constraints?(product, quantity)
@inventory.products[product] and
quantity >= 0 and
quantity < 100
end
end
class Inventory
attr_reader :products, :coupons
def initialize
@products = {}
@coupons = {}
end
def register(product, price, discount={})
raise "Invalid arguments passed." unless in_constraints? product, price.to_d
@products[product] = [product, price, discount]
end
def register_coupon(coupon_name, coupon_rules)
raise "Coupon already registered" if @coupons.has_key? coupon_name
coupon_type, coupon_value = coupon_rules.flatten
@coupons[coupon_name] = [coupon_type, coupon_value]
end
def new_cart
Cart.new(self)
end
private
def in_constraints?(product, price)
product.length <= 40 &&
(not @products[product]) &&
price >= '0.01'.to_d &&
price <= '999.99'.to_d
end
end

Лог от изпълнението

...................

Finished in 0.62348 seconds
19 examples, 0 failures

История (1 версия и 2 коментара)

Пламен обнови решението на 02.11.2011 19:52 (преди над 12 години)

+require 'bigdecimal'
+require 'bigdecimal/util'
+
+module InvoiceRow
+ def to_row
+ sprintf("| %-44s%2s | %8.2f |\n", @label, @quantity.to_s, calculate)
+ end
+end
+
+class CartTotal
+ include InvoiceRow
+
+ def initialize(cart)
+ @cart = cart
+ @label = 'TOTAL'
+ end
+
+ def calculate
+ (@cart.coupon) ? @cart.coupon.calc_price : @cart.without_coupon
+ end
+end
+
+class DiscountGetOneFree
+ include InvoiceRow
+
+ def initialize(required_quantity, product)
+ @required = required_quantity
+ @product = product
+ @label = " (buy #{ @required - 1 }, get 1 free)"
+ end
+
+ def calculate
+ -(@product.quantity / @required * @product.price)
+ end
+end
+
+class DiscountPackage
+ include InvoiceRow
+
+ def initialize(rule={}, product)
+ @packaged, @percent = rule.flatten
+ @product = product
+ @label = " (get #@percent% off for every #{ @packaged })"
+ end
+
+ def calculate
+ -(@product.quantity / @packaged) * @product.price * @packaged * @percent * '0.01'.to_d
+ end
+end
+
+class DiscountThreshold
+ include InvoiceRow
+
+ SUFFIXES = ['th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th']
+
+ def initialize(rule, product)
+ @required, @percent = rule.flatten
+ @product = product
+ suffix = SUFFIXES[@required % 10]
+ @label = " (#@percent% off of every after the #@required#{ suffix })"
+ end
+
+ def calculate
+ discounted = @product.quantity - @required
+ (discounted > 0) ? -discounted * @product.price * @percent * '0.01'.to_d : 0
+ end
+end
+
+class Product
+ include InvoiceRow
+
+ alias :original_row :to_row
+
+ DISCOUNTS = {
+ get_one_free: DiscountGetOneFree,
+ package: DiscountPackage,
+ threshold: DiscountThreshold,
+ }
+
+ attr_reader :quantity, :price
+
+ def initialize(name, price, discount={})
+ @label = name
+ @price = price.to_d
+ @quantity = 0
+ if discount != {}
+ discount_type, discount_rule = discount.flatten
+ @discount = DISCOUNTS[discount_type].new(discount_rule, self)
+ end
+ end
+
+ def calculate
+ @price * @quantity
+ end
+
+ def calc_price
+ calculate + (@discount ? @discount.calculate : 0)
+ end
+
+ def add(quantity)
+ @quantity += quantity
+ end
+
+ def to_row
+ original_row + (@discount ? @discount.to_row : '')
+ end
+end
+
+class Coupon
+ include InvoiceRow
+
+ def calc_price
+ @cart.without_coupon + calculate
+ end
+end
+
+class CouponPercent < Coupon
+ def initialize(name, value, cart)
+ @name = name
+ @value = value
+ @cart = cart
+ @label = "Coupon #@name - #@value% off"
+ end
+
+ def calculate
+ -@value * '0.01'.to_d * @cart.without_coupon
+ end
+end
+
+class CouponAmount < Coupon
+ def initialize(name, value, cart)
+ @name = name
+ @value = value.to_d
+ @cart = cart
+ @label = "Coupon #@name - #{ '%3.2f' % @value } off"
+ end
+
+ def calculate
+ (@value > @cart.without_coupon) ? -@cart.without_coupon : -@value
+ end
+end
+
+class Cart
+ COUPONS = {
+ percent: CouponPercent,
+ amount: CouponAmount,
+ }
+
+ attr_reader :coupon
+
+ def initialize(inventory)
+ @inventory = inventory
+ @contents = {}
+ @cart_total = CartTotal.new self
+ end
+
+ def add(product, quantity=1)
+ raise "Invalid arguments" unless in_constraints? product, quantity
+ unless @contents[product]
+ @contents[product] = Product.new(*@inventory.products[product])
+ end
+ @contents[product].add(quantity)
+ end
+
+ def total
+ @cart_total.calculate
+ end
+
+ def without_coupon
+ @contents.map { |label, product| product.calc_price }.inject(&:+)
+ end
+
+ def use(coupon)
+ raise "No such coupon #{ coupon }" unless @inventory.coupons[coupon]
+ raise "You already used coupon #{ @coupon.label }" if @coupon
+ coupon_type, coupon_value = *@inventory.coupons[coupon]
+ @coupon = COUPONS[coupon_type].new(coupon, coupon_value, self)
+ end
+
+ def invoice
+ h_line = "+------------------------------------------------+----------+\n"
+ title = "| Name qty | price |\n"
+ content = @contents.map { |label, product| product.to_row }
+ content += [@coupon.to_row] if @coupon
+ [h_line, title, h_line, *content, h_line, @cart_total.to_row, h_line].join ''
+ end
+
+ private
+
+ def in_constraints?(product, quantity)
+ @inventory.products[product] and
+ quantity >= 0 and
+ quantity < 100
+ end
+end
+
+class Inventory
+ attr_reader :products, :coupons
+
+ def initialize
+ @products = {}
+ @coupons = {}
+ end
+
+ def register(product, price, discount={})
+ raise "Invalid arguments passed." unless in_constraints? product, price.to_d
+ @products[product] = [product, price, discount]
+ end
+
+ def register_coupon(coupon_name, coupon_rules)
+ raise "Coupon already registered" if @coupons.has_key? coupon_name
+ coupon_type, coupon_value = coupon_rules.flatten
+ @coupons[coupon_name] = [coupon_type, coupon_value]
+ end
+
+ def new_cart
+ Cart.new(self)
+ end
+
+ private
+
+ def in_constraints?(product, price)
+ product.length <= 40 &&
+ (not @products[product]) &&
+ price >= '0.01'.to_d &&
+ price <= '999.99'.to_d
+ end
+end

Най-сериозната ти грешка, е че имаш само един Coupon клас и само един Discount клас. И двата правят по няколко неща и в тях има гадни if-ове.

Опитай да направиш 3 класа за промоции и два за купони. Дори няма нужда да имат общ родител, просто общ интерфейс.

Другото е, че ако ползваш sprintf, няма нужда от rjust. Виж как форматира %-5.2f.

Така по-добре ли е?

Ако имаме три класа за промоция и два за купони реално нямаме повторение на код, заради различните им действия, така ли?

Също така, махнах Invoice класа, изглежда ми по-добре всичко да се върши в Cart#invoice. Исках да напиша отделен клас, но за да можеше да принтира нещата му трябваха много неща, които се намират в Cart, затова реших всичко да се върши там (или почти всичко, по същите съображения сложих тези Product#to_rows и Coupon#to_row методи). Не знам дали е съвсем правилно така.

Edit: Махнах повторенията с малко наследяване. Станаха 10 класа. В началото се чудех откъде ще дойдат толкова много класове, когато ти спомена на лекцията 10 (или 12, не помня точно). Вече горе-долу виждам откъде :)

Edit2: Понеже се размотаваха аргументи наляво-надясно постоянно (quantity и price например), реших да позвам направо полетата на съответните класове (т.е. примерно Discount-ите да взимат стойностите за количество и единична цена направо от продукта, за който се отнасят). Не знам дали е лошо да се постъпи така в случая, но го оставям така.