Solved

Which design pattern?

Posted on 1998-12-08
18
407 Views
Last Modified: 2010-04-04
I'm trying to develop an object model for a scenario related closely to the one described below.

Imagine an application for displaying video windows containing TV images on a PC monitor.  The video windows are generated by video overlay / inlay devices.  The app should be able to control a range of video devices from
different manufacturers so the type of video device will not be known at compile time.  In addition the app could potentially have to control more than one device simultaneously so that the user could have 2 or more video
windows visible at any time.  We can assume that all of these devices have some core funtionality, such as the ability to display different TV channels and to set the size and position of video window.  However, some of the
devices may have additional features such as the ability to freeze-frame.

How the TV channel must be set etc. will vary from device to device but this should be seemless to the client.  Extensibility is also key.  It should be possible to cater for new devices in the future with minimal impact on the
rest of the system.

I've been looking at Gamma et al's 'Design Patterns..." to try and establish an existing object model for this type of problem but with not too much success so far.  From a structural view point I'd considered the Bridge Pattern since that way I could expose a generic VideoWindow class to the client and encapsulate the implementation for different video devices (DeviceA and DeviceB) in separate 'device driver' classes...

TVideoWindow = class (TObject)
   DeviceImp : TVideoDeviceImp;
   SetChannel ();
   SetBounds ();
   SetFreezeFrame ();

TVideoWindowImp = class (TObject)
   DeviceSetChannel ();
   DeviceSetBounds ();
   DeviceSetFreezeFrame ();

T_DeviceA_WindowImp = class (TVideoWindowImp);
   DeviceSetChannel ();
   DeviceSetBounds ();
   DeviceSetFreezeFrame ();

T_DeviceB_WindowImp = class (TVideoWindowImp);
   DeviceSetChannel ();
   DeviceSetBounds ();

In this case the ability to freeze-frame is supposed to be unique to DeviceA, but I can't see how the client can determine that DeviceB does not have this feature.  Is there a better pattern for doing this, i.e. a pattern that allows the client to determine the unique capabilities of sub-classes of some base class?

Also, what's the best way of initialising these classes in the first place. Its seems that the Abstract Factory pattern would only be useful if either DeviceA OR DeviceN were being used at any one time, but my app may have each
device displaying a video window and so it will need instances of each device driver class.

Thanks in advance.
0
Comment
Question by:andrewjackson
  • 7
  • 5
  • 4
  • +1
18 Comments
 
LVL 27

Expert Comment

by:BigRat
Comment Utility
As far as I can see there is no solution for this problem, because you want the functionality to be open ended yet handle all derived objects as the same object. The Factory pattern is ideal for implementing a fixed functionality (or one that varies little over time) and provide vastly differing implementations for it, ie: typical Printer, Monitor or Modem drivers.
   I have an implemetation of a SearchServer where I have an abstract base class which when I add new functionality I add an implementation which says "can't do". My derived classes override this functionality if then can provide an implementation. My Server executes the "SearchObjects" as if they were only base class objects. It is a pain having to continually override EVERY method, so I have arranged my classes in a tree. Certain classes in this tree are still abstract classes, overriding certain methods providing a partial implementation. This enables me to keep duplicated code to a minimum BUT I do not strive for this (it would lead to absurdity if I weren't too careful).
   I keep each Delphi class in its own file - like in Java. And the file name is the same as the class name. This leads to many files, but it is very clear which is which. I too was disappointed that, after receiving the book as a birthday present, I couldn't find a "clever" way of doing it. (I have a Java program where I HAVE used the book, so it wasn't that bad after all)
0
 
LVL 1

Expert Comment

by:AndersWP
Comment Utility
andrew:
I think you should consider a COM / Interface based approach to your design. This will allow your client to determine at runtime the functionality of an instantiated object. You could, e.g. declare a IBasicVideoDevice which defines the basic SetChannel & SetBounds. In addition you define an interface that represents enhanced functionality that may or may not be present in a specific device, e.g. IFreeze. Your concrete 'device drivers' can then implement the enhanced interface or not, as their devices mandate. When you instantiate the device objects, you can use interface querying to determine whether or not a given functionality is present (indicated by support of the corresponding interface). Your client program needs to be aware of all the enhanced functionality, but your 'device drivers' need not.

I hope this will help. If you need more details, feel free to ask.

Regards,
AndersWP


0
 
LVL 1

Accepted Solution

by:
wamoz earned 200 total points
Comment Utility
You don't need a pattern to find out the capabilities of each subclass. Just give the base class a Capabilities list, after the fashion of the ubiquitous Items property, initialise the contents of FItems in the constructor, and add any further capabilities to FItems in each subclass's constructor. Then if you want to know what an object can do, you need only ask it.

As for your need to vary dynamically the class that your factory creates, look up "class of" in the Delphi help. You can create a type that is an instance of the *class* object. Thus

    type
      TCustomWidget = class
        .
        .
        .
        end;
      TClassOfCustomWidget = class of TCustomWidget;
      TFramework = class
        private
          FItems:FList;
          FClassToMake:TClassOfCustomWidget;
        public
          procedure Add;
          property ClassToMake:TClassOfCustomWidget
            read FClassToMake
            write FClassToMake;
          .
          .
          .
        end;

    var
      aFramework:TFramework;

    procedure TFramework.Add;
      begin
      FItems.Add(FClassToMake.Create);
      end;
 
And somewhere in your code you can do this:

    aFramework.ClassToMake := TSomeOtherWidget;
    aFramework.Add;

You could make it even simpler and just pass the class as a parameter to the Add method.

    type
      TCustomWidget = class
        .
        .
        .
        end;
      TClassOfCustomWidget = class of TCustomWidget;
      TFramework = class
        private
          FItems:FList;
        public
          procedure Add(ClassToMake:TClassOfCustomWidget);
          .
          .
          .
        end;

    var
      aFramework:TFramework;

    procedure TFramework.Add(ClassToMake:TClassOfCustomWidget);
      begin
      FItems.Add(ClassToMake.Create);
      end;

Which of these approaches is best depends on whether your code will manufacture a great number of the same class of object in one pass (eg spreadsheet cell objects). In this case it may be worth the trouble of setting up to pass the class independently of the method call.
0
 
LVL 27

Expert Comment

by:BigRat
Comment Utility
If I may be allowed to add a small comment to the other two proposals. They are both good in their own right. But one of the problems with Delphi is name polution. There is no heirarchy of names like in Java, which means that one proliferates files. One can of course put more than one class in a file. But I then loose track of what is where. One can of course uses aliases in use lists, but that's also not a real solution. I objected to this in Turbo Pascal as my uses lists were becoming very long. Mind you, it's far better than C++.
   Using interfaces or capabilities has the major advantage that the feature is separated from the object. Which means that you have a collection of features and a collection of objects (not physically of course). If this is managable then its fine.
0
 

Author Comment

by:andrewjackson
Comment Utility
Yes I liked the interface solution because although the code will be written in Delphi, I want to make the object model as langauge independant as possible (that's why I'd considered patterns originally).

It also means that each device driver could potentially be created in Delphi and extracted out of my client app as a separate COM component.  This means that the client app could also be extended in the future to use new drivers not written in Delphi as long as they support the same COM interfaces.

Andrew
0
 
LVL 1

Expert Comment

by:wamoz
Comment Utility
Just some afterthoughts:

The solution I proposed is actually specifically described in Design Patterns (DP) on page 112, in the Implementation section for Factory Method creational pattern:

"An even more flexible approach akin to parameterised factory methods is to store the class to be created as a class variable of Application. That way you don't have to subclass Application to vary the product."

The last sentence of this quote is a succinct explanation of why I favour this approach over all others - and one of the reasons I love Delphi. Few languages support it so directly.

About interfaces: they are syntactic sugar. They are functionally equivalent to a completely abstract class. If this isn't rampant namespace pollution I don't know what is.

That's not to say that I object to them; they are merely irrelevant (IMHO).

About COM: it is merely a convention for a implementing a subset of the patterns elucidated in DP. AndersWP would do well to acquire DP and read it thoroughly. Though his proposed solution was solid it lacks a theoretical basis. It's one thing to be right, and another thing to know *why* one is right, and to be able to prove it. Get the book, Anders, and go from being pretty good to really great! (I like DP, can you tell? <g>)
0
 
LVL 1

Expert Comment

by:AndersWP
Comment Utility
"Get the book, Anders, and go from being pretty good to really great! (I like DP, can you tell? <g>)". I must say I find your statement rather patronizing - just for the record I actually *have* "read it thoroughly".

I think the interface approach is in fact better than the capabilities list, for (among others) the following reason: Suppose that you have a set of functionality groups; in this video context it could be Basic Video, FreezeFrame, CromoKeying, ... Now declare an interface for each functionality group (IBasicVideo, IFreezeFrame, ICromoKeying, ...). You are now free to make 'device drivers' that implement various subsets of these function groups. As long as you do not introduce new function groups, your client application does not need to be modified!
If, on the other hand, you use capability lists it seems to me that the client that instantiates the 'device drivers' needs explicit knowledge of all defined 'device driver' classes - in other words, each time you make a new 'device driver' you need to modify your client. This again means that you have a stronger coupling between your objects, which is a sign of a weak architecture.
So I do not think it fair to call interfaces 'irrelevant' and 'syntactic sugar'. They are in my opinion a clever way of doing safe, hierarchy independant dynamic type casting, and a good way of realising implentation/information hiding.

Regards,
AndersWP

0
 
LVL 1

Expert Comment

by:wamoz
Comment Utility
Andrew - it wasn't intended to be patronising. I thought your answer was pretty good. If I erred by assuming that you hadn't read DP, it was for two reasons:

- a tragic number of talented software designers haven't
- you couched your response in terms of a particular and proprietary technology

OK, I was wrong. It happens a lot. Sorry if I ruffled your feathers. Didn't mean to.
----------------
Peter R-W.
0
 
LVL 1

Expert Comment

by:AndersWP
Comment Utility
Well, no harm done. Anyway, the focus should be on solving andrewjackson's problem; we should not turn it into a litterary contest.

Regards,
AndersWP


0
How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

 
LVL 1

Expert Comment

by:wamoz
Comment Utility
Anders, about the coupling you mentioned -

There's no requirement for knowledge of descendant classes due to the way Delphi resolves method calls.

It uses the RTTI of the *object*, not the type of the variable referring to the object. Since it is possible to refer to instances of descendant classes with variables typed as the base class, knowledge of descendent types is not required in code - only of the base class - and the coupling problem you anticipate does not eventuate.

I've found this fact very very handy over the years, as you can imagine.
0
 
LVL 1

Expert Comment

by:AndersWP
Comment Utility
I am sorry, but I still do not quite get it.

Let us say that andrewjackson implements 'device drivers' for, say, 10 different devices using your proposed design. He would then have 10 classes: TWidgetClass1, TWidgetClass2,... right? And as far as I can see all these classes would have to be known in the client object that instantiates them, and every time he adds a new TWidgetClassX, he has to modify the client object as well to include the new class identifier in order to be able to instantiate it. Furthermore, code as to be added to the client to allow it to use the new class, something like

If MyWidget Is TWidgetClassX Then
  With TWidgetClassX(MyWidget) Do
     FreezeFrame;

With an interface based approach he would only have to modify his client when a new feature set is introduced, not every time a new concrete device is implemented.

Or maybe I have not understood the idea behind you proposed solution?

Regards,
AndersWP
0
 

Author Comment

by:andrewjackson
Comment Utility
Anders

I think you're correct in terms of how you understand objects to be instantiated, but the type checking you've shown would not be required when using the instantiated object.  I would expect all of the concrete (TWidgetClass1, TWidgetClass2 etc.) class to inherit from an abstract TCustomWidgetClass.  So the client would declare the object as -  MyWidget : TCustomWidgetClass

All functionality of these classes would be accessible via virtual methods so this would suffice:

MyWidget.FreezeFrame

Even if, as I think you are suggesting, the drivers for all Widgets were implemented as separate COM objects (i.e. DLLs, OCXs etc) surely there would still need to be wrapper class in the client for each driver.  So TWidgetClass1, TWidgetClass2 etc would effectively be wrappers for Widget1.DLL, Widget2.DLL etc.  To support a driver for new WidgetX you'd still need to write wrapper for it.

Nevertheless, the advantage to me of this approach is that its only the wrapper that would need to be added to the client which is a less involved modification.

Andrew
0
 

Author Comment

by:andrewjackson
Comment Utility
Anders

I think you're correct in terms of how you understand objects to be instantiated, but the type checking you've shown would not be required when using the instantiated object.  I would expect all of the concrete (TWidgetClass1, TWidgetClass2 etc.) class to inherit from an abstract TCustomWidgetClass.  So the client would declare the object as -  MyWidget : TCustomWidgetClass

All functionality of these classes would be accessible via virtual methods so this would suffice:

MyWidget.FreezeFrame

Even if, as I think you are suggesting, the drivers for all Widgets were implemented as separate COM objects (i.e. DLLs, OCXs etc) surely there would still need to be wrapper class in the client for each driver.  So TWidgetClass1, TWidgetClass2 etc would effectively be wrappers for Widget1.DLL, Widget2.DLL etc.  To support a driver for new WidgetX you'd still need to write wrapper for it.

Nevertheless, the advantage to me of this approach is that its only the wrapper that would need to be added to the client which is a less involved modification.

Andrew
0
 
LVL 1

Expert Comment

by:AndersWP
Comment Utility
No, actually you do not need a wrapper class in your client - this is what you use the interfaces for. What you need is the name and path of the OCX implementing the servers and the name of the object server within the OCX. This is stored in the registry, and is therefore not encoded in your client. In addition you need to define a set of interfaces representing the functionality groups you want to implement. The definitions of these must be available to your client, e.g. via a type library, and your client must be aware of the functionality they represent. But you need no information relating to a specific server hardcoded in you client in the form of wrappers or otherwise. All you need is awareness of the interfaces that the servers may or may not implement.

Regards,
AndersWP


0
 

Author Comment

by:andrewjackson
Comment Utility
I'm still not very hot on how to work with COM in Delphi so I apologise if I'm missing something here, but surely to instantiate a COM object you must know its Interface and Class GUIDS...

function CreateComObject(const ClassID : TGUID) : IUnknown;

When I refer to 'wrapper' I mean just mean a class that encapsulates these GUIDS.

Andrew
0
 
LVL 1

Expert Comment

by:AndersWP
Comment Utility
Well, you are partially right. You do need to hardcode the interface information into your client. The class IDs you can store in the registry (or a configuration file if you prefer that) and read them at run time.

Regards,
AndersWP
0
 
LVL 1

Expert Comment

by:wamoz
Comment Utility
Anders, sorry I took so long to pick up the thread again, been busy.

You wrote:
If MyWidget Is TWidgetClassX Then
  With TWidgetClassX(MyWidget) Do
     FreezeFrame;

Actually, no - you don't need to downcast. You can replace the code above with

MyWidget.FreezeFrame;

and let the VMT work out what to call. Yes, this only works for methods introduced in the base class, but the thing is, we were talking progressively overriding a Capabilities method.

It is true that to add capabilities not described in the base class it would be necessary to use another pattern such as decorator, but this does not impact existing code operating in ignorance of the new methods.

Also, I've been thinking about what you said about interfaces, and it occurs to me that they address to some extent the ambiguities otherwise inherent in multiple inheritance. For this reason I hover on the brink of retracting my dismissal of interfaces as syntactic sugar. I still don't think they're strictly *necessary* but if I insisted on omitting the unnecessary we'd all be coding in assembler.


0
 
LVL 1

Expert Comment

by:AndersWP
Comment Utility
wamoz, I believe we have discussed most of what can be said on the subject; the rest will be a matter of taste and habit.

Regards,
AndersWP
0

Featured Post

Highfive Gives IT Their Time Back

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
Hello everybody This Article will show you how to validate number with TEdit control, What's the TEdit control? TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text. Usua…
Excel styles will make formatting consistent and let you apply and change formatting faster. In this tutorial, you'll learn how to use Excel's built-in styles, how to modify styles, and how to create your own. You'll also learn how to use your custo…
This video demonstrates how to create an example email signature rule for a department in a company using CodeTwo Exchange Rules. The signature will be inserted beneath users' latest emails in conversations and will be displayed in users' Sent Items…

763 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now