I want to build a shop in which the products have a little wizard through which the price then is determined. In this case I'm talking about printing products.
So for (a little) example when you come to the shop and want to print a business card, you get to decide if you want to print black and white or in color, if you want to choose thick paper or thin, if you want to print 100, 200, 500 or 1000 pieces and so on.
After all there will be a price for let's say: black and white, thick paper, 200 piece => 40,-$
In real you have many more choices to choose from. So as you can think there are many many prices, for which there is no formula.
So my question is: How can I handle the prices?
My first idea: Decorator Pattern.
But when I thought about it, it's not a very good idea. As I told there is no real formula, also I have to generate the wizard (which may be different if you want to print greeting cards). Also I want to be able to change the price trough a admin interface or add a product or add a new "decision" like "do you want glossy paper?" to a product or remove one (and still keep the prices intact).
So now I'm thinking about using a tree for each product in which I can add a level (a new decision), resort levels and so on.
Another Idea is to Build some Kind of Key-Objects through the decisions and look the price up in a Price table. A bit like a Dictionary in which I add the decisions and after all I generate a key out of it to look up the price in a price table.
So before prototyping I was wondering if I'm just blind and I don't see the obvious solution or maybe there is another way which is more elegant I don't know about?
Well, the idea I'm having is rather complex, but your desire is complex as well.
The problem with the wizard you are describing is that after they've made all their choices, they may want to go back to #2 and change one thing to see how it changes the price - but the way you're building your wizard it may affect whether later choices can be made at all, and result in a rather complex set of code you have to customize for each product - instead of storing everything nicely in a database such that the code can do everything needed, and any product changes go into the database, not code changes.
First, you need to understand that you have pricing logic, and ideally this will be separate from presentation. In other words, you should be able to present a step by step wizard to one customer, and a single page with checkbox options and fields to another and have them build the same thing with the same restrictions.
Try to avoid designing the database so you can't do one or the other. In other words, you restrict future options (and customer flexibility) if you do a "choose your own adventure" style wizard. The reason is that if I go through 10 steps, then change step 2, I have to go through 8 more steps - this may be fine if you guarantee that I'm not duplicating choices I've already made, but if I'm only changing paper color I don't want to make the same 8 choices again. If you do go this route, make sure you keep a lot of state around so that the choices they've made previously show up as the default next time through.
I would consider adding one more level of abstraction to the database. Rather than specify the path through a tree that the user can select, I'd like to see a matrix with each option and product and process showing compatibility.
For instance, I have three paper types, 3 colors, and 3 processes:
Paper
Color
Process
Then I'd have pricing for each match:
Light Regular Card Red Blue Green Print Fold Bind
Light 0.05 0.05 0.001 0.50
Regular 0.06 0.05 0.07 0.001 0.02 0.80
Card 0.06 0.002 0.90
Print 0.002
Fold
Bind
Empty spaces denote items that can't be mixed.
Notable entries:
This is just a sample of a simple incarnation - each price would actually be a link to another table that gives the price per each if the quantity is within a certain range (ie, 100 might be cheaper than 10). It would also hold whether the charge is per piece of paper, per operation, or per order - and this might stack (so you might have a binding charge of 0.001 per piece of paper to account for wear and tear on the cutters, and 0.50 per bound book for the piece price, then $5 setup charge for the binding operation altogether)
This will quickly become pretty complex given the shear number of things you have that you can other things to.
But once the data is entered it will give you unlimited flexibility in specifying costs and presenting them to the user. You may find, for instance, that it's worthwhile putting each different model of printing machine in its own column (rather than just calling them all printers) and finding the lowest printing price for the customer based on the paper type, color, and printing operations.
You can also fill out the matrix with labor (time) so that you can give time estimates in the same place as price estimates.
Whether you do a wizard, or show everything on the page at once, this backend will support it. Look at what Dell does for laptop configurations - you cant' get certain things with other things. So if you display all the options on a page, then when they change from regular paper to card stock you can alert them with, "You've also chosen folding, which is incompatible with card stock. Are you sure you wish to make this change?"
But first and foremost - write up use cases and design the system as a whole first. If you jump in now and start coding something this complex, you're going to make decisions now that will be very hard to undo later, but you'll find later you have to either make major changes, or compromise on a feature.
Of course, the choose your own adventure style is much easier to design and develop, the only problem is that if you add a new process or paper, you may have to update 200 trees, and add 50 new trees. Every change in the shop requires a new database (and possibly software) rollout, which delays payback on capital expenses. If you make it very flexible up front, it's harder now, and easier/faster later.
Furthermore, you don't limit your customers to the tree options - if they really want to print via 4 color press on one side, and cheap copier on the other they can explore that option if the processes and materials are compatible. Past 20 products/processes, you can't really fully fill out the tree.
Lasty, it gives you very, very fine control over cost. If your business process tracks machine usage and cost (maintenance, etc) at a fine grained level, you can underbid your competitor because you know that your machine costs exactly 0.00234 per page, whereas they're stuck with the generic guess. If you start tracking using this system and make employees track problems, maintenance, etc, you may find very odd correlations such as red card stock costs you more at the folding machine because it jams more frequently than the blue (for whatever reason). You can either adjust your pricing (to the nth degree, in this system), talk to the paper manufacturer, stop offering that option, or a number of other tactics to squeeze the system. If some of your machines require skilled operators you can add employees to the matrix with their pay rate, and start scheduling jobs so they're working more efficiently and effectively.
So let's say I choose Light Paper, Red Color, Print. How do I get to the right price? In a two dimensional table I have three possible pairs Red/Print, Red/Light, Print/Light. Sadly I can't calculate the price in an evolving formula I just have one end price after all choices have been made.
Well, I've simplified the above, but let's assume the simple case first.
I didn't mention in the above table what each price represented. The cost for everything is per page, except binding. The cost for binding was per bound item.
So in your example you choose Light paper, red, print. Each piece of red paper is $0.05 (expensive paper!) and each print is $0.001 (cheap printing) so the cost per piece of printed paper is $0.051. If you are making 300 copies, then the total charge is $15.30.
Since binding is listed per bound item, then you might add binding to the above order, and you might be binding 50 pages together, for a total of 6 bound items, 300 pages. We already know the cost of everythign preceding, so the additional cost of 6 bound items (at $0.50 per item) is $3.00, for a new total of $18.30.
There's a few things you're going to have to do beyond the simplification I've specified above though:
In the matrix I've left many cells empty. In some cases that's because the processes/objects are incompatible (can't bind folded items, for instance) but in other cases there's no conflict, but it doesn't cost anything. So your example of red/regular vs regular/red - since we're adding prices together the alternate combination costs nothing.
Let's see if I can make this more clear...
The selection light paper, red, printed, bound (300 pages, 6 bound items) go through the table and finds all the prices that fall inside that set:
You search for all elements in the table (both red/light and light/red) and then take the prices and multiply by the quantity individually, then sum them. The intersection of all these selections is shown in green. (Messed up on the light paper/light paper coloring - should be green too)
I removed the printing/printing combination because it won't work with this method (I was hoping to simplify things, but it actually makes things harder) If you want to specify double sided printing you'll need another item in the table (Print Reverse, for instance) and you'd select both print and print reverse. Alternately have two items, "print single side" and "print double side".
Keep in mind that while I've listed each price in each cell, the reality is that each cell actually describes a few circumstances:
The reference in #1 refers to another table which contains the prices and a third dimension. For instance, the cell at combination "light/bound" would reference a table that has the following three attributes:
These would be summed according to the number of pages and bound items, and then the per job cost is added on top.
This may work for your needs, but what if you have processes that are only incompatible in certain more complex situations?
Right now we can cover the general case of one process or object conflicting with another. We cannot, however, handle anything more complex.
Suppose that the light paper can be printed, and it can be folded, but it can't be both printed AND folded. We can't put a constraint on Print/Fold because other papers can be both printed and folded.
There are a few things you can do:
Vectors are nice, but might be overkill for this application. It is basically the first item, but without defining how many dimensions you can have - you have as many dimensions as you have objects, so any possible combination can be represented.
The additional layer of abstraction is a compromise between the two. Ideally you won't have very many conflicts that are more complex than two items, but when you do, you define a new object that represents a combination. So in this case you might have a new object/process that is the combination of printing and folding, and it has a constraint at the column with light paper.
You can do this only for constraints, in which case it doesn't modify your pricing algorithm (ie, pricing still comes from the addition of individual processes and objects.)
Alternately, you can use an algorithm that selects the greatest combination - so instead of searching the database for Light, Red, Print, Fold you'd first search the database for combinations, which would return the greatest combination of Light, Red, PrintFold, and then price it normally.
Lastly, you might search the database for both individual and combinatory items, and where there's a conflict (double pricing of combination and individual items) then you either select:
-Adam