Good Old OOA, OOD, OOP Techniques are Economically Sound
I was recently asked a general question on OO (object orientation). The question was: "I have a class with some members that I don't need in a particular child class. How do I hide the unneeded features?"
There are a couple of answers, which I will cover in this article. Answers that are about good old straight forward OO techniques!
Stepping down from soap box, let's get started.
Defining the Problem Space
The problem as defined by my questioner is basically how are members of classes hidden in child classes if they are not needed? You have a potpourri of possibilities:
- Without sounding too flippant, one answer is who cares? If you don't need it don't use it. There is an infinite amount of space in the digital world. (It's like the old joke: "how much does a gigabyte weigh?"
- If you didn't like the first option you'll hate this one even more: OO is hard to produce; the members are probably in the wrong class. (I am not kidding here people these are valid answers)
- You can always add a new member with the same signature in the child class and pretty effectively quash that nasty dead weight (see Listing 1). Hiding members is a bit ugly and polymorphism, reflection, and the DirectCast will actually break this approach
- Or a pretty good answer is to use a façade class. A façade is a class that selectively exposes the elements of another class or classes. (The rest of this article focuses on our friend the Façade pattern.)
Listing 1: Hide an existing member in a parent class by stomping on the signature you'd like to block.
Module Module1 Sub Main() Dim o As HideMyMethod = New IWillHideYou() Dim p As IWillHideYou = New IWillHideYou() o.HideMe() p.HideMe() ' Calls shadowing member in HideMyMethod.HideMe called DirectCast(p, HideMyMethod).HideMe() Console.ReadLine() End Sub End Module Public Class HideMyMethod Public Sub HideMe() Console.WriteLine("HideMyMethod.HideMe Called") End Sub End Class Public Class IWillHideYou Inherits HideMyMethod Public Sub HideMe() Console.WriteLine("IWillHideYou.HideMe Called") End Sub End Class
In Listing 1 o actually calls the base class method—HideMyMethod.HideMe—even though the instance of o is IWillHideYou. The instance p actually calls the child class method, and even though p should call IWillHideYou.HideMe because of the DirectCast HideMyMethod.HideMe is called. And, of course, with reflection the class consumer could circumvent the shadowing of HideMe done by IWillHideYou. (Note: using the same signature in a child class is called shadowing in VB and the Shadows modifier—as in, Public Shadows Sub HideMe()—will eliminate the warning caused by implicit shadowing.
Using the Façade Pattern to Hide Deadweight or Reduce Complexity
Over time classes can end up with too many members to make them easy to use. The general rule of thumb is that a class should have about a half dozen public members and be responsible for one basic kind of solution. What happens in reality is that junk is added to classes over time and eventually those classes become hard to own and hard to use. The next thing that happens is that someone wants to re-write everything. Aside from the fact that one should generally not re-write everything, rather one should refactor things, re-writing is risky business.
|Caveat: Bad OO can become cumbersome to consume even if you produced it (some time ago).|
When code is re-written the likeliest thing to happen is that code that works will be broken. The result is one goes from code that is hard to use and own to code that is just broken. At least with Refactoring code is changed in carefully prescribed ways without changing its observable testable behavior. One such Refactoring is to introduce a façade.
By using a façade you can hide complexity behind the façade making the existing stuff easier to consume while hiding all of the murky complexity. Interestingly enough a façade works with our problem, which is, hiding stuff you don't want to see or use. In Listing 2 another method is added to HideMyMethod. This time a façade is created to expose only the method—the new one—desired.
Listing 2: The façade pattern let's you simplify interfaces by concealing unneeded members.
Module Module1 Sub Main() Dim obj As HideMyMethodFacade = New HideMyMethodFacade() obj.DontHideMe() Console.ReadLine() End Sub End Module Public Class HideMyMethod Public Sub HideMe() Console.WriteLine("HideMyMethod.HideMe Called") End Sub Public Sub DontHideMe() Console.WriteLine("I am available") End Sub End Class Public Class HideMyMethodFacade Private obj As HideMyMethod = New HideMyMethod Public Sub DontHideMe() obj.DontHideMe() End Sub End Class
In Listing 2 HideMyMethod is easier to use because the unneeded methods are hidden behind the HideMyMethodFacade class. The consumer doesn't even have to know HideMyMethod.HideMe exists. By the way WCF services are an implementation of the Façade pattern.
For more on OO design check out my book UML DeMystified from Osborne-McGraw-Hill, check out http://www.dofactory.com, or my blog at http://community.devexpress.com/bloks/paulk.
It is possible to change the exposed public members of a class without breaking the class by using the Façade pattern, which is actually a design pattern and a refactoring. Keep in mind that economic paranoia is no excuse for taking shortcuts or throwing good techniques out the window even though your pointy-headed manager might think so. (I know you aren't taking shortcuts, but someone in the cubicle next to you might be. Tell him or her to quit it.)
I get my ideas for columns from problems I experience or from readers. (I don't generally mention the inspirer unless they provide a good solution.)
About the Author
Paul Kimmel is the VB Today columnist for www.codeguru.com and has written several books on object-oriented programming and .NET. Check out his upcoming book LINQ Unleashed for C# now available on Amazon.com and fine bookstores everywhere. Look for his upcoming book Teach Yourself the ADO.NET Entity Framework in 24 Hours. You may contact him for technology questions at firstname.lastname@example.org.