Composite (GoF) and Other Design Principles - UML

To raise yet another interesting requirements and design problem: How do we handle the case of multiple, conflicting pricing policies? For example, suppose a store has the following policies in effect today (Monday):

  • 20% senior discount policy
  • preferred customer discount of 15% off sales over $400
  • on Monday, there is $50 off purchases over $500
  • buy 1 case of Darjeeling tea, get 15% discount off of everything

Suppose a senior who is also a preferred customer buys 1 case of Darjeeling tea, and $600 of veggieburgers (clearly an enthusiastic vegetarian who loves chai). What pricing policy should be applied?

To clarify: There are now pricing strategies that attach to the sale by virtue of three factors:

  1. time period (Monday)
  2. customer type (senior)
  3. a particular line item product (Darjeeling tea)

Another point of clarification: Three of the four example policies are really just "percentage discount" strategies, which simplifies our view of the problem.

Part of the answer to this problem requires defining the store's conflict resolution strategy. Usually, a store applies the "best for the customer" (lowest price) conflict resolution strategy, but this is not required, and it could change. For example, during a difficult financial period, the store may have to use a "highest price" conflict resolution strategy.

The first point to note is that there can exist multiple co - existing strategies, that is, one sale may have several pricing strategies. Another point to note is that a pricing strategy can be related to the type of customer (for example, a senior). This has creation design implications: The customer type must be known by the StrategyFactory at the time of creation of a pricing strategy for the customer.

Similarly, a pricing strategy can be related to the type of product being bought (for example, Darjeeling tea). This likewise has creation design implications: The ProductDescription must be known by the StrategyFactory at the time of creation of a pricing strategy influenced by the product.

Is there a way to change the design so that the Sale object does not know if it is dealing with one or many pricing strategies, and also offer a design for the conflict resolution? Yes, with the Composite pattern.

  • Name : Composite
  • Problem : How to treat a group or composition structure of objects the same way (polymorphically) as a non - composite (atomic) object?
  • Solution : Define classes for composite and atomic objects so that they implement the same interface.

For example, a new class called CompositeBestForCustomerPricingStrategy (well, at least it's descriptive) can implement the ISalesPricingStrategy and itself contain other ISalesPricingStrategy objects. Figure explains the design idea in detail.

Observe that in this design, the composite classes such as CompositeBest - For
CustomerPricingStrategy
inherit an attribute pricingStrategies that contains a list of more ISalePricingStrategy objects. This is a signature feature of a composite object: The outer composite object contains a list of inner objects, and both the outer and inner objects implement the same interface. That is, the composite class itself implements the ISalePricingStrategy interface.

Thus, we can attach either a composite CompositeBestForCustomerPricingStrategy object (which contains other strategies inside of it) or an atomic PercentDiscountPricingStrategy object to the Sale object, and the Sale does not know or care if its pricing strategy is an atomic or composite strategy - it looks the same to the Sale object. It is just another object that implements the ISalePricingStrategy interface and understands the getTotal message.

Figure 26.14 The Composite pattern

Figure 26.15 Collaboration with a Composite

UML - In Figure, please note a way to indicate objects that implement an interface, when we don't care to specify the exact implementation class.

To clarify with some sample code in Java, the CompositePricingStrategy and one of its subclasses are defined as follows:

//superclass so all subclasses can inherit a List ofstrategies
publicabstractclass CompositePricingStrategy
implementsISalePricingStrategy {
protected List strategies= new ArrayListO;
public add(ISalePricingStrategy s)
strategies.add(s );
public abstract Money getTotal(Salesale);
} //end ofclass
// a Composite Strategy that returns the lowest total // of its inner SalePricingStrategies
public class CompositeBestForCustomerPricingStrategyextends CompositePricingStrategy
{
public Money getTotal( Sale sale )
Money lowestTotal = new Money(Integer.MAX VALUE );
// iterate over all the inner strategies
for( Iterator i = strategies.iterator(); i.hasNext(); ) {
ISalePricingStrategy strategy =
(ISalePricingStrategy)i.next(); Money total = strategy.getTotal< bale);
lowestTotal = total.min( lowestTotal ); } return lowestTotal; }
} // end of class

Figure 26.16 Abstract superclasses, abstract methods, and inheritance in theUML

Creating Multiple SalePricirigStrategies

With the Composite pattern, we have made a group of multiple (and conflic pricing strategies look to the Sale object like a single pricing strategy. The opposite object that contains the group also implements the ISalePricingStratinterface. The more challenging (and interesting) part of this design problem When do we create these strategies?

A desirable design will start by creating a Composite that contains the present moment's store discount policy (which could be set to 0% discount if none is active), such as some PercentageDiscountPricingStrategy. Then, if at a later step in the scenario, another pricing strategy is discovered to also apply (such as senior discount), it will be easy to add it to the composite, using the inherited CompositePricingStrategy. add method.

There are three points in the scenario where pricing strategies may be added to the composite:

  1. Current store - defined discount, added when the sale is created.
  2. Customer type discount, added when the customer type is communicated to thePOS.
  3. Product type discount (if bought Darjeeling tea, 15% off the overall sale), added when the product is entered to the sale.

The design of the first case is shown in Figure. As in the original design discussed earlier, the strategy class name to instantiate could be read as a system. property, and a percentage value could be read from an external data store.

Figure 26.17 Creating a composite strategy

For the second case of a customer type discount, first recall the use case extension which previously recognized this requirement:

Use Case UC1: Process Sale

Extensions (or Alternative Flows):

Customer says they are eligible for a discount (e.g., employee, preferred customer)

  1. Cashier signals discount request.
  2. Cashier enters Customer identification.
  3. System presents discount total, based on discount rules.

This indicates a new system operation on the POS system, in addition to make - NewSale, enterltem, endSale, and makePayment. We will call this fifth system operation enterCustomerForDiscount; it may optionally occur after the endSaleoperation. It implies that some form of customer identification will have to come in through the user interface, the customerlD. Perhaps it can be captured from a card reader, or via the keyboard.

The design of the second case is shown in Figures. Not surprisingly, the factory object is responsible for the creation of the additional pricing strategy.

It may make another PercentageDiscountPricingStrategy that represents, for example, a senior discount. But as with the original creation design, the choice of class will be read in as a system property, as will the specific percentage for the customer type, to provide Protected Variations with respect to changing the class or values. Note that by virtue of the Composite pattern, the Sale may have two or three conflicting pricing strategies attached to it. but it continues to look like a single strategy to the Saleobject.

Figure 26.18 Creating the pricing strategy for a customer discount, part 1

UML - Figures show an important UML 2 idea in interaction diagrams: Using the ref and sd frame to relate diagrams.

Considering GRASP and Other Principles in the Design

To review thinking in terms of some basic GRASP patterns: For this second case, why not have the Register send a message to the PricingStrategyFactory, to create this new pricing strategy and then pass it to the Sale?One reason is to support Low Coupling. ThekSaZe is already coupled to the factory; by making the Register also collaborate with it, the coupling in the design would increase. Furthermore, the Sale is the Information Expert that knows its current pricing strategy (which is going to be modified); so by Expert, it is also justified to delegate to the Sale.

Figure 26.19 Creating the pricing strategy for a customer discount, part 2

Observe in the design that customerlD is transformed into a Customer object via the Register asking the Store for a Customer, given an ID. First, it is justifiable to give the getCustomer responsibility to the Store; by Information Expert and the goal of low representational gap, the Store can know all the Customers. And the Register asks the Store, because the Register already has attribute visibility to the Store (from earlier design work); if the Sale had to ask the Store, the Sale would need a reference to the Store, increasing the coupling beyond its current levels, and therefore not supporting Low Coupling.

IDs to Objects

Second, why transformA the customerlD(an "ID" - perhaps a number) into a Customer object? This is a common practice in object design - to transform keys and IDs for things into true objects. This transformation often takes place shortly after an ID or key enters the domain layer of the Design Model from the UI layer. It doesn't have a pattern name, but it could be a candidate for a pattern because it is such a common idiom among experienced object designers - perhaps IDs to Objects.

Why bother? Having a true Customer object that encapsulates a set of information about the customer, and which can have behavior (related to Information Expert, for example), frequently becomes beneficial and flexible as the design grows, even if the designer does not originally perceive a need for a true object and thought instead that a plain number or ID would be sufficient. Note that in the earlier design, the transformation of the itemID into a ProductDescription object is another example of this IDs to Objects pattern.

Pass Aggregate Object as Parameter

Finally, note that in the addCustomerPricingStrategy (s:Sale) message we pass a Sale to the factory, and then the factory turns around and asks for the Customer and PricingStrategy from the Sale.

Why not just extract these two objects from the Sale, and instead pass in the Customer and PricingStrategy to the factory? The answer is another common object design idiom: Avoid extracting child objects out of parent or aggregate objects, and then passing around the child objects. Rather, pass around the aggregate object that contains child objects.

Following this principle increases flexibility, because then the factory can collaborate with the entire Sale in ways we may not have previously anticipated as necessary (which is very common), and as a corollary, it reduces the need to anticipate what the factory object needs; the designer just passes as a parameter the entire Sale, without knowing what more particular objects the factors - may need. Although this idiom does not have a name, it is related to Low Coupling and Protected Variations. Perhaps it could be called the Pass Aggregate Object as Parameter pattern.

Related Patterns

Composite is often used with the Strategy and Command patterns. Composite is based on Polymorphism and provides Protected Variations to a client so that it is not impacted if its related objects are atomic or composite.


All rights reserved © 2018 Wisdom IT Services India Pvt. Ltd DMCA.com Protection Status

UML Topics