Before getting into this first column, I want to establish my assumptions for the column as a whole, including the kinds of solutions I want to develop. My first assumption is that the goal of a design pattern is to provide an object-oriented template for solving typical business applications. These templates have been created by people smarter than me and validated against the experiences of lots of programmers. My second assumption is that the goal of object-oriented design is to build complex solutions out of many simple classes that are easy to understand, test, discuss, maintain and (perhaps) even use more than once (though reuse can be more of a political and social issue than a technical one).
There’s a cost associated with these kinds of solutions: lots of “moving parts” replace larger, more complex objects. Long term, however, these solutions reduce maintenance costs: changes are isolated to individual classes rather than rippling through the application, and new functionality is added by defining new classes rather than modifying existing, working code. (Vogel’s first law of programming: “You don’t <expletive deleted> with working code.”)
Choosing the right design pattern for these solutions is important. If you don’t use the right pattern, you’ll end up with lots of objects that don’t make your life easier and that you’ll have to constantly rewrite as times change. That would be bad.
So here’s my plan. I’ll start each column by presenting some business problem, discussing some potential solutions that I don’t think will work, and then proposing a solution that addresses the problem in a testable/maintainable way, based on some design pattern. From there I’ll build out the design to explore what the pattern means. Then I’ll demonstrate what an implementation of the solution looks like. In some cases, I’ll start with the data design but eventually show you the code required. I’ll be able to cover some problems within one column. Other problems will require more space.
I also suspect that this column will be interactive. I imagine that most comments will come from readers who point out how I have picked the wrong solution, misunderstood the pattern or bungled the implementation. I take feedback seriously, so you might find one month’s column proposing a solution and promising to look at its implementation in the next month, but then the next month’s column abandoning my original solution because a reader has suggested a better one. Or the implementation might be deferred to address issues that readers raise. Life’s like that—and an effective developer is flexible.
Here’s the problem I’ll tackle in this inaugural Patterns in Practice column. In an application for managing sales orders, the application adds Product objects to a SalesOrder object by creating OrderLine objects. OrderLines specify the quantity being purchased and the SalesOptions the customer has chosen. Those SalesOptions include giftwrapping, discounts, expediting the product delivery or backordering products that aren’t currently available. The company will almost certainly offer additional SalesOptions in the future. The problem is how to best manage the functionality those added SalesOptions will require.
I can dismiss some solutions right away. Having a single Product object with all the necessary code would create a class with lots of complex logic that would pile up on itself quickly (handling discounted products that are also backordered, for example). The result would be a Product class that’s hard to understand, talk about, test or maintain. What I’m after is a set of objects that are dedicated to doing one thing well, ideally without any conditional statements. I want script-like code with one procedural statement following another and as little logic as possible.
Inheritance isn’t going to solve the problem either, at least not in .NET. In addition to defining a dedicated BackorderedProduct and a DiscountedProduct, you would need to create a class for every combination (for example, BackorderedDiscountedGiftwrappedProduct). In a framework where a single class could inherit from multiple classes, inheritance might work. In .NET, each object would do one thing well, but a large number of objects would be required. To complicate the scenario, the number of objects would double every time a new option for buying a product was added.
What I’m talking about here is adding functionality to the Product object as SalesOptions are added. This functionality isn’t required all the time—it’s needed only when a product is assigned a specific option. Rather than bake that functionality into the Product class or its inheritance hierarchy (which is what the inheritance solution would involve), the functionality should be added as needed at run time. That requirement suggests a couple other solutions covered by some well-known design patterns.
NOTE If you’re not familiar with the patterns I describe in the next few paragraphs, don’t be disheartened. I’m bringing them up as a way of better describing the business problem and explaining why they don’t solve this particular problem, either because they just don’t fit or they create more problems than they solve. You’ll likely encounter these patterns again in later columns as the appropriate solution to some problem.
One possible solution is to apply the Strategy pattern: pass some object that wraps up the necessary processing and have the Product class use that object internally. However, the Strategy pattern should be used when a single algorithm handles all the processing for some operation. I’m going to arbitrarily assert that each sales option affects multiple parts of the Product class (its price, obviously, but also when it will be shipped and which departments it will go through on its way to the customer). The Strategy pattern isn’t intended to handle this kind of problem.
Another option is the Decorator pattern, which allows functionality to be piled up onto an object. This pattern is especially appropriate when the output from one piece of additional functionality is the input to another piece of functionality. In the current problem, the price of the product has that characteristic: the discount option is applied to the product to reduce the price, and then any charge for the giftwrapping option is added on top of that discounted charge—discounts don’t apply to giftwrapping. However, the Decorator pattern works by manipulating the public interface of the object that the client is working with. Again, I’m going to arbitrarily assert that supporting SalesOptions requires access to private data within the Product object. In addition, with the exception of price, each option is relatively independent of any other option. The Decorator pattern doesn’t address these conditions.
The State pattern sounds like another alternative. In the State pattern, the class decides what state it’s in and delegates work to an internal object that holds all the code for that state. The current scenario has two conditions that make State inappropriate, however. First, the Product can have multiple SalesOptions applied at any one time. Second, and more critical, in this business the Product object doesn’t decide whether the discount or giftwrapping should be applied. The client applies these SalesOptions to the Product as part of making the Product part of a SalesOrder. The State pattern doesn’t solve this type of problem.
The Roles pattern is the best solution for most parts of this problem, even though the pattern is usually discussed in in sociological terms: the functions a person takes when interacting with others. For example, a typical discussion of the Roles pattern would describe an employee as having several roles: an employee, of course, but also a customer when the employee buys a product from the company; a manager if the employee has some supervisory rank; a specialist in some area; or someone who has been laid off or has retired. What is relevant in the Roles pattern isn’t its name but rather its functionality. (In the Squeak dialect of Smalltalk, an equivalent concept is called “traits”; Erich Gamma has a similar pattern called Extension Objects that provides a broader reference.) Despite its name, the Roles pattern is a good fit for solving the SalesOptions problem.
The critical issues for selecting the Roles pattern are whether a “functionality set” is applied through the entity’s relationship with a client and whether several of those functionality sets are in play at the same time. In this case, the SalesOptions exist because of the relationship between the Product object and the OrderLine object. Each SalesOptions adds its own functionality set, and multiple sets can be in play at once. The Roles pattern isn’t the best solution for dealing with all parts of the problem, however: calculating price is best handled by the Decorator pattern because of the interdependencies between the SalesOptions. If the functionality sets have to interact with each other, you probably shouldn’t use the Roles pattern. For this column, I’ll discuss just the Roles part of the solution. (The Decorator pattern will undoubtedly come up in a later column.)
Rather than bury the code for each option in the entity class, the Roles pattern bundles it up into a separate class for each role. Not only does this simplify the design/implementation/testing of each class, but it also means that the application can add new roles (in this case, more SalesOptions) without rewriting either the OrderLine or the Product class or, possibly, even the client (though that seems unlikely because the client probably needs to access unique members on each role).
Part of designing a solution is designing the interfaces that the solution needs throughout that process. For me, the design process eventually shades off into implementation activities. Once I start working with the implementation, I begin to understand the problem better and find myself revisiting my interfaces. When using the Roles pattern, I can make a good start on solving the problem by designing the interfaces before I start working on the implementation.
First, when the client selects or determines that an option should be applied, the appropriate Role object should be added to the Product object. The Role should be created by the Product, however, even if it’s just so that the Product can determine whether the Role is valid—a backordered Product can’t be expedited, for example. So the Product needs an AddRole method that a client can call, passing a Role Specifier that stipulates which Role to add. The specifier can be as simple as a string but should be an enumerated value and might be a class with multiple members. Product also needs a GetRole method that accepts a specifier so that the client can retrieve a Role to work with (and if the Role isn’t applicable, GetRole should return null or raise an exception).
Right now, I’m assuming that when a Product is created, all the Roles currently applied to the Product are also created. That code may end up in the Product’s constructor, but if the code for determining what Roles are to be loaded is complex or likely to be volatile, I might move the code to a method in a separate class. If it turns out that Role objects are resource intensive to create or are used infrequently, I might defer creating the Role object until requested in the GetRole method. Having all the current Roles created when the Product class is created, however, means that I can have a read-only Roles collection that a client can iterate through. If it turns out that the functionality isn’t required, I drop it.
To support iterating through all the current Roles, I want all the Roles to look alike. That means the Roles should share a common base class or an interface. If my Role class has some default behavior, a base class would make sense. For example, if there was a lot of overlap in the members of the different Role classes and I didn’t expect the number of members to grow as new Roles are added, having a base Role class would be smart. I could have the base class implement all the members for all the Roles and throw a “not implemented” exception when called. Each individual Role would just override those members it implemented. The base class might also provide a way for clients to check which members were implemented in a particular Role so that clients could avoid raising exceptions.
But let’s say that I expect the company to keep adding new Roles, which will result in new members. I’ll be forced to rewrite the base object each time I add a new Role—not the end of the world, but something I’d prefer to avoid. Instead of rewriting a base object with each new Role, I’ll have all the Role objects implement the same interface, which will make them look alike without sharing any code. If there were no overlap between the Roles, I might be wiser to follow the Extension Objects pattern. For now, however, I’ll have the interface implement just two members: a RoleStatus property (values to be defined later) and a RoleStatusChanged event that fires whenever the RoleStatus changes.
I’ve now started down the slippery slope between design and implementation. And while the design should drive the implementation, working with the implementation helps me understand the problem better, which always leads to changes in the design. Developing the implementation, and making any required changes to the design, are the topics of next month’s column.
Peter Vogel is the principal system architect in PH&V Information Services, specializing in SharePoint and service-oriented architecture (SOA) development, with expertise in user interface design. In addition, Peter is the author of four books on programming and wrote Learning Tree International’s courses on SOA design ASP.NET development taught in North America, Europe, Africa and Asia.
Design patterns provide solutions to common problems and I encourage developers to think in a pattern oriented way, neverthelss experience (at least mine) shows that, pattern oriented developers and none pattern oriented developers often (again my personal experience) reach the same correct solution! In the article's example above, the question (for me) is not which pattern to use, instead it is how to make the system testable, expandable, efficient, maintablele....etc. without having to know 50 design patterns. In the example mentioned in the article, the most important thing to do is: 1) Provide an interface or base class (e.g. PriceBase). 2) For each price modification provide a subclass of PriceBase. 3) The SalesOption class must have a collection of at least one PriceBase class. so somewhere I end up having a collection of PriceBase class, this basically will satisfy my needs.
I've found the article an excellent read. It's not "too wordy". In an explaination of the thinking process involved in not just coming up with "a" solution but in "choosing between" alternatives. I like the "think out loud" style. I can get terse explainations from all over the net but what we see more and more in Q&A forums is people asking about "Best Practices" but often those discussions are shut down by moderators as being too off topic, or discussion based. So it's a very pleasant change to to be able to listen to the internal dialog of another, more experienced developer. I don't understand Bill comments re data first, etc. To me, you have written an article about the options available to address "behaviour and responsibility", not "state". Obviously you're constructing a context/narrative in which to discuss a particular set of ideas. So the idea that the 'problem has already been solved' so no discussion is required is somewhat absurd to me. I think you've adequately stressed and anyone reading any article should know by now there is more than one way to skin a cat. Just because a problem "got solved" in the real world doesn't mean it was solved "optimally" either then or for the future. So any idea that we shouldn't revisit our thinking, just sounds old and grumpy. So please keep them coming. I am explicitly looking for these kinds of "insight" articles. Architecture is a very "fuzzy" space so it's great to read about how others also "um and ah" a bit, not because they cant think of a way to solve a problem but because they can think of several ways to solve a problem.
Crowie13: You're actually raising interesting point--how people take in information. About 70% of the population prefers getting their information with some visual component that extends or reinforces what the words say. Among programmers, not surprisingly I guess, those with visual preferences are underrepresented compared to the general population. So I omitted a class diagram that didn't occur to me but, as soon as Anonymous4497 saw it, he thought how much clearer the document would be with a diagram. The variety of the human mind is really astounding.
This isn't too word oriented. There are bucket loads of code and simple demonstrations of patterns, but this is an excellent clear explanation of reasoning which I've found very lacking in material on patterns. I initially thought it was too word oriented too. But when I read it I found I was learning more and more about my options for implementing code through the explanation of the reasoning. Cheers from a young Java developer
Bill: the first thing to note is that I'm not actually interested in the orderline problem--I'm interproblemested in how to support the sales options. But, even in the orderline problem: If every implementation you've seen in 20 years has been excellent, I think you've been very lucky. I've seen any number of implementations in "write only" code that couldn't be extended, was hard to understand, and resistant to automated testing. I suspect that the version you saw 20 years ago wasn't written with objects in mind. But (good news!) the next column has the data design.
This is by far the worst design patterns article i've ever read, esp bad for the first one in a column. No insight whatsoever has been gained by reading it. For classic problems such as product/order/orderline/shipping, isn't it a done deal? I recall seeing the same problem solved back in SQL Server 6.5 days. And shouldn't you start with the design of the data end instead? In organizations that I've worked in, this is such a typical and well-known business problem, and people always start by working out the database structures, and add auxilliary stored procs, and some business objects on top of that. No one worried about design patterns and no issue at all in maintaining it over 20 years. Recently I've seen a trend on MSDN where trivial issues like this are bandied around to promote certain "best practices", and vague benefits such as long term savings are used to convince people of how great it is. For me, these are mere common sense and the complexity lies with carefully analyzing the true business needs rather than spending a million hours tweaking a design. I guess the industry of developing business apps has matured so much that it has become harder and harder to find more interesting topics.
Anonymous4497: Having a class diagram for the part of the application that isn't the problem would have been a good idea, alright--the class diagram for the part of the application that is part of the problem will appear in the column that covers the class design. I hadn't thought of including a graphic (too word-oriented, I guess). I'll do that next time (assuming that there is part of the application that isn't part of the problem).
It would have been great, if there was a class diagram which explained the problem under section "Managing Products in Orders: Non-Solutions". For laymen like me, i understood it after reading it three times
perhaps10: Embarrassingly enough, I didn't even consider chain of responsibility! However, the roles are independent of each other with side effects that have nothing to do with each other, rather than forming a chain of processing to accomplish some end. So, while (like the decorator pattern) chain or responsibility might be appropriate for calculating price, I don't see it as a solution for handling sales options. I find it difficult to draw a bright line between design and implementation (that may be a failing). I find that when I start thinking about the implementation, I discover issues with my design that cause me to reconsider my design. Similarly, at the design stage I find myself considering various "practicalities" (e.g. how much data are we talking about here? how many people will be using this?) that alter my design choices. So I consider design and implementation a continuum that I cycle through rather than a set of stages, each of which I abandon after completing. Much of the distinction may be semantics: Is deciding on the table design, a design issue or an implementation issue? For me it falls into that area between the two. The roles pattern isn't all that new a pattern--I was surprised when I went to do my research for the column (research: making sure I didn't say anything spectacularly stupid) that it isn't in the original GoF book. There's certainly a lot of literature about it now (including some discussion by Gamma). And the problem is, as you say, trivial. My goal in this column is to take typical problems developers face every day and work through solving them in a maintainable, extendable, understandable, TDD kind of way (which are what patterns are supposed to promote).
Jaco: I'm probably always going to shortchange the description of the problem for fear of making the column too long and unwieldy (and the assumption that working through the implementation will--eventually--clarify the design space). Also, of course, the problem is perfectly obvious to me. It's only when the comments start flowing in that I'll realize that I've left out information that would make the problem obvious to others. As you'll see (the next column will have the data design), I do end up storing the options against the order/orderline (and validating against two other tables). But I don't think that it makes sense for the object model to follow the table design here. I hadn't considered either using extension methods! I'm not comfortable with extension methods because I don't think that I can count on all the functionality required by a Sales Option to boil down to a single method call that returns a value or performs some update. For instance, at the order taking stage, picking the giftwrapping option changes the workflow so that the user has to go through the "select your wrapping" page/form. This option requires an object that can be used to alter the workflow. I'm not sure that I understand the issue with storing business logic in the model--obviously, the business logic has to go in some class somewhere. I'm taking you to mean that all the business logic shouldn't go into classes that are used in the UI and I certainly agree with that. The Roles objects involved will vary from one part of the process to another. On calculating Totals--I'm of two minds on storing those. If you store totals, for instance, then you have information stored in two place: in the individual items (which can be added up) and in the total itself. When information is stored in two places, it will (eventually) disagree. So I'd prefer not to store that information except for two cases (1) where the processing is so time consuming, it can't be done at run time and (2) in the data warehouse where it reflect past data. While we may disagree on storing totals I suspect we agree on having the logic that calculates the results in objects that are used throughout the organization and can be called when needed--which is what the Roles pattern will let me do.
Interesting. The starting point is a rather trivial problem, solved better or worse in trillions of running applications. The question is, which pattern applies best, right?. IMO the beauty and usability of pattern approach gets lost when there are too many patterns (invented on the fly?). "Roles pattern"? Is it a new name of old-good polymorphism/inheritance? My first, rather intuitive thought was: Chain Of Responsibility should do the work. It is dynamic, allowing many handlers to do their part and protects Separation Of Concerns principle, where every handler taker care of one (business) issue. Practical implementation you can find in .NET Remoting Stack and many, many others. I'd love to leave all the implementation details out of scope at the design stage as they rather blur the problem, as stated above.
In principle I agree with Max Daniels on this one. Why for the love of all things good and pure would you embed business logic in this model? I think we might understand your proposed architecture better if you elaborate a bit more on the problem space. What are the boundaries? Who are the players? Given that my understanding of the problem scope is limited: I would also validate against product->options. I would however store against salesorder->line->options. I would separate Processing off into a separate assembly and implement it using Extension Methods. This way the Processing logic is centrally located and easily maintained, the model objects remain lightweight and devoid of business logic and the extension library can also be used in other modules of the application. The last thing you want with any solution that has financial ramifications are “smart” object that are able to manipulate their own values? Prices, Discount, Tax and Totals are calculated and stored. Never recalculated on the fly!
Max: Well, let me work through my view of the process. At this point in the process, the person working with the customer to take the order (or the person placing the order in a self-serve environment). At this point, we're simply adding the options. Later in the process, as you say, different systems will react to those sales options (for instance, the shipping system will probably react to the gift wrapping option); costing will bill the user for expediting. However, even at this stage, the product is going to need some functionality added to it as part of taking the option (for instance, to edit the gift wrapping options that are selected as part of taking the order). That functionality may well be created by the shipping system team (and be held in the shipping team's DLLs) but it needs to be used in the order taking application. Focusing in on the cost side: If the user chooses the option have this product expedited, we'll have to present the user with a cost for that when the option is selected, as part of the order-taking process. What we both want to avoid is embedding that code in the order-taking application--we want the order-taking operation to be able to access the necessary functionality to give the user a cost estimate. So, the order-taking operation needs to add on the functionality without embedding that functionality in the order-taking operation (and it's not hard to imagine that other systems might need some of this functionality).
TBH I dont agree with either one. This is basically a works order - operations - performed on a product being sold. You would have to record details at sales level, operation state etc for the order. Each operation progress being part of the despatch process, eg picking, packing, loading etc. I would never ever mix pricing, discounts etc at product level. How would you allow for sales history ? accounting ? costings ? price changes ? customer elligibility ? You could validate against product/operations, store against sales/operations and process by a totally seperate central procedure. If you dont do this then when it comes to crediting an order or amending an order you are in a world of hurt. In the real world you would have a whole seperate works order system which would allow different operations by an activation date. Otherwise new services/department/events could not be catered for. Complete with a costing system attached. Never mind an accountant trying to narrow down costings against sales. I find it really strange how you are both talking about putting functionality in what is static and moving data classes.
Malhotra: You make a good point--presumably the OrderLine could be responsible for the SalesOptions that are applied to it. If the SalesOptions were independent of the Product--if they really were just conditions applied as part of the sales then I think you're exactly right: it should be the OrderLine that manages the process. However, in the business case that I'm building on here (this is from one of my clients) the SalesOptions were dependent on the Product. For instance, the company sold both "things" and "services"--you can have the things gift wrapped but not the services; expediting a thing means you get it delivered earlier/prioritizing a service means that we put more people on it and it gets done faster. So that's why I'm currently suggesting that the roles are related to the Product. I can still be talked out of this...
Thanks for starting this interesting series. Very good explanation of the decisions made and associated reasoning. IMHO, client (OrderLine class) will know what SalesOptions have to be applied so it should create those and pass a collection of those to the Product class instead of Product taking up creating SalesOptions. Another point I'd like to make is that with the assumption that SalesOptions needs access to private data of Product class leads to a circular dependency between these classes. Not sure how can that be avoided !!
More MSDN Magazine Blog entries >
Browse All MSDN Magazines
Subscribe to MSDN Flash newsletter
Receive the MSDN Flash e-mail newsletter every other week, with news and information personalized to your interests and areas of focus.