Reputation: 593
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.
Upvotes: 1
Views: 367
Reputation: 625
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);
}
}
}
Upvotes: 1
Reputation: 24134
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.
Upvotes: 0
Reputation: 107277
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
.
Upvotes: 2
Reputation: 29186
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.
Upvotes: 0