First, this turned out to be way longer than I had intended. But that's for
my benefit as well as anyone else that is also from a classic VB (non-OO)
background. Composing this reply has really focused my mind on design
issues.
I'm not sure I agree that this is a hack. All that article is doing is
overriding a base method of the form class. It seems very OO to me.
I wouldn't quite call it a hack either. I said it had "hack value", as in
it's an interesting thing to read and experiment with.
and
Just by creating any form you are subclassing the base form and creating
your own class. Why is that bad design? Just curious on your thoughts on
this question.
Subclassing is not bad design. Subclassing arbitrarily to add or change one
feature *can lead to* bad design. For example, suppose I design a subclass
of Windows.Forms.Form that adds this single feature. I would likely wind up
using it as a base from which to build lots of different forms in different
applications:
QueryUnloadForm Inherits Windows.Forms.Form
Then, let's say I want to add another embellishment or feature later on that
I can use independently, and does not make use of this QueryUnload thing.
That would be another subclass of Windows.Forms.Form that I would likely
wind up using as a base from which to derive many other forms in different
applications.
EmbellishedForm Inherits Windows.Forms.Form
Sooner or later, I am going to want to use one of those that ALSO has the
QueryUnload feature. Since we don't have multiple inheritance, I couldn't
make a form deriving from both:
EmbellishedQueryUnloadForm Inherits Windows.Forms.Form
As you can see, as the number of customizations increases, there is an
explosion of subclasses of Form, because of the presumed need to have a Form
subclass for each possible combination of customizations.
Although code reuse is a benefit of inheritance, it is not the true power.
The true power of inheritance is that a client can treat subclasses
uniformly, so that application code is less cumbersome, less
"straight-down", and less brute-force, and therefore, easier to maintain.
Plus, the sibling subclasses get to share common parent implementation code.
This type of polymorphism is vastly superior to that filthy hack that VB5 &
VB6 called Interface Polymorphism.
The context here is a Notepad application as a real world design /
implementation sample case study for students. The project is highly
focused on best practices, not just banging out an app, and it is intended
to be of good design, not just simply functional.
The problem at hand was how to implement the Edit->Find functionality in a
way that yields great design. See, if I put the implementation of the text
searching inside the find dialog, then how should the application implement
the Edit ->FindNext menu command, which invokes the find feature silently
(without displaying the dialog) to find the next occurance of something the
user already searched for? My first thought was to put the searching
algorithm inside the Find class (the dialog). If the user needs to display
the form in Edit-> Find and the application needs to borrow it from
Edit->FindNext, I need a uniform way of treating the dialog. My next
thought was to have the Notebook class (main form) hold a private instance
of Find, passing its constructor a reference to the TextBox on the Notebook
form:
Private _find As New Find(txt)
At first glance, this would free me to have Find implement the QueryUnload /
UnloadMode thing. But that's not clean enough, because now the Find class
is wired to the Notebook class by the control it's hooked up to. The Find
class is now no longer portable between other apps, and it still doesn't
lend itself to be flexible enough to Find other things besides text.
So here is the solution that offers a flexible, less tighty coupled, more
portable, more maintainable design. The Find class defines a delegate for
generic searching. FindNExtHandler's interface allows for text-search,
database search, or whatever. Below that, the Notebook class (main
application) implements the specific searching algorithm that is appropriate
for the nature of itself as the application. When the application needs to
do a search, it passes a Delegate (typesafe function pointer) of it's own
FindNext implementation to the Find dialog's Constructor. The find dialog
then invokes the delegate when the user clicks a button. If the user then
clicks Edit->FindNext, The application can call its own method directly.
If I had used inheritance to solve this problem, I easily could have ended
up with a zoo of subclasses: SearchableForm, FindForm, QueryUnloadableForm,
SearchableQueryUnloadableForm, etc...
Here, I have two classes, not a fistful of them. Inheritance is an "OO"
technique, but just because an application uses lots of subclassing, it does
not, however, imply that the application has good design. When I mentioned
in the previous post that arbitrary subclassing can turn into problems, this
what I was on about. Inheritance heirarchies need to be carefully and
thoughtfully planned so that features can be added or customized after the
fact, without the need to disturb existing code, and to not have side
effects.
The resulting advantages:
Find.vb
Is portable because it is not married to this application.
Is flexible because it is not married to this search implementation
Doesn't care who is using it or what is being searched.
Doesn't have to expose unnecessary (sp?) stuff
Notebook.vb
Can supply whatever search it wants and pass a function pointer.
Doesn't care what the Find box looks like or how it works.
Doesn't have to share any unnecessary (sp?) stuff
Public Class Find
Inherits Windows.Forms.Form
Public Delegate Sub FindNextHandler( _
ByVal startPos As Integer, _
ByVal searchFor As String, _
ByVal matchCase As Boolean, _
ByVal down As Boolean)
Private _findNext As FindNextHandler
Private _caretPos As Integer
Sub New(ByVal findNext As FindNextHandler, caretPos As Integer)
Me.New()
_findNext = findNext
_caretPos = caretPos
End Sub
Private Sub btnCancel_Click(...) Handles btnCancel.Click
Close()
End Sub
Private Sub btnFindNext_Click(...) Handles btnFindNext.Click
_findNext.Invoke(_caretPos, txtFind.Text, _
chkMatchCase.Checked, radDown.Checked)
End Sub
....
End Class
Public Class Notebook
Private _findText As String
Private _findCase As Boolean
Private _findDown As Boolean
Private _findPos As Integer
Friend Sub FindNext( _
ByVal startPos As Integer, _
ByVal searchFor As String, _
ByVal matchCase As Boolean, _
ByVal down As Boolean)
'Actual implementation of the Text Search
End Sub
Private Sub mnuEditFind_Click(...) Handles mnuEditFind.Click
'Display the Find dialog, passing it a pointer to FindNext()
'and the current location of the caret within the text.
Dim start As Integer = Math.Max(txt.SelectionStart, 1)
Dim f As New Find(AddressOf FindNext, start)
f.Show()
End Sub
Private Sub mnuEditFindNext_Click(...) Handles mnuEditFindNext.Click
FindNext(_findPos, _findText, _findCase, _findDown)
End Sub
....
End Class
--
Peace & happy computing,
Mike Labosh, MCSD MCT
Owner, vbSensei.Com
"Escriba coda ergo sum." -- vbSensei