1

Say I am writing a user interface for a hardware accessory. There are two versions of the accessory - let's say Widget Lite and Widget Pro.

Widget Pro can do everything that Widget Lite can do, but with a few more options and can do a few things that Widget Lite can't. In more detail, Widget Lite has one channel, widget Pro has two, so when it comes to something analogous to volume control, I need only one control for the Lite, but two for the Pro allowing independent control.

In my first attempt at building an application to handle this, I had the class representing Widget Pro extend Widget Lite, but then I've ended up with all sorts of conditional cases to handle the differences which seems ugly. Does anyone know a suitable design pattern to help with such a situation? My imagination is drawing a blank in coming up with synonymous situations that might help in my search.

Jason Evans
  • 28,906
  • 14
  • 90
  • 154
Darran
  • 593
  • 2
  • 13
  • 1
    The LSP violation is crucial in your example. If you can find the place where Lite couldn't be just substituted with Pro, you can't derive Pro from Lite. – Bartek Banachewicz Aug 29 '12 at 09:31

4 Answers4

2

I would start off by looking at the plug in pattern (one of the forms of Dependency Inversion).

Try and abstract an interface common to the Lite and Pro versions, e.g.

interface IBaseWidget
{
   IControl CreateVolumeControl();
   // ... etc
}

In separate assemblies / dlls, implement your Lite and Pro widgets:

class LiteWidget : IBaseWidget
{
   int _countCreated = 0;
   IControl CreateVolumeControl()
   {
       _countCreated++;
       if (_countCreated > 1)
       {
          throw new PleaseBuyTheProVersionException();
       }
   }
}

Since you don't want to distribute the Pro version with the Lite deployment, you will need to load the dlls at run time, e.g. by Convention (e.g. Your base app Looks around for DLL's named *Widget.dll), or by Configuration, which finds the applicable concrete implementation of IBaseWidget. As per @Bartek's comment, you ideally don't want your base engine classfactory to 'know' about specific concrete classes of IBaseWidget.

StuartLC
  • 104,537
  • 17
  • 209
  • 285
1

Visitor pattern might be useful for you. Check dofactory.

Visitor class...

...declares a Visit operation for each class of ConcreteElement in the object structure. The operation's name and signature identifies the class that sends the Visit request to the visitor. That lets the visitor determine the concrete class of the element being visited. Then the visitor can access the elements directly through its particular interface

This is similar to Abstract class implementation as told by Vikdor.

Here is a wiki link for this.

The visitor pattern requires a programming language that supports single dispatch and method overloading.

I have provided a very simple implementation using the visitor pattern for your requirement of different channels and volume settings for WidgetLite and Pro. I have mentioned in comments where the visitor pattern will greatly help you in reducing the if-else calls.

The basic philosophy is that you pass the control (ex. volume) to the widget and it will know how to use it as required. Hence, control object itself has a very simplified implementations. Each widget's code remains together!!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WidgetVisitor
{
    //This is the widget interface. It ensures that each widget type
    //implements a visit functionality for each control. The visit function
    //is overloaded here.
    //The appropriate method is called by checking the parameter and this 
    //avoids the if-then logic elegantly
    public interface Widget
    {
        void visit(Volume vol);
        void visit(Channel chan);
        void Display(AllControls ac);
    }

    //This is the interface which defines the controls. Each control that 
    //inherits this interface needs to define an "accept" method which 
    //calls the appropriate visit function of the right visitor,
    //with the right control parameter passed through its call!
    //This is how the double dispatch works.
    //Double dispatch: A mechanism that dispatches a function call to different concrete 
    //functions depending on the runtime types of two objects involved in the call.
    public interface WidgetControls
    {
        void accept(Widget visitor); 

    }

    //I have implemented the volume control and channel control
    //Notice how the code for defining each control is the SAME
    //in visitor pattern! This is double dispatch in action
    public class Volume : WidgetControls
    {
        public int volLevel { get; set; }
        public int volJazz { get; set; }
        public int volPop { get; set; }
        public void accept(Widget wc)
        {
            wc.visit(this);
        }
    }

    public class Channel : WidgetControls
    {
        public int channelsProvided { get; set; }
        public int premiumChannels { get; set; }
        public void accept(Widget wc)
        {
            wc.visit(this);
        }
    }

    //Widget lite implementation. Notice the accept control implementation
    //in lite and pro.
    //Display function is an illustration on an entry point which calls the
    //other visit functions. This can be replaced by any suitable function(s)
    //of your choice
    public class WidgetLite : Widget
    {
        public void visit(Volume vol)
        {
            Console.WriteLine("Widget Lite: volume level " + vol.volLevel);
        }

        public void visit(Channel cha)
        {
            Console.WriteLine("Widget Lite: Channels provided " + cha.channelsProvided);
        }

        public void Display(AllControls ac)
        {
            foreach (var control in ac.controls)
            {
                control.accept(this);
            }

            Console.ReadKey(true);
        }
    }

    //Widget pro implementation
    public class WidgetPro : Widget
    {
        public void visit(Volume vol)
        {
            Console.WriteLine("Widget Pro: rock volume " + vol.volLevel);
            Console.WriteLine("Widget Pro: jazz volume  " + vol.volJazz);
            Console.WriteLine("Widget Pro: jazz volume  " + vol.volPop);
        }

        public void visit(Channel cha)
        {
            Console.WriteLine("Widget Pro: Channels provided " + cha.channelsProvided);
            Console.WriteLine("Widget Pro: Premium Channels provided " + cha.premiumChannels);
        }

        public void Display(AllControls ac)
        {
            foreach (var control in ac.controls)
            {
                control.accept(this);
            }

            Console.ReadKey(true);
        }
    }

    //This is a public class that holds and defines all the 
    //controls you want to define or operate on for your widgets
    public class AllControls
    {
        public WidgetControls [] controls { get; set; }

        public AllControls(int volTot, int volJazz, int volPop, int channels, int chanPrem)
        {
            controls = new WidgetControls []
            {
                new Volume{volLevel = volTot, volJazz = volJazz, volPop = volPop},
                new Channel{channelsProvided = channels, premiumChannels = chanPrem}
            };
        }
    }

    //finally, main function call
    public class Program
    {
        static void Main(string[] args)
        {
            AllControls centralControl = new AllControls(3, 4, 2, 5, 10);

            WidgetLite wl = new WidgetLite();
            WidgetPro wp = new WidgetPro();

            wl.Display(centralControl);
            wp.Display(centralControl);

        }
    }
}
TheSilverBullet
  • 605
  • 7
  • 21
  • -1 You should explain why it would help him. Give examples and not just quote an arbitrary pattern. (I'll upvote if you fix that) – jgauffin Aug 30 '12 at 06:02
  • @jgauffin, I will update it suitable explanation as soon as I have time! – TheSilverBullet Aug 30 '12 at 09:06
  • @jgauffin, here is a crude explanation. I was referring to the wiki link I have provided in the initial answer for this. I have also referred to [this] (http://stackoverflow.com/questions/2604169/could-someone-in-simple-terms-explain-to-me-the-visitor-patterns-purpose-with-e/2604294#2604294) extremely well written explanation. Please check the second answer for this question. Here, `WidgetLite` and `WidgetPro` are two visitor classes implementing the `visit` function. The controls like volume and channels which `accept` these visitors. – TheSilverBullet Sep 03 '12 at 17:51
  • @Darran, The Visitor pattern is not only a powerful slayer of if-then-else loop, it also allows the person to add more visitors and more acceptors with minimal code shuffle. – TheSilverBullet Sep 03 '12 at 17:52
  • Still. You have to explain why you think that it's a good choice to ***this*** question. You might also want to read my comment on the answer you referred to. – jgauffin Sep 03 '12 at 18:05
  • @jgauffin, Please see the last comment addressed to OP. – TheSilverBullet Sep 04 '12 at 04:27
  • @jgauffin, Updated with an actual implementation and explanation in the comments. Thanks for urging me to complete the implementation. Up-voted or not, I still gained :) – TheSilverBullet Sep 04 '12 at 09:49
0

I would strongly suggest that you have a base Widget class which both Widget Lite and Widget Pro derive from.

public class Widget {}

public class WidgetLite : Widget {}

public class WidgetPro : Widget {}

Have the properties/methods which are shared by both Pro and Lite inside the base class. This is a much cleaner design for you.

Jason Evans
  • 28,906
  • 14
  • 90
  • 154
0

I would do it as follows:

              AbstractWidget (Abstract class)
                   /\
                  /  \
                 /    \
                /      \
               /        \
         WidgetLite   WidgetPro

The common code would go into the AbstractWidget (abstract because, it shouldn't be instantiated) and the behaviour that is different between these two classes would go into the concrete classes.

Vikdor
  • 23,934
  • 10
  • 61
  • 84