2

I'm creating the UI for a "Point of Sale" type page. You can add orders, grouped orders, and custom items to your transaction, and it should display these in a list so you can see what you have selected.

They all have the same basic details:

  • Type
  • Description
  • Price

But they also have major differences. E.g.

  • Orders must show the items they contain
  • Grouped orders (which are orders containing other others) must show the orders they contain
  • Custom items also have a quantity that must be displayed

I'm struggling to find an elegant way to program this without just explicitly checking the type of each object.

This is pseudo-code for what I have currently:

foreach (ITransactionItem item in transaction.AllItems.OrderBy(x => x.AddedDateTime))
{
    <text>{item.Description}</text>
    <text>{item.Price}</text>
    <div>
        if (item is TransactionOrder)
        {
            // Show order items
        }
        else if (item is TransactionGroupOrder)
        {
            // Show child orders
        }
        else if (item is TransactionCustomItem)
        {
            // Show item quantity
        }
    <div>
}

I basically created an interface for the shared details, and then added everything into one list of this interface.

The big "if else" just feels wrong and I'm sure there is a better solution, but I can't think of it or find anything from searching. I can't delegate the display logic to the classes themselves because they are from a class library (they can't contain UI code). I also can't just loop over each type separately because they need to be ordered by when they were added to the transaction.

Any ideas or design patterns I'm missing? I'm writing this in Blazor if it has any relevance.

3
  • 2
    Why not use a display template for the part that varies? Have the framework do the type checking for you. Jan 11 at 12:55
  • Display templates sound good - how do they match the type? Can you pass a variable of an interface type, and have DisplayForModel() select the template that matches the concrete type?
    – pjc50
    Jan 11 at 15:03
  • @GregBurghardt Blazor doesn't support display templates unfortunately (as far as I know)
    – Conman_123
    Jan 12 at 1:24

3 Answers 3

3

If the TransactionOrder/TransactionGroupOrder/etc. classes can be modified, but you just are not supposed to add UI-specific stuff to them, then you could use the Visitor Pattern.

Changes to the classes in the class library would be needed to support the infrastructure of the Visitor Pattern, but that infrastructure is not UI-specific.

Within the UI component, you then create a visitor class that can visit all transaction items and build the UI form from each.

3

If you have just 3 (or 4 or 5) cases, an if-else cascade might be the simplest and most readable solution. In my opinion, you should extract the case-specific code into separate methods so that the cascade stays compact, though, but that's a judgement call.

Otherwise adding the necessary code to implement the visitor pattern as suggested by Bart would likely work well, especially when there are other similar places in the UI (for example, when the user should be able to interact with the items somehow).

3

Displaying a list of similar but different objects without resorting to type checking

This looks like a job for polymorphism!

But they also have major differences.

No they don't. When told to display what they contain they should display what they contain. Whatever different things they contain is for them to deal with. What needs to be uniform is how they display. Not what.

I'm struggling to find an elegant way to program this without just explicitly checking the type of each object.

foreach (ITransactionItem item in transaction.AllItems.OrderBy(x => x.AddedDateTime))
{
    <text>{item.Description}</text>
    <text>{item.Price}</text>
    <div>
    item.display(whateverUIPresenter); 
    <div>
}

With this the item can pass the presenter what it needs formatted. The item doesn't care what UI this is. It just sends the required info over some shared interface.

Done this way type specific differences live in the types.

I recently dealt with a very similar issue at the architectural level. If you're wondering where the presenter interface should live see this.

3
  • I can't add the display logic into the actual ITransactionItem classes because they are from the "Business logic" layer so shouldn't (and actually can't in my case) contain UI code. Unless I'm mistaking how the item.display(whateverUIPresenter line works? I haven't heard of Presenters before.
    – Conman_123
    Jan 12 at 1:18
  • You wouldn’t be adding UI code to items. Just passing a reference that the item can use. The item will tell the presenter what to present. The presenter will figure out how to present it. The code is separated. Jan 12 at 4:16
  • Ah so it's similar to the visitor pattern, where the presenter interface has a different method for each type, and each item chooses which method on the Presenter interface to call?
    – Conman_123
    Jan 14 at 23:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.