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.
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.