超市系统的设计模式
Design Patterns for a SuperMarket system
我是一级软件开发人员,开始像二级软件开发人员一样思考。作为 interview:build 超市定价系统的一部分,我的任务是一个相对简单的编码挑战用例。
Rules and Requirements: Each item at Super Foods is identified by a
unique four-digit code. Today, pricing schemes at Super Foods use the
following pricing categories, but beware: prices are constantly
changing, and the sales department is always creating new incentives
and deals, such as buy one-get-one free.
EG: Chips and salsa (items #6732 and #4900) cost .99 together, but
they cost .49 and .49 alone, respectively.
EG2: Buy two toothbrushes .99 each, get one free.
EG3: A bottle of wine (item #0923) costs .49 and is taxed an
additional 9.25%
通读了设计模式,这看起来像是某种形式的 Decorator
模式的自然位置,用于计算对象的销售额。具有模式 <ID, ObjectName, Price>
的 SQLite 数据库也将以某种方式有用,尽管我对我们如何在所有这些中制作数据访问对象感到生疏。
我正试图以全栈 MVC 的心态来思考这个问题,我觉得我可能对某些事情生疏了。这就是 Spring 框架的著名之处吗?也许可以为这个用例推荐更好的 API?
感谢任何帮助我集思广益设计这个系统的人。
可能的步骤:
- 定义数据库模型
- Java
中的镜像数据库模型
- 实现 DAO 层 -> CRUD 操作
- 实施服务层 -> 业务逻辑
- 公开服务层 -> 例如休息
- 实现客户端,即面向暴露服务层的客户端
装饰器模式用于 add/modify 现有 class 的行为而不改变 class 本身。因此,它充当现有 class 的包装器,因此您可能会想到它。重点是您没有要扩展的系统,而是从头开始构建它!
S/w设计难度大,不能一蹴而就。此外,我确定您的潜在雇主对您的设计方式比您使用的技术堆栈更感兴趣。所以我不会对此发表评论。由你决定。
根据您的要求,这些是我的初步想法。有改进的余地(是的!),但至少这应该适用于您的任务范围。这是 C#。不过,这不应该阻止您理解它。
namespace Entities {
public class StoreItem
{
// Code
// Name
// Cost
// Tax -> for specific items
// MfgDate
// ExpDate
}
public class StoreDeal
{
// Code
// Name
// Cost
// Validity
// Items (type: IList<StoreItem>) -> items participating in a deal
}
}
namespace Domain {
public class Cart
{
// Items (type: IList<CartItem>)
// TotalAmount
// TotalDiscount
// FinalAmount
}
public class CartItem
{
public CartItem(string code) {
Code = code; // assume "6732" -> Chips
}
public CartItem(StoreItem item) {
MapStoreItem(item);
}
// declare props: Code, Name, Quantity, Cost
public void Prepare() {
if(Quantity > 0) {
// Invalid operation alert and return
// This is one time call per item type
}
// Sample. Retrieve item from database.
var item = new StoreItem { Code = code, Name = "Chips", Cost = 2.49, Tax = 0 /* etc */ }
MapStoreItem(item);
Quantity = 1;
}
public void UpdateQuantity(int quantity) {
Quantity = quantity;
Cost = Cost * Quantity;
}
private void MapStoreItem(StoreItem item) {
Code = item.Code;
Name = item.Name;
Cost = CalculateCost(item.Cost, item.Tax);
}
private static double CalculateCost(double cost, double tax) {
// If tax > 0, apply it to cost
// else return cost as is
}
}
}
public class DealService
{
public StoreDeal GetDeal(string itemCode) {
// Assume item to be Chips. Retrieve current deal that involve Chips.
// Sample data. You should delegate this stuff to data access layer.
return
new StoreDeal {
Code = "CS4.99",
Name = "Chips and salsa @ .99",
Cost = 4.99,
Items = new List<StoreItem> {
new StoreItem { Code = "6732", Name = "Chips" },
new StoreItem { Code = "4900", Name = "Salsa" }
}
}
}
}
public class CartService
{
private Cart cart;
private DealService dealService;
// ctor - inject dependencies
public CartService(Cart cart, DealService dealService) {
this.cart = cart;
this.dealService = dealService;
}
public void AddItem(CartItem item) {
var found = cart.Items.Find(i => i.Code == item.Code);
if (found != null) { // Update quantity
found.UpdateQuantity(found.Quantity + 1);
}
else { // Add new item
item.Prepare();
cart.Items.Add(item);
}
}
public void RemoveItem(string code) {
var found = cart.Items.Find(i => i.Code)
if (found != null) {
cart.Items.Remove(found);
}
}
public void CalculateTotal() {
// Used for checking which items in cart have got deal applied on.
// We don't want "CS4.99" deal applied to both Chips and Salsa, for ex. Only for one of them.
// So this collection simply holds deal codes already applied.
var dealsApplied = new List<string>();
foreach(var item in cart.Items) {
// Check deal
StoreDeal deal = dealService.GetDeal(item.Code);
// Apply the logic for TotalAmount, TotalDiscount, FinalAmount
}
}
}
请注意,如果您要真正设计这样的系统,那么 class 的数量会比上面的多得多。例如,在实际情况下 "Chips" 不是一个项目,它是一种项目,因此不能有代码。但是,"Lays Potato Chips" 将是类型为 "Chips" 的单个项目,具有自己的代码。此外,StoreItem 将成为抽象实体,它由 EdibleItem、PersonalCareItem、HealthCareItem、CosmeticItem 和任何存在于真实商店中的类型派生,其中 EdibleItem 将特别具有不适用于此列表中其他内容的营养信息。
最后,我只是写了这个(不完整的)代码,没有测试!你看到评论的代码是不完整的,我是故意这样做的,因为我不想让你盲目地在面试中作弊。 :)
好的,这是一个没有任何设计模式的逻辑角度的答案。
table 带有商品代码和价格的就可以了。
因此,如果正在签出的列表中的任何项目都没有交易和奖励,那么只需使用此 table,计算总价并完成结账。
现在,当结帐列表中的任何产品 deals/incentives 时,您将不得不进行各种可能的组合。
为了使问题陈述简单:假设列表中有 3 个项目。
因此您必须查看以下组合:
项目 1、项目 2、项目 3
项目 1,项目 2
项目 1,项目 3
项目 2 项目 3
现在对于这些组合中的任何一个,您都必须搜索 deal/incentive 列表。这就像说我正在谷歌搜索一个字符串 "Item1 Item2",结果我得到了一组网络链接,这些链接在这个组合上是 deals/incentives。
在组合中,我检查哪个组合现在处于活动状态并对其应用交易价格。
设置交易价格后,从结帐列表中删除项目组合,然后继续处理列表中的剩余项目。
希望对您有所帮助。
我允许自己添加的不仅仅是设计模式。我也把它当作我的练习:)
让我们分解一下,并通过我的思考过程:
- 系统中的数字和名称表示类型
ID
- four digits number - unsigned short int
- we only need numbers from 0 - 9999 which means we need just 14 bits (2^14 = 16384)
and unsigned short int uses 16 bits. That gives us space in case we
would like to increase the number of items to 65536 (2^16)
Name
- string UTF-16
, usually is good enough. However we have to remember that some products might come from far countries and use
various characters. UTF-16 has only 16 bits per a character (again
65536), so we have to remember in our implementation, that if we need
more bits we need to use different encoding (like UTF-32)
Price
or Discount
- is float
good enough? I think it's not. In many languages performing various arithmetic operations lead to not fully
correct answers (try in JavaScript 0.2 * 0.4
- you can use your
browser's console). When it comes to money you don't want to increase
prices or loose money. There are special libraries and types
implementations to be safe, when it comes to dealing with money
- 数据结构和类
Item
- class/structure with following fields: ID
, Name
, Original price
(types described above)
Item Collection
- array - As we use short unsigned int
for IDs, we can also use it to index the array. We can even initialize
the whole array and load all items into memory when the app starts
up.
PriceModifier
- ID
- short unsigned int
, Type
- ENUM to represent type of discount (or tax) and to know what do to in the
case, it is applied, Name
- String UTF-16 (just to inform an user),
Value
- short signed int
percentage modifier
PairPriceModifier
- class
- extends PriceModifier
and adds pair of item IDs
.
PriceModifier Collection
- here we need to use a structure with quick search, as we use ID
as a short usigned int
, we can use an
array of array. That list won't change frequently. When we need to add/remove a modifier, we can do that in a linear time
O(1)*O(m), where m is Modifier length of an Item
. We won't copy
physically Modifiers as we can use references to PriceModifier
objects.
Shopping Cart Collection
- the key functionality of each shop is a shopping cart, it has to be optimized to: add/remove items and to list
saved items. We need to frequently add/remove elements and check for
already added items to apply various discounts, but also we want to
list all saved items (for instance in the cart view). We can use
hash table with coupled array [to have O(1) when searching/adding/removing items and O(n) when listing] We update the
final price when we add/remove an item. In the array we save a
reference to object of a class, which represents an item from shopping
cart to know which PriceModifier
was applied
CartItem
- class
with fields: ItemID
- short unsigned int
, Quantity
- unsigned int
, AppliedPriceModifier Collection
-
hash table to easy access applied PriceModifiers
with coupled array to list them in Cart view.
AppliedPriceModifier
- class
with fields: PriceModifierID
, Quantity
Interesting case: In case we add some element into Shopping Cart
Collection
we need to check the PriceModifier Collection
. In O(1)
we can access the right list of PriceModifiers
we have to apply.
Then in case we spot PairPriceModifier
we need to check Shopping
Cart Collection
againm, if we don't have an paired item there. The
whole operation takes:
- O(1) - to obtain array of
PriceModifier
of length m
- O(m) - to go thru the array and apply modifiers
- O(1) - in case we've found
PairPriceModifier
we access existing item from Shopping Cart Collection
- O(k)+O(m) - to update collections of
AppliedPriceModifier
for both items in the Shopping Cart Collection
- O(1) - to update final price (explained below)
- 设计模式
- For the whole APP structure I think it's good to use some framework with Inversion of Control. To keep our code modulerized and easier to unit test we
can use a dependecy injection pattern.
- For presentation layer we can use either MVC or its mutation - MVVM. It will give us dual binding ability to quickly update UI. Information
about final price, deals and applied discounts or taxes is essential.
- For updating final price, we can use Observer
pattern. We observe the
Shopping Cart Collection
and when
something goes in or goes out, we can update final price in constant
time
- To keep our code compact we can use chaining(Cascade)
- You can use Decorator
pattern to add extra functionality to
PriceModifiers
without
changes in classes (for instnace when one situation prevents from
applying an another PriceModifier
)
- To keep the
CartItems Collection
safe from changes outside of the cart, we can use Mediator design pattern. That mediator service would be responsible for applying PriceModifiers
, only when an Item
is being added or removed from the Cart Collection
- 其他问题
我们可以扩大话题问:
- How to represent your data in DB
- What if we would like to increase number of items and represent them as 10.000 of alpha-numeral characters
- What to do, if we would like to keep our shopping app 99.9% stable and running
- How can we handle many requests
- What happens if a client has a discount applied in his cart, but system administrator removes that discount from the system and many
more.
请添加一些评论,因为这对我来说也是一个有趣的话题。我能improve/change做什么?我没有想到什么?
我是一级软件开发人员,开始像二级软件开发人员一样思考。作为 interview:build 超市定价系统的一部分,我的任务是一个相对简单的编码挑战用例。
Rules and Requirements: Each item at Super Foods is identified by a unique four-digit code. Today, pricing schemes at Super Foods use the following pricing categories, but beware: prices are constantly changing, and the sales department is always creating new incentives and deals, such as buy one-get-one free.
EG: Chips and salsa (items #6732 and #4900) cost .99 together, but they cost .49 and .49 alone, respectively.
EG2: Buy two toothbrushes .99 each, get one free.
EG3: A bottle of wine (item #0923) costs .49 and is taxed an additional 9.25%
通读了设计模式,这看起来像是某种形式的 Decorator
模式的自然位置,用于计算对象的销售额。具有模式 <ID, ObjectName, Price>
的 SQLite 数据库也将以某种方式有用,尽管我对我们如何在所有这些中制作数据访问对象感到生疏。
我正试图以全栈 MVC 的心态来思考这个问题,我觉得我可能对某些事情生疏了。这就是 Spring 框架的著名之处吗?也许可以为这个用例推荐更好的 API?
感谢任何帮助我集思广益设计这个系统的人。
可能的步骤:
- 定义数据库模型
- Java 中的镜像数据库模型
- 实现 DAO 层 -> CRUD 操作
- 实施服务层 -> 业务逻辑
- 公开服务层 -> 例如休息
- 实现客户端,即面向暴露服务层的客户端
装饰器模式用于 add/modify 现有 class 的行为而不改变 class 本身。因此,它充当现有 class 的包装器,因此您可能会想到它。重点是您没有要扩展的系统,而是从头开始构建它!
S/w设计难度大,不能一蹴而就。此外,我确定您的潜在雇主对您的设计方式比您使用的技术堆栈更感兴趣。所以我不会对此发表评论。由你决定。
根据您的要求,这些是我的初步想法。有改进的余地(是的!),但至少这应该适用于您的任务范围。这是 C#。不过,这不应该阻止您理解它。
namespace Entities {
public class StoreItem
{
// Code
// Name
// Cost
// Tax -> for specific items
// MfgDate
// ExpDate
}
public class StoreDeal
{
// Code
// Name
// Cost
// Validity
// Items (type: IList<StoreItem>) -> items participating in a deal
}
}
namespace Domain {
public class Cart
{
// Items (type: IList<CartItem>)
// TotalAmount
// TotalDiscount
// FinalAmount
}
public class CartItem
{
public CartItem(string code) {
Code = code; // assume "6732" -> Chips
}
public CartItem(StoreItem item) {
MapStoreItem(item);
}
// declare props: Code, Name, Quantity, Cost
public void Prepare() {
if(Quantity > 0) {
// Invalid operation alert and return
// This is one time call per item type
}
// Sample. Retrieve item from database.
var item = new StoreItem { Code = code, Name = "Chips", Cost = 2.49, Tax = 0 /* etc */ }
MapStoreItem(item);
Quantity = 1;
}
public void UpdateQuantity(int quantity) {
Quantity = quantity;
Cost = Cost * Quantity;
}
private void MapStoreItem(StoreItem item) {
Code = item.Code;
Name = item.Name;
Cost = CalculateCost(item.Cost, item.Tax);
}
private static double CalculateCost(double cost, double tax) {
// If tax > 0, apply it to cost
// else return cost as is
}
}
}
public class DealService
{
public StoreDeal GetDeal(string itemCode) {
// Assume item to be Chips. Retrieve current deal that involve Chips.
// Sample data. You should delegate this stuff to data access layer.
return
new StoreDeal {
Code = "CS4.99",
Name = "Chips and salsa @ .99",
Cost = 4.99,
Items = new List<StoreItem> {
new StoreItem { Code = "6732", Name = "Chips" },
new StoreItem { Code = "4900", Name = "Salsa" }
}
}
}
}
public class CartService
{
private Cart cart;
private DealService dealService;
// ctor - inject dependencies
public CartService(Cart cart, DealService dealService) {
this.cart = cart;
this.dealService = dealService;
}
public void AddItem(CartItem item) {
var found = cart.Items.Find(i => i.Code == item.Code);
if (found != null) { // Update quantity
found.UpdateQuantity(found.Quantity + 1);
}
else { // Add new item
item.Prepare();
cart.Items.Add(item);
}
}
public void RemoveItem(string code) {
var found = cart.Items.Find(i => i.Code)
if (found != null) {
cart.Items.Remove(found);
}
}
public void CalculateTotal() {
// Used for checking which items in cart have got deal applied on.
// We don't want "CS4.99" deal applied to both Chips and Salsa, for ex. Only for one of them.
// So this collection simply holds deal codes already applied.
var dealsApplied = new List<string>();
foreach(var item in cart.Items) {
// Check deal
StoreDeal deal = dealService.GetDeal(item.Code);
// Apply the logic for TotalAmount, TotalDiscount, FinalAmount
}
}
}
请注意,如果您要真正设计这样的系统,那么 class 的数量会比上面的多得多。例如,在实际情况下 "Chips" 不是一个项目,它是一种项目,因此不能有代码。但是,"Lays Potato Chips" 将是类型为 "Chips" 的单个项目,具有自己的代码。此外,StoreItem 将成为抽象实体,它由 EdibleItem、PersonalCareItem、HealthCareItem、CosmeticItem 和任何存在于真实商店中的类型派生,其中 EdibleItem 将特别具有不适用于此列表中其他内容的营养信息。
最后,我只是写了这个(不完整的)代码,没有测试!你看到评论的代码是不完整的,我是故意这样做的,因为我不想让你盲目地在面试中作弊。 :)
好的,这是一个没有任何设计模式的逻辑角度的答案。 table 带有商品代码和价格的就可以了。 因此,如果正在签出的列表中的任何项目都没有交易和奖励,那么只需使用此 table,计算总价并完成结账。 现在,当结帐列表中的任何产品 deals/incentives 时,您将不得不进行各种可能的组合。
为了使问题陈述简单:假设列表中有 3 个项目。
因此您必须查看以下组合: 项目 1、项目 2、项目 3 项目 1,项目 2 项目 1,项目 3 项目 2 项目 3
现在对于这些组合中的任何一个,您都必须搜索 deal/incentive 列表。这就像说我正在谷歌搜索一个字符串 "Item1 Item2",结果我得到了一组网络链接,这些链接在这个组合上是 deals/incentives。
在组合中,我检查哪个组合现在处于活动状态并对其应用交易价格。
设置交易价格后,从结帐列表中删除项目组合,然后继续处理列表中的剩余项目。
希望对您有所帮助。
我允许自己添加的不仅仅是设计模式。我也把它当作我的练习:) 让我们分解一下,并通过我的思考过程:
- 系统中的数字和名称表示类型
ID
- four digits number -unsigned short int
- we only need numbers from 0 - 9999 which means we need just 14 bits (2^14 = 16384) and unsigned short int uses 16 bits. That gives us space in case we would like to increase the number of items to 65536 (2^16)Name
-string UTF-16
, usually is good enough. However we have to remember that some products might come from far countries and use various characters. UTF-16 has only 16 bits per a character (again 65536), so we have to remember in our implementation, that if we need more bits we need to use different encoding (like UTF-32)Price
orDiscount
- isfloat
good enough? I think it's not. In many languages performing various arithmetic operations lead to not fully correct answers (try in JavaScript0.2 * 0.4
- you can use your browser's console). When it comes to money you don't want to increase prices or loose money. There are special libraries and types implementations to be safe, when it comes to dealing with money
- 数据结构和类
Item
- class/structure with following fields:ID
,Name
,Original price
(types described above)Item Collection
- array - As we useshort unsigned int
for IDs, we can also use it to index the array. We can even initialize the whole array and load all items into memory when the app starts up.PriceModifier
-ID
-short unsigned int
,Type
- ENUM to represent type of discount (or tax) and to know what do to in the case, it is applied,Name
- String UTF-16 (just to inform an user),Value
-short signed int
percentage modifierPairPriceModifier
-class
- extendsPriceModifier
and adds pair of itemIDs
.PriceModifier Collection
- here we need to use a structure with quick search, as we useID
as ashort usigned int
, we can use an array of array. That list won't change frequently. When we need to add/remove a modifier, we can do that in a linear time O(1)*O(m), where m is Modifier length of anItem
. We won't copy physically Modifiers as we can use references toPriceModifier
objects.Shopping Cart Collection
- the key functionality of each shop is a shopping cart, it has to be optimized to: add/remove items and to list saved items. We need to frequently add/remove elements and check for already added items to apply various discounts, but also we want to list all saved items (for instance in the cart view). We can use hash table with coupled array [to have O(1) when searching/adding/removing items and O(n) when listing] We update the final price when we add/remove an item. In the array we save a reference to object of a class, which represents an item from shopping cart to know whichPriceModifier
was appliedCartItem
-class
with fields:ItemID
-short unsigned int
,Quantity
-unsigned int
,AppliedPriceModifier Collection
- hash table to easy access appliedPriceModifiers
with coupled array to list them in Cart view.AppliedPriceModifier
-class
with fields:PriceModifierID
,Quantity
Interesting case: In case we add some element into
Shopping Cart Collection
we need to check thePriceModifier Collection
. In O(1) we can access the right list ofPriceModifiers
we have to apply. Then in case we spotPairPriceModifier
we need to checkShopping Cart Collection
againm, if we don't have an paired item there. The whole operation takes:
- O(1) - to obtain array of
PriceModifier
of length m- O(m) - to go thru the array and apply modifiers
- O(1) - in case we've found
PairPriceModifier
we access existing item fromShopping Cart Collection
- O(k)+O(m) - to update collections of
AppliedPriceModifier
for both items in theShopping Cart Collection
- O(1) - to update final price (explained below)
- 设计模式
- For the whole APP structure I think it's good to use some framework with Inversion of Control. To keep our code modulerized and easier to unit test we can use a dependecy injection pattern.
- For presentation layer we can use either MVC or its mutation - MVVM. It will give us dual binding ability to quickly update UI. Information about final price, deals and applied discounts or taxes is essential.
- For updating final price, we can use Observer pattern. We observe the
Shopping Cart Collection
and when something goes in or goes out, we can update final price in constant time- To keep our code compact we can use chaining(Cascade)
- You can use Decorator pattern to add extra functionality to
PriceModifiers
without changes in classes (for instnace when one situation prevents from applying an anotherPriceModifier
)- To keep the
CartItems Collection
safe from changes outside of the cart, we can use Mediator design pattern. That mediator service would be responsible for applyingPriceModifiers
, only when anItem
is being added or removed from theCart Collection
- 其他问题
我们可以扩大话题问:
- How to represent your data in DB
- What if we would like to increase number of items and represent them as 10.000 of alpha-numeral characters
- What to do, if we would like to keep our shopping app 99.9% stable and running
- How can we handle many requests
- What happens if a client has a discount applied in his cart, but system administrator removes that discount from the system and many more.
请添加一些评论,因为这对我来说也是一个有趣的话题。我能improve/change做什么?我没有想到什么?