4

I am building a service for pricing options of a product and for pricing the product itself when product has various options.

Example

User selects some X options out of Y options available, where X <= Y. The service then computes the pricing from selected options using cost for those options.

Using Pseudocode, it goes something like this:

$totalPrice = 0;

//number of modules comes from user input OR can be derived from it
//price per module comes from database
$productPrice = [PRICE FOR A MODULE] * [NUMBER OF MODULES];

$totalPrice += $ProductPrice;

//option 1 selection comes from user input
if ([OPTION ONE IS SELECTED])
     $totalPrice += [PRICE FOR OPTION ONE]

Question:

How can I decide who deals with options and who deals with pricing?

For example, I can inject selected options into a Pricing class but that complicates Pricing, since now business logic inside Pricing class needs to be aware of selected options and how to use them to compute the price.

Perhaps I can have a class for just options, and another class for just pricing, and then some kind of a third "Combiner" class that knows how to combine options and pricing to come up with final totals. But won't that be too complicated?

Is there a model for this sort of thing?

General Problem Statement

A more general problem is --

Given

  • A) User Input (i.e. Add-on Options)
  • B) Product Specifications (i.e. number of product sub-parts) which can be derived elsewhere from user input and its own service
  • C) Pricing for sub-parts, Pricing for Options

Compute total Product price.

How can I do this in OO?

  • Passing selected options (perhaps some kind of an Option model) seems fine to me. What don't you like about it? You are afraid of complicated code, but calculating the values of the selected options seems as easy as a single foreach loop to me. Or is that the complication you are worried about? – Andy Feb 11 '16 at 22:18
  • I am not worried about 'lower constructs' such as for loops, but more so OO-design. My concern about injecting options into pricing is that pricing now will contain code like 'has option Q been selected?' and it will have to know that option Q exists. If option Q later becomes discontinued, then pricing will have to be changed/updated. In other words, I am concerned that taking care of computing pricing using options in one class will violate SRP. – Dennis Feb 11 '16 at 22:44
  • My propsal is to pass only the selected options into the Pricingclass, not all of them. That way the Pricing class only contains data relevant for the specific transaction. – Andy Feb 12 '16 at 7:44
  • Question - where do I fit multiple options? Say a product has 6 options, I select 4 and I need to have them displayed later using {"option name", "price for option"} pairs. Would they be placed as a linked list into the Pricing class, or be done separately? – Dennis Feb 12 '16 at 22:13
1

Code is C# pseudocode.

A Word about MVC

The domain model is unconcerned with trasformations at the seams of MVC. That is we don't care how the data is going to get from the UI to the business classes to the data store (and back). A good domain model is the fixed reference around which all the doing-stuff and MVC transmuting code evolves.


Summary

Looks like a lot? No. Appropriate classes makes coding easy and the resulting code simple. How? Single Responsibility Principle and encapsulation.

  • Big Ideas

    • Structure

      • A domain model is the central focus
      • Coherent objects get created from UI selections
      • Coherent objects get created from data-source
    • Custom Collections

      • Single Responsibility Principle at play, i.e. A place to put collective functionality
    • Pricing API

      • "Price" is calculated dynamically.
        • sales, promotions, option packages
      • "Cost" is fixed. MSRP essentially.
      • Easy, idiot-proof for the client
      • Fanatically hide all details from the API clients.
  • Patterns, at least conceptually

    • Visitor
      • A PriceRule knows what Product properties to use.

Domain Data Structures

Design a model that makes sense so that it is easy to access and is stable as the cost/pricing algorithm and code evolves.

public class Product {
    var Id;
    var Cost;  // MSRP. Before the kibitzing begins.
    // No Price property. Will dynamically calc.
    // My design decision. You can do otherwise.
    var Options;

    // MSRP. No price adjusting.
    public Decimal GetCost { return this.Cost + this.Options.GetCost(); }

    // The visitor pattern. 
    public void GetPrice ( PricingCollection adjustments) {
        if(adjustments == null) return this.GetCost();

        return adjustments.Apply(this);
    }
}

public class Option {
    var Id;
    var Cost;  // MSRP
    bool Discontinued;  // don't show it on the UI if it is.
}

public class OptionCollection {
    var Options;  // Add() method not shown

    public decimal GetCost() {
        var total = 0;

        foreach (var option in Options)
            total += option.Cost;

        return total;
    }
}

Product-Options

    public ProductOptions : Dictionary<Product, OptionCollection>

Populated from a data-store at program initialization time.

The complete set of product-options and can be used in the UI. Change the data-store of course changes the available options for the user.

Removing options after being selected for a product is a requirements issue. I would think you fill existing product orders with now discontinued options.


Pricing

Perhaps I can have a class for just options, and another class for just pricing,

Built into a separate set of classes. We inject the "pricing model" into the Product and the "pricing model" knows how to adjust costs to come up w/ a final, total price.

For example "Buy option X and get option Y at half price." Or "$3,OOO off of our Texas Tough truck, San Jacinto Day only."

I'm not going to argue "interface" vs "abstract class". Client code cares about API, not its implementation.

public abstract class PriceRule {      
    // Sub classes must implement this method
    // As complex as your sales gimmic demands.
    public abstract Decimal Apply (Product thisProduct);
}

PriceRule must know the domain model structure. That's OK. That is the idea of the Visitor design pattern.

The PriceRule API implies (for the domain model) that an OptionCollection can exist only w/in a Product object. I like that because it keeps our structure(s) consistant - it always belongs to some Product object. Unless requirements dictate otherwise of course.

 public class PriceRuleCollection {
    var PriceRules; // Add() method not shown

    public Decimal Apply (Product thisProduct) {
        var total = 0;
        foreach (var rule in PriceRules)
            total = rule.Apply(thisProduct);

        return total;
    }      
  }

and then some kind of a third "Combiner" class that knows how to combine options and pricing to come up with final totals. ... But won't that be too complicated?

Just the opposite! You may think it too trivial, but it has 2 important purposes.

  • It declares the concept of a "pricing model" explicitly. The term is part of the business we're coding for. We can talk and code in our customers' terms.
  • A place to add code at the "pricing model" level of abstraction. We can manipulate the order of price rule application. We can add new concepts like financing.

.

public class PricingModel{
    public PricingModel( PriceRuleCollection pricing, Product product, ProductOptions allOptions ) {
        this.Pricing = pricing;
        this.Product = product;
        this.ProductOptions = allOptions;   // might be handy. We everything in one place.
    }

    public Decimal TotalPrice() {
        return this.pricing.Apply(this.product);
    }
  }
0

Personally I would go with your idea of a third class. To me, this seems most consistent with an MVC (Model, View, Controller) pattern. If I understand correctly, an option (or Module) has a price property. This is part of your model. A user makes selections -- this is something that happens in your view. When it comes time to calculate a total based on a user's selection, this should be the job of your controller. In other words, the controller asks the view, "what has the user selected?" and gets a list of selections; the controller then queries the database/model and asks "what does each of these selected options cost?" and maps these selections to prices. It then has all the information it needs to perform the calculation, in which it asks itself "what is the total cost of all these options?" -- and then sends that information back to the view to be displayed to the user. To extend this whole use case to completion, if the user then interacts with the view and agrees to go ahead with their purchase, then the controller can go back to the model and have it create an order entity, and update the "number sold" property on each option/module, and whatever else you might want to do as part of your business logic.

As a rule, business logic lives in the controller. Entities, and information about entities, lives in the model. Raw user input, and what is displayed back to the user, belongs in the view. This isn't more complicated - you might have more 'parts', but each part has a clearly defined role, and it's much simpler to make changes to any of these parts while the rest remain the same. You could, for example, change the way everything is presented to the user as much as you like, and not even have to look at/touch/have access to the model or controller components. Likewise, if you wanted to rewrite the whole app, but keep the same data regarding available modules/options, all you need to do is keep in mind how your data is structured when designing the new controller and view however you like.

It can seem a little counter-intuitive at first, but creating more components can make an application a lot easier to write, debug, and modify later. You already seem to understand this intuitively, in that you're thinking about what should be responsible for what - but luckily you don't need to make all these decisions for yourself in a vacuum, as there are already established patterns like 'MVC' which answer most of these questions for you.

A good rule of thumb is to keep all your business logic separate from everything else. A product on a shelf needs a price tag, it doesn't need its own cash register to ring up a sale. Make the module that handles your business logic independent, and you can do anything you like with it - reuse it to sell different products by plugging in a new model, or create a whole new method of interaction, e.g. API calls from another application, instead of your dedicated user interface.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.