Design Patterns for a SuperMarket system [closed]
Asked Answered
B

4

6

I'm a Software Developer I who is beginning to think like a Software Developer II. I am tasked with a relatively simple Use Case for a coding challenge following as part of an interview:build a Supermarket Pricing System.

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 $4.99 together, but they cost $2.49 and $3.49 alone, respectively.

EG2: Buy two toothbrushes $1.99 each, get one free.

EG3: A bottle of wine (item #0923) costs $15.49 and is taxed an additional 9.25%

Having read through Design Patterns, this looks like a natural place for some form of Decorator Pattern for totaling the sales of objects. A SQLite database, with schema <ID, ObjectName, Price> will also be useful somehow, though I'm rusty on how we go about making the data access objects in all this.

I'm trying to wrap my mind around this in a full stack MVC mindset, I feel like I might be rusty on something. Is this what the Spring Framework is renowned for? Maybe an even better API for this use case can be recommended?

Thank you for anyone helping me to brainstorm the design of this system out.

Byebye answered 30/3, 2017 at 14:39 Comment(1)
I suppose at this point I've hit a roadblock. I've made some code, but how and where to implement what objects as the mediator or observables has me in a cycle of doubt on where to go next: github.com/dnmorris7/supermarketdemoByebye
M
2

Decorator pattern is used to add/modify existing class's behavior without changing class itself. So that acts as a wrapper for existing class, and may be therefore you thought of it. Point is your don't have system that you're extending, you are building it from scratch!

S/w design is tough and cannot be finalized in one go. Also I'm sure your prospect employer is more interested in how you design it, than what tech stack you use. So I'll not comment on that. It's your call.

Per your requirements, these are my initial thoughts. There is room for improvement (Yes!) but at the least this should work for the scope of your assignment. This is C#. That shouldn't stop you from understanding it though.

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 @ $4.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
        }
    }
}

Note that, if you were to design such system for real, then there would be much more classes than above. For example, in real case "Chips" is not an item, it's a type of item and hence cannot have Code. However, "Lays Potato Chips" would be an individual item of type "Chips" with it's own Code. Also StoreItem would become abstract entity which is derived by types like EdibleItem, PersonalCareItem, HealthCareItem, CosmeticItem and anything that exists in real store where EdibleItem will specifically have Nutrition info which does not apply to other in this list.

Lastly, I just wrote this (incomplete) code, didn't test it! Code is incomplete where you see comments, and did this purposely because I don't want you to cheat interview blindly. :)

Manse answered 31/3, 2017 at 6:46 Comment(1)
Thank you. The biggest help was the suggestions that I should think of a Deal as a set of items. I should then be able to set rules within the Collections class, and iterate through it. I'm thinking of setting a tax rate as a static int that requires a class method to change, but an Enum is something consider as well.Byebye
E
3

I allowed myself to add a little more than design patterns. I treated it as an exercise for me as well :) Let's break it down, and go thru my thought process:

  1. Numbers and names representation in system as types
  • 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
  1. Data structures and classes
  • 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)
  1. Design patterns
  • 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
  1. Another questions

We can expand the topic and ask:

  • 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.

Please add some comments, as it's an interesting topic for me as well. What could I improve/change? What I didn't think through?

Epner answered 31/3, 2017 at 10:56 Comment(5)
I'm trying to think of "Deals" as an object in and of itself, but I'm having a hard time running the IS-A HAS-A test on it. Do items have deals? Or is it transactions? I thought I'd just use a decorator design pattern but I now realize how it doesn't exactly fit here. Unless it's maybe Prices that have deals. I seem to have hit a logic wall here.Byebye
@Byebye The decorator pattern modifies existing classes and/or functions. I think, that it fits more for coding than architecture. You need to think that PriceModifiers or, how you named them, Deals are separate objects that can connect with Items and Transactions. Even if you would pair Deals with Items you still need to keep information about other Items in your shopping cart. Also I'm not sure if you wanted to ask me as you didn't accept my answer :)Epner
How I compare the set of deals to what's in a shopping cart instance is another thing that I fear I'm overthinking. In Python, there are Set opertations to return the intersection, union, or difference between two lists. Eg: >>discount = {'chips', 'salsa'} >>cart= ['chips', 'salsa', 'milk'] >>match = discount.intersection(cart) >>match ['chips', 'salsa', 'milk'] I figure if I could do the same intersection in Java it'd clear up some of the logic issues.Byebye
@Byebye Yup that's useful functionality and probably optimized. However how would you check for two (or more) the same items which are in the cart?Epner
I've finished much of the code. While not bug free, but it completes two out of the three features utilizing the Decorators and my attempt at a mediator. The missing basic feature is the capacity to check two items and create a bundle. Any ideas on how to implementation? github.com/dnmorris7/supermarketdemoByebye
M
2

Decorator pattern is used to add/modify existing class's behavior without changing class itself. So that acts as a wrapper for existing class, and may be therefore you thought of it. Point is your don't have system that you're extending, you are building it from scratch!

S/w design is tough and cannot be finalized in one go. Also I'm sure your prospect employer is more interested in how you design it, than what tech stack you use. So I'll not comment on that. It's your call.

Per your requirements, these are my initial thoughts. There is room for improvement (Yes!) but at the least this should work for the scope of your assignment. This is C#. That shouldn't stop you from understanding it though.

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 @ $4.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
        }
    }
}

Note that, if you were to design such system for real, then there would be much more classes than above. For example, in real case "Chips" is not an item, it's a type of item and hence cannot have Code. However, "Lays Potato Chips" would be an individual item of type "Chips" with it's own Code. Also StoreItem would become abstract entity which is derived by types like EdibleItem, PersonalCareItem, HealthCareItem, CosmeticItem and anything that exists in real store where EdibleItem will specifically have Nutrition info which does not apply to other in this list.

Lastly, I just wrote this (incomplete) code, didn't test it! Code is incomplete where you see comments, and did this purposely because I don't want you to cheat interview blindly. :)

Manse answered 31/3, 2017 at 6:46 Comment(1)
Thank you. The biggest help was the suggestions that I should think of a Deal as a set of items. I should then be able to set rules within the Collections class, and iterate through it. I'm thinking of setting a tax rate as a static int that requires a class method to change, but an Enum is something consider as well.Byebye
R
0

Okay here is an answer on a logical perspective without any design patterns. The table that you have with item code and price is fine. So if there are no deals and incentives for any of the items in the list being checked out then just use this table, calculate the total price and complete the check out. Now when there are deals/incentives for any product(s) within the check out list you would have to go through various combinations possible.

To keep problem statement simple: Lets assume there are 3 items in the list.

So you would have to look at the following combinations : Item1, Item2, Item3 Item1, Item2 Item1, Item3 Item2 Item3

Now for any of these combinations you would have to search your deal/incentive list. This is like saying I am googling for a string "Item1 Item2" and as a result I get a set of web links that are deals/incentives on this combination.

Within the combination I check which one is active right now and apply the deal price on it.

After setting the deal price remove the combination of items from the checkout list and proceed for the remaining items in the list.

I hope this helps.

Rebato answered 31/3, 2017 at 10:24 Comment(0)
P
-1

possible steps:

  • define database model
  • mirror db model in Java
  • implement DAO layer -> CRUD operations
  • implement Service layer -> bussiness logic
  • expose service layer -> e.g. REST
  • implement client side which is client towards exposed Service layer
Palimpsest answered 31/3, 2017 at 6:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.