1

I have 4 classes that handle logic for a large project. I have Product that clients buy and i have to bill them. Nevertheless the price of those products is varying greatly according to many variables. I have create a class PriceGenerator that handles the pricing of the products, an Inventory class that checks if the 'Product' is still available and a 'Cart' class that contains a list of 'Product' for a total bill if the client buys many 'Product'

Class PriceGenerator:
    def get_price(*args)

Class Product:
    def prod_bill()

Class Inventory:
    def get_inventory(*args)

Class Cart:
    self.list_product = [product1, product2, product3,...]
    def cart_bill(*args)

my first option:

pg = PriceGenerator()
pd = Product()
inv = Inventory()
cart = Cart()

I could pass the PriceGenerator and Inventory as argument of Cart

def cart_bill(pg, inv, amount):
    bill = 0
    for prod in self.list_product:
        px = prod.prod_bill(pg)
        bill_p = px * min(amount, inv.get_inventory(product_args))
        bill += bill_p

Obviously as the number of methods grows in Product, it becomes very complicated to keep track of all the arguments you have to pass. you pass PriceGenerator and Inventory to Cart that are then passed to the product prod.prod_bill(pg), all those nested dependencies are very cumbersome to pass through all the objects and it makes the code much more complicated.

I could call pg and inv without passing it as arguments for example in Product as a global variable

def produce_bill(self):
    price = pg.get_price(product_args)
    inventory = inv.get_inventory(product_args)

but i really don't like not knowing what are the class/arguments required, necessary for the method.

As the project grows, what design pattern would you suggest?

pam
  • 676
  • 1
  • 7
  • 27

1 Answers1

1

I would recommend implementing a context object containing anything that is relevant to your process. I am assuming this is about placing an e-commerce order in the following example:

class OrderContext:
    price_calculator: PriceCalculator
    inventory: Inventory
    cart: Cart

Now you can pass this object around for all operations that are involved in your process:

cart.add_product(context, product, amount)

This allows you to add/remove further bits to the context without modifying the signature of all your functions. The drawback is that this has the potential to massively increase the number of dependencies within your application (depending on how disciplined the programmers in your team are).

soulmerge
  • 73,842
  • 19
  • 118
  • 155
  • 1
    Also a sidenote: Be particularly aware that the context object pattern is often misunderstood and may end up being "a wrapper for stuff" anti-pattern if not implemented correctly: https://stackoverflow.com/questions/986865/can-you-explain-the-context-design-pattern/16161219 – soulmerge Feb 11 '20 at 11:56
  • would you use context as a global variable or pass it through as an argument? – pam Feb 11 '20 at 12:46
  • 1
    In my opion, if you are storing such an object globally, you are no longer implementing the Context Object Pattern, you are just storing certain things globally. Because a context is by definition not global: you shouldn't have a global `Cart` object, for example, since a cart is tied to a user, which is not a global variable, either (at least it shouldn't be IMHO). If you *do* have a global cart/user, you don't need to pass anything to your functions at all, they can just pull every dependency from the global scope (Please note that I am not advocating this approach: it is a very bad idea) – soulmerge Feb 11 '20 at 13:33
  • Ok so if you need the Context Object in a nested method: you would pass context to as a cascading arguments in nested method. I am juste wondering if there is another approach. overall i discovered this pattern, much appreciated – pam Feb 11 '20 at 13:37