Welcome!

.NET Authors: Lee Novak, Liz McMillan, Mark O'Neill, Peter Silva, Yakov Werde

Related Topics: .NET

.NET: Article

Implementing Patterns with Generics

It's more than just collections

There's only one difficulty with design patterns in general: Patterns reuse design elements not code elements. To save time I want to be able to reuse both. Some patterns don't require generics to create a reusable coding solution. For example, the INotifyPropertyChanged interface in the 2.0 .NET Framework provides the hooks for the Observer pattern. If you implement the INotifyPropertyChanged interface on any class, it can easily serve as the Subject in the Observer pattern. (See Design Patterns for more details).

The Proxy pattern provides a wrapper around objects that are expensive to create or keep in memory. Until the item is needed, the proxy serves as a placeholder for that item. Once the application requests the expensive object, the proxy creates and initializes the target. In the sample application, you'll use the proxy pattern to wrap the images before they're loaded. The CollageImage class already uses two different concrete proxy implementations: one for the full image and one for the thumbnail. It's a bit more complicated to create a generic proxy, but the payoffs are larger. In practice, creating generic classes will often require this amount of thinking to get to the point where you can simply reuse an implementation.

Creating Generic Implementations
The design pattern for a proxy is simple: your class contains a wrapper for an expensive resource, and that expensive resource is loaded (or initialized) on-demand.

The specific implementation for images is simple. The CollageImage.loadImage method already does just that:

private void loadImage()
{
      if ( theImage == null )
        theImage = new Bitmap(path);
}

If the embedded image hasn't been created, it's loaded from disk and then we keep the image around in memory. The thumbnail image has a slightly different specific implementation of the proxy. In this case, the thumbnail is created from the full size image.

You can build the generic class that will let you create any expensive object from an arbitrary set of parameters. Each time you reuse it, you have to write one new method: the method that initializes the expensive resource from its parameters. The pathway from a specific implementation to the generic class takes two steps. The first step is to separate any specific algorithms for the general algorithms. Once that is done, you turn the general algorithms into the generic class. Finally, you use the specific implementations as the type parameters of the generic class.

We'll start by creating an Image Proxy implementation that separates the specific implementation of loading an image from the general Proxy code. The one obvious method specific to an Image Proxy is the code to load the image from a file name. It's a simple one-line method:

public static Image Reconstitute(string imagePath)
{
      return new Bitmap(imagePath);
}

The ImageProxy class uses this method as a delegate to load the image. (See Listing 4.) Let's look at the internals of the ImageProxy class and see how we can modify it to be a generic implementation:

  • It stores three bits of information: The target image, the path to the target image, and a reference to the delegate that creates an image from the path.
  • It's got two public methods: the constructor and a public property to retrieve the target. Notice that anything specific to the Image class is encapsulated in the Reconstitute method.
  • The reason to factor out the Reconstitute method into a different class and use a delegate is that a delegate provides the best method to separate the general logic from the specific logic that loads the target image.
Our next step is to replace the specific types used in the ImageProxy class with generic type parameters. To produce the generic proxy class, you'll need two type parameters: one to represent the target (an Image) and a second to represent the arguments need to create the image (the string representing the file). Notice that if your target requires multiple parameters to initialize, you can package those parameters into a struct or class and still use the same proxy.

A little simple refactoring on the ImageProxy class gives us Listing 5. To instantiate the generic image proxy, you declare the proper variable type:

private Proxy<Image,string> theImage;

And then instantiate it with the proper variables:

theImage = new Proxy<Image, string>
    (path, new Proxy<Image,string>.proxyLoader
    (ImageBuilder.Reconstitute));

You can use this proxy class to generate the proxy for the thumbnail image. There are a couple different ways to build the thumbnail for the images. You can build the Thumbnail from the instantiation of the image proxy. That's got a couple of disadvantages. The first is that the code is somewhat more complicated:

private Proxy<Image,Proxy<Image, string> >
thumbNail;
thumbNail = new Proxy<Image,
Proxy<Image, string>>
(theImage, new Proxy<Image,
Proxy<Image, string>>.proxyLoader
(ThumbnailBuilder.Create-Thumbnail));

I show this just so you can see that there's nothing special about the types used to build the thumbnails. You can use any types that satisfy the declared constraints, including other generics to instantiate a generic class.

The second issue is more important. You would load all the full-size images just to view all the thumbnails. It's likely (at least in this application) that a user will want to browse through the thumbnails before loading the full image. So, let's build a different form of the thumbnail proxy: one that creates the thumbnail from the image on disk and discards the full-size image. That just takes a different method for the delegate:

public static Image LoadThumbnail(string src)
{
    using (Image fullSize = new Bitmap(src))
      return fullSize.GetThumbnailImage(150, 150,
        new Image.GetThumbnailImageAbort(uselessDelegate),
        IntPtr.Zero);
}

Once this is in place, the user can browse for images and the proxy will create the thumbnail and discard the full image. The full image is only used in memory when the user has added it to his or her current collage.

Conclusion
In some sense, generics suffer from being so well suited to collections: that's almost the only use case most people ever see. But, generics are really a general-purpose tool that can be used for many different functions in your applications. Sometimes you'll create generic classes to implement common bits of logic, other times you can use generics to provide the mechanism to collaborate between objects of different types. It's a little more work to create these reusable blocks, but the payoff is more than worth it: more functionality, without an increase in code to maintain. Learn to see where these opportunities exist, and create the code to match. The payback is immediate and continues to grow. You'll find that once you have even a small library of generic classes, you'll reuse them often and save even more time. And well-designed generic classes can be used in almost countless ways. I've shown you how to spot specific instances of algorithms that could become generic classes and how to modify those algorithms to match the syntax necessary to create generic classes.

More Stories By Bill Wagner

A frequent writer and speaker, Bill's work is published in ASP.NET Pro and Visual Studio Magazine. He is the author of C# Core Language Little Black Book and Effective C#. You can reach Bill at wwagner@srtsolutions.com.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.