469,156 Members | 2,201 Online
Bytes | Developer Community
New Post

Home Posts Topics Members FAQ

Post your question to a community of 469,156 developers. It's quick & easy.

Design pattern for cost calculator app?

Hi,

I have a problem that I’ve tried to get help for before, but I wasn’t able to solve it then, so I’m trying to simplify the problem now to see if I can get some more concrete help with this because it is driving me crazy…

Basically, I have a working (more complex) version of this application, which is a project cost calculator. But because I am at the same time trying to learn to design my applications better, I would like some input on how I could improve this design. Basically the main thing I want is input on the conditionals that (here) appear repeated in two places. The suggestions I got before was to use the strategy pattern or factory pattern. I also know about the Martin Fowler book with the suggestion to Refactor conditional with polymorphism. I understand that principle in his simpler example. But how can I do either of these things here (if any would be suitable)? The way I see it, the calculation is dependent on a couple of conditions: 1. What kind of service is it, writing or analysis? 2. Is the project small, medium or large? (Please note that there may be other parameters as well, equally different, such as “are the products new or previously existing?” So such parameters should be possible to add, but I tried to keep the example simple with only two parameters to be able to get concrete help)

So refactoring with polymorphism would imply creating a number of subclasses, which I already have for the first condition (type of service), and should I really create more subclasses for the second condition as well (size)? What would that become, AnalysisSmall, AnalysisMedium, AnalysisLarge, WritingSmall, etc…??? No, I know that’s not good, I just don’t see how to work with that pattern anyway else?

I see the same problem basically for the suggestions of using the strategy pattern (and the factory pattern as I see it would just be a helper to achieve the polymorphism above). So please, if anyone has concrete suggestions as to how to design these classes the best way I would be really grateful! Please also consider whether I have chosen the objects correctly too, or if they need to be redesigned. (Responses like "you should consider the factory pattern" will obviously not be helpful... I've already been down that road and I'm stumped at precisely how in this case)

Regards,

Anders

The code (very simplified, don’t mind the fact that I’m using strings instead of enums, not using a config file for data etc, that will be done as necessary in the real application once I get the hang of these design problems):
Expand|Select|Wrap|Line Numbers
  1. public abstract class Service
  2. {
  3.     protected Dictionary<string, int> _hours;
  4.     protected const int SMALL = 2;
  5.     protected const int MEDIUM = 8;
  6.  
  7.     public int NumberOfProducts { get; set; }
  8.     public abstract int GetHours();
  9. }
  10.  
  11. public class Writing : Service
  12. {
  13.     public Writing(int numberOfProducts)
  14.     {
  15.         NumberOfProducts = numberOfProducts;
  16.         _hours = new Dictionary<string, int> { { "small", 125 }, { "medium", 100 }, { "large", 60 } };
  17.     }
  18.  
  19.     public override int GetHours()
  20.     {
  21.         if (NumberOfProducts <= SMALL)
  22.             return _hours["small"] * NumberOfProducts;
  23.         if (NumberOfProducts <= MEDIUM)
  24.             return (_hours["small"] * SMALL) + (_hours["medium"] * (NumberOfProducts - SMALL));
  25.         return (_hours["small"] * SMALL) + (_hours["medium"] * (MEDIUM - SMALL))
  26.             + (_hours["large"] * (NumberOfProducts - MEDIUM));
  27.     }
  28. }
  29.  
  30. public class Analysis : Service
  31. {
  32.     public Analysis(int numberOfProducts)
  33.     {
  34.         NumberOfProducts = numberOfProducts;
  35.         _hours = new Dictionary<string, int> { { "small", 56 }, { "medium", 104 }, { "large", 200 } };
  36.     }
  37.  
  38.     public override int GetHours()
  39.     {
  40.         if (NumberOfProducts <= SMALL)
  41.             return _hours["small"];
  42.         if (NumberOfProducts <= MEDIUM)
  43.             return _hours["medium"];
  44.         return _hours["large"];
  45.     }
  46. }
  47.  
  48. public partial class Form1 : Form
  49. {
  50.     public Form1()
  51.     {
  52.         InitializeComponent();
  53.         List<int> quantities = new List<int>();
  54.  
  55.         for (int i = 0; i < 100; i++)
  56.         {
  57.             quantities.Add(i);
  58.         }
  59.         comboBoxNumberOfProducts.DataSource = quantities;
  60.     }
  61.  
  62.     private void comboBoxNumberOfProducts_SelectedIndexChanged(object sender, EventArgs e)
  63.     {
  64.         Service writing = new Writing((int) comboBoxNumberOfProducts.SelectedItem);
  65.         Service analysis = new Analysis((int) comboBoxNumberOfProducts.SelectedItem);
  66.  
  67.         labelWriterHours.Text = writing.GetHours().ToString();
  68.         labelAnalysisHours.Text = analysis.GetHours().ToString();
  69.     }
  70. }
  71.  
May 5 '10 #1
7 7982
Anders,

I'm not sure I understand exactly what you are trying to do, but I think the Template Method design pattern may be what you're looking for. http://en.wikipedia.org/wiki/Template_method_pattern

You still use polymorphism, but it's done so in a more privately.

Provide an implemenation at Service for getHours. It would be something like the following:

public int getHours() {
if (NumberOfProducts <= SMALL) {
return getSmall();
}
else if (NumberOfProducts <= MEDIUM) {
return getMedium();
else return getLarge();
}

Service would also abstractly declare getSmall(), getMedium() and getLarge(), each of which returns an int.

The Writing and Analysis classes would each have to declare and define getSmall(), ...

Here are what both would look like.

For Writing, getSmall() would be:
int getSmall() {
return _hours["small"] * NumberOfProducts;
}

For Analysis, getSmall() would be:

int getSmall() {
return _hours["small"];
}

You'd provide similar implementations for getMedium() and getLarge() as well.

I'm not sure about public versus protected or private, but you can look that up in other template method examples on line.

The main idea is that you keep the conditional logic in the base class, but the events processed are pushed down to the derived classes.
May 5 '10 #2
@jhumelsine
Sorry about the formatting. That was my first post. There's a mistake in the getHours() method. The final } should be before the else, not after it.
May 5 '10 #3
Thanks for your reply! I have tried similar ideas, but unfortunately that doesn't quite accomplish what I want. Basically I want to avoid a lot of if-statements that would have to be changed all over the place if a parameter was added or changed. I want to achieve loose coupling so that if something changes I would only have to change in one place. In your example, what would happen e.g. if another size was added? Say XLarge or XSmall? And what if also more services were added? Then I would have to change the Analysis class to add the new sizes, as well as the Writing class and any other added service class...
I thought there would be a good object-oriented way of creating a simple class structure that would avoid complex if-statements and achieve loose coupling. Basically the idea is to have a calculator that calculates the hours needed for a project with different services provided (in this case Writing and Analysis, but there could be others, like say Administration), and the pricing depends on the number of products (manuals actually) produced. But analysis and writing depend on this number in different ways as you can see. Writing is priced progressively, so that if the number of products falls in the SMALL span it will have one price per manual, but if it goes into MEDIUM it will add the price for the whole SMALL span + the discounted MEDIUM price, and so on. While Analysis is much simpler, it just has a bulk price for each size category, period. If I were to restate the problem procedurally it would look like this:
Expand|Select|Wrap|Line Numbers
  1.  public class Conditional
  2.     {
  3.         private int _numberOfManuals;
  4.         private string _serviceType;
  5.         private const int SMALL = 2;
  6.         private const int MEDIUM = 8;
  7.  
  8.         public int GetHours()
  9.         {
  10.             if (_numberOfManuals <= SMALL)
  11.             {
  12.                 if (_serviceType == "writing")
  13.                     return 30 * _numberOfManuals;
  14.                 if (_serviceType == "analysis")
  15.                     return 10;
  16.             }
  17.             else if (_numberOfManuals <= MEDIUM)
  18.             {
  19.                 if (_serviceType == "writing")
  20.                     return (SMALL * 30) + (20 * _numberOfManuals - SMALL);
  21.                 if (_serviceType == "analysis")
  22.                     return 20;
  23.             }
  24.             else
  25.             {
  26.                 if (_serviceType == "writing")
  27.                     return (SMALL * 30) + (20 * (MEDIUM - SMALL)) + (10 * _numberOfManuals - MEDIUM);
  28.                 if (_serviceType == "analysis")
  29.                     return 30;
  30.             }
  31.             return 0;
  32.         }
  33.     }
Of course, I would never write it like that, but it may help to see it like that to see what I'm trying to refactor out into a good object-oriented design. One that avoids the conditional pretty much altogether. I would like to achive a structure where I could, as you say, have it only in one place, such as a base class or a factory. But I just can't wrap my head around it. I've looked at Fowler's examples for Replace conditional with polymorphism (and strategy). But his examples concern a simple switch statement. Here there are more dimensions to the conditional, and I can't get a grip on it...

Any help greatly appreciated!

Regards,

Anders
May 7 '10 #4
Here's another possibility of thinking about this problem. Instead of using template method, which depends upon inheritence, let's try chain-of-responsibility (CoR), which is based upon delegation, and does so by forming a chain of delegates.

Chain of Responsibility can be found at: http://en.wikipedia.org/wiki/Chain-o...bility_pattern

Rather than trying to type in all of the code, you can go there or look else where, but I can kind of describe it.

Decorator is comprised of a linked list of objects. Each either performs a task or it delegates the request to the next object in the link. This continues until an object performs the task or you reach the end of the chain.

if you think about this chain, it functions as a sequence of if/else if.../else commands, like you've shown. Each object would be a conditional check, and the last object would be the final defaulted else condition.

The difference is that it's not hard coded in any one method. The conditional logic is spread over several objects. Here are the relative advantages and disadvantages.

Advantages:
  • It's flexible. In your example, you'd have 3 main objects. One for small, one for medium and the default at the end. When XSmall and and XLarge are needed, these would be 2 new objects and they would be added at the beginning and near the end end of the chain. No other code would be affected. You would still need to implement the XSmall and XLarge CoR objects.
  • You could have several sets of chains for different kinds of customers or analysis. For example, Writing and Analysis could each have their own separate chain. One might use XSmall and XLarge and the other might not.
  • The chain can be updated dynamically. You may want to start with Small, Medium and Large, and then XSmall and XLarge can be added without having to stop your program!

Disadvantages:
  • It's not obvious what's going on. The logic depends upon the configuration of the links. You can't just "see" it like you can with traditional implemenations.
  • Someone has to configure the links. This won't be the user. It may not even be you the developer. However, it might be. It all depends upon where you want that control.
  • You have to be concerned about infinite loops. If a chain links back upon itself by accident, you could get stuck in an infinite loop.

One final note about XSmall and XLarge. As suggested above, if they are added, you'll need to implement them and compile and/or link them in. However, if you use the Prototype Pattern, you might be able to clone new objects without having to know their type. http://en.wikipedia.org/wiki/Prototype_pattern

By the way, Prototype is a misleading name. It should have been called Clone or Breeder.
May 7 '10 #5
rahuljat
1 Bit
The problem is driving me crazy, so I have tried before to get some assistance; however, I was unable to do so, so now that I have simplified it, I am hoping that I can receive some more concrete assistance...
4 Weeks Ago #6
gillsgarryy
1 Bit
I have the same issue. Any tips for solution?
4 Weeks Ago #7
Miash
2 2Bits
You have 6 different formulas for calculating ‘hours’, they look similar but it’s difficult to generalize them (seems it will introduce more complexity than simplification). 6 formulas means you have 6 implicit functions. So you need a pattern that allows easily implement variability of the function used. That is “strategy” pattern is ideal for you, initial proposal was good enough to look for ‘better’ solutions.
2 Weeks Ago #8

Post your reply

Sign in to post your reply or Sign up for a free account.

Similar topics

3 posts views Thread by Omer van Kloeten | last post: by
1 post views Thread by Nick | last post: by
12 posts views Thread by FluffyCat | last post: by
4 posts views Thread by FluffyCat | last post: by
6 posts views Thread by pitachu | last post: by
22 posts views Thread by Krivenok Dmitry | last post: by
1 post views Thread by CARIGAR | last post: by
reply views Thread by zhoujie | last post: by
1 post views Thread by Mortomer39 | last post: by
By using this site, you agree to our Privacy Policy and Terms of Use.