The Factory pattern uses one class to create instances ofother classes.. The following code shows how a program might make a clone of a Customerobject named old_customer: Dim new_custome
Trang 1Design Patterns
A design pattern consists of one or more objects that together perform some useful task In a sense,
a design pattern is an algorithm for using classes to do something useful It’s a recipe for buildingpart of an application A design pattern may include objects from a single class, or an ensemble ofclasses working together
As you refine your application’s design, you should start looking for common design patterns.You may discover a group of classes that implement a pattern You may also find a group ofclasses that you could rewrite to follow a pattern In either case, if you can rewrite your design tofollow a design pattern, then you can take advantage of the solutions that other developers havedeveloped to solve the same problem That can save you time and gives you some confidence thatthe solution will work correctly so you can move on to design other parts of the application.Some patterns also make an application easier to implement, debug, and maintain For example,the Adapter and Facade patterns described later in this chapter help decouple classes so they areless tightly related That lets you build, debug, and modify the classes with less impact on the rest
of the application
Since the seminal book Design Patterns by Gamma et al (Boston: Addison-Wesley, 1995) was
pub-lished, all sorts of patterns have been devised for data access, integration, interoperability, services,security, Web Services, configuration, exceptions, testing, threat modeling, legacy applications,project management, and many more This chapter, or even this whole book, doesn’t have room tocover them all Rather than just listing the names of a bunch of design patterns with no explana-tion, this chapter describes in more detail some of the patterns that I have found most useful in theapplications that I’ve built
A lot of the design pattern books treat their patterns in a very formal way with introductory tions for each pattern giving a synopsis, context, “forces,” and other sections eventually leading up
sec-to a solution, which is often so simple it’s anticlimactic In some cases, the prelude sections are sostilted and full of technical diagrams that they’re practically unintelligible This chapter takes amuch more intuitive approach
Trang 2For a more complete treatment of design patterns, see a book entirely about them such as the original
Design Patterns This book was written by four authors (Gamma, Helm, Johnson, and Vlissides), who are
sometimes called “the gang of four,” so the book is also sometimes called “the gang of four,” or GOF
You can also see the book Visual Basic Design Patterns by Mark Grand and Brad Merrill (Indianapolis:
Wiley, 2005)
The patterns that I find most useful can be grouped into three categories: creation patterns, relationpatterns, and behavior patterns
Creation Patterns
Creation patterns are patterns that deal with how objects are created The Clone pattern provides a way
to make new objects by copying existing ones The Factory pattern uses one class to create instances ofother classes
Clone
Sometimes it’s useful to make an exact copy of an existing object That’s what a clone is: a copy of anobject that is initialized with the same property values as the object from which it was cloned
The Clone pattern is usually called the Prototype pattern in the design pattern literature The idea is that
the application can make a new object based on a prototype object by cloning the prototype
It’s relatively easy to allow cloning in Visual Basic Simply add a Clonefunction to a class that returns anew instance of the class with the same parameters as the existing object
In fact, Visual Basic’s Objectclass provides a MemberwiseClonemethod that returns a new object withthe same property values as the original object Because all classes are descendants of the Objectclass,they can all have the MemberwiseClonemethod
The following code shows how a program might make a clone of a Customerobject named
old_customer:
Dim new_customer As Customer = DirectCast(old_customer.MemberwiseClone, Customer)
The MemberwiseClonemethod returns a generic Objectso this code uses DirectCastto convert theresult into a Customerobject
To make cloning objects a little easier, you can make a Clonemethod that wraps up the call to
MemberwiseClone The following code shows how a simple Customerclass can use MemberwiseClone
to implement a Clonemethod:
Public Class Customer
‘ Code omitted
‘ Return a shallow clone
Public Function Clone() As Customer
Trang 3Return DirectCast(Me.MemberwiseClone(), Customer)End Function
End Class
Now the main program can use this method, as in the following code:
Dim new_customer As Customer = old_customer.Clone()
The MemberwiseClonemethod makes a new copy of an object and initializes its fields to the same ues used by the original object That works well for simple data types such as Integeror Stringbut itdoesn’t work as well with references to other objects
val-For example, suppose an Orderitem contains a reference to a Customerobject When you clone an
Order, should the clone contain a reference to the same Customerobject as the original, or should itcontain a reference to a new Customerobject that has been cloned from the original? If the new Order
has a reference to the existing object, then changes to the joint Customerobject are shared by both
Orders If the new Orderobject has a reference to a new copy of the Customer, then changes to the two
Customerobjects happen independently
A clone that uses the same references as the original object is called a shallow copy The MemberwiseClone
method makes shallow copies A clone that uses references to new objects is called a deep copy.
You could also define partially deep copies if you want to For example, suppose an Orderitem has erences to a Customerobject, and a collection of references to OrderItemobjects that describe the items that are included in the order You might want an Orderclone to share the same Customer
ref-object as the original, but get its own new OrderItemscollection This would let you easily make a new order for an existing customer This kind of partially deep clone would be defined for a particular application, so there aren’t really any standards for defining them.
The following code shows an Orderclass that can make shallow or deep copies:
Public Class OrderPublic OrderCustomer As CustomerPublic OrderItems As New List(Of OrderItem)
‘ Code omitted
‘ Return a deep or shallow clone
Public Function Clone(ByVal deep As Boolean) As Order
‘ Start with a shallow clone
Dim new_order As Order = DirectCast(Me.MemberwiseClone(), Order)
‘ If appropriate, copy deeper data
If deep ThenWith new_order
‘ Clone the OrderCustomer
.OrderCustomer = Me.OrderCustomer.Clone()
‘ Clone the OrderItems list
.OrderItems = New List(Of OrderItem)For Each order_item As OrderItem In Me.OrderItems.OrderItems.Add(order_item.Clone())
Trang 4Next order_itemEnd With
End IfReturn new_orderEnd Function
End Class
The Orderclass contains a reference to a Customerobject and a generic list of OrderItemobjects
The Clonemethod takes a parameter indicating whether it should return a shallow or deep copy Thefunction starts by using MemberwiseCloneto make a shallow copy If it should make a deep copy, itthen sets the new object’s Customerand OrderItemreferences to clones of the original objects
In this example, the Customerand OrderItemclasses don’t contain any references, so they only provideshallow copies, and the Orderclass’s Clonemethod doesn’t need to pass a parameter into the Customer
and OrderItemclass’s Clonemethods If those classes did support shallow and deep cloning, you wouldprobably want to pass the same deep parameter into those methods
The following code shows how the Clonesexample program demonstrates cloning in the Customer
and OrderItemsclasses:
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Dim txt_originals As String = “”
Dim txt_clones As String = “”
‘ Make and clone a Customer
Dim cust1 As New Customer(“Rod”, “Stephens”, “1337 Leet St”, “Bugsville”, _
“AZ”, “87654”)Dim cust2 As Customer = cust1.Clone()txt_originals &= “*** cust1:” & vbCrLf & cust1.ToString() & “ -” & _vbCrLf
txt_clones &= “*** cust2:” & vbCrLf & cust2.ToString() & “ -” & vbCrLf
‘ Make and clone an Order
Dim order1 As New OrderWith order1
.OrderCustomer = New Customer(“Bob”, “Hacker”, “123 Decompile Ct”, _
“Attatash”, “ME”, “01234”).OrderItems.Add(New OrderItem(“Cookies”, 12)).OrderItems.Add(New OrderItem(“Pencil”, 2)).OrderItems.Add(New OrderItem(“Notebook”, 6))End With
‘ Make a shallow clone
Dim order2 As Order = order1.Clone(False)
‘ Make a deep clone
Dim order3 As Order = order1.Clone(True)
‘ Modify the original Order object
With order1.OrderCustomer
Trang 5.FirstName = “Mindy”
.LastName = “Modified”
.Street = “12 Altered Ave”
.City = “Changed City”
.State = “AL”
.Zip = “98765”
End WithWith order1.OrderItems(0).Item = “Rice (grain)”
.Quantity = 10000End With
‘ Display the three Orders
txt_originals &= “*** order1: “ & vbCrLf & order1.ToString() & _
“ -” & vbCrLftxt_clones &= “*** order2: “ & vbCrLf & order2.ToString() & _
“ -” & vbCrLftxt_originals &= “*** order1: “ & vbCrLf & order1.ToString() & _
“ -” & vbCrLftxt_clones &= “*** order3: “ & vbCrLf & order3.ToString() & _
“ -” & vbCrLf
‘ Display the results
txtOriginals.Text = txt_originalstxtClones.Text = txt_clonestxtOriginals.Select(0, 0)txtClones.Select(0, 0)End Sub
The code first creates Customerobject Its constructor initializes its properties The code uses the
Customerclass’s Clonemethod to make a copy of the object It then uses the objects’ ToStringods to add textual representations of the objects to a pair of output strings
meth-Next, the program makes an Orderitem, attaches a Customerobject, and fills its OrderItemslist with
OrderItemobjects It then makes shallow and deep clones of the Orderobject It modifies some of thevalues in the original object, and then adds textual representations of all three objects to the outputstrings The code finishes by displaying the result strings
Figure 7-1 shows the result Notice that the Customerobject on the top left is the same as its clone on theright The original Orderitem named order1on the left matches the shallow copy order2on the right.The deep copy order3does not match, because it got its own new Customerand OrderItemobjects when
it was copied, so those items were not modified when the program altered the original Order’s data
Visual Basic defines an ICloneableinterface to make cloning more consistent Unfortunately, the face defines a Clonemethod, but it doesn’t take a parameter indicating whether it should be a deep orshallow copy, and it returns a generic Objectinstead of a more specific object type
inter-The intent is that the program will use MemberwiseCloneto make a shallow copy, and Cloneto make adeep copy In either case, the main program must use DirectCastto convert the returned Objectinto
an appropriate type
Trang 6Figure 7-1: Program Clonesdemonstrates shallow and deep clones.
The ICloneableinterface doesn’t provide much advantage over simply writing your own Clone
method that takes a parameter indicating the kind of copy to make and returning a specific object type.Unless you need to work with another class that requires the ICloneableinterface, you are just as welloff writing your own Clonemethod
Factory
A factory is a class that provides a means for creating instances of other classes The main program
cre-ates the Factoryobject and calls a method to create an instance of another class Usually the factorycould return more than one type of object depending on the circumstances Pulling the decision-makingprocess into the class helps isolate the main program from information about those created classes.Suppose it’s hard to decide what type of object to create Perhaps the type of object depends on theuser’s selections, the value of a calculation, or the data in a file In that case, the program must performsome sort of complicated test, and then create the appropriate object
For example, suppose the user opens a file that might be a bitmap, text file, or database The programmust create a different kind of object to handle each choice If file_infois a FileInfoobject represent-ing the selected file, then you could use code similar to the following to create the right object Here,
FileProcessoris a parent class from which BitmapProcessor, TextFileProcessor, MdbProcessor,and OtherProcessorare derived:
‘ Make an appropriate FileProcessor
Dim file_processor As FileProcessor = Nothing
Select Case file_info.Extension.ToLower
Trang 7file_processor = New MdbProcessor(file_info)Case Else
file_processor = New OtherProcessor(file_info)End Select
‘ Tell the FileProcessor to do its thing
fp.ProcessFile()
The Select Casestatement examines the file’s extension and creates an object of the appropriate
FileProcessorsubclass The program then calls the processor’s ProcessFilemethod to take action.This code works, but it locks information about the file types into the main program’s code If you laterneeded to change the types of files supported, or the method by which the program decides which type
of processor to build, you must update the main program
You can pull the intelligence out of the main program into its own class by making a Factoryclass The
FileProcessorFactoryclass shown in the following code holds the logic to create different kinds of
FileProcessorsubclasses:
‘ Make various kinds of FileProcessor objects
Public Class FileProcessorFactoryPublic Function MakeFileProcessor(ByVal file_name As String) As FileProcessorDim file_info As New FileInfo(file_name)
‘ See what kind of file this is
Dim file_processor As FileProcessor = NothingSelect Case file_info.Extension.ToLowerCase “.bmp”
file_processor = New BitmapProcessor(file_info)Case “.txt”
file_processor = New TextFileProcessor(file_info)Case “.mdb”
file_processor = New MdbProcessor(file_info)Case Else
file_processor = New OtherProcessor(file_info)End Select
Return file_processorEnd Function
Dim fp As FileProcessor = fp_factory.MakeFileProcessor(dlgOpen.FileName)
‘ Tell the FileProcessor to do its thing
fp.ProcessFile()
Trang 8The code makes a new FileProcessorFactoryobject and calls its MakeFileProcessorfunction
to make an object from one of the FileProcessorsubclasses It then calls that object’s ProcessFile
method
Notice that the factory creates instances of classes that all inherit from the FileProcessorbase class.
In general, the objects that the factory creates should either come from the same ancestor class, or ment the same interface so the main program can do something meaningful with them Otherwise, the
imple-program will need to use some sort of test such as If TypeOf returned_object Is Class1 Then and so forth That pulls logic about the subclasses back into the main program and defeats much of the
purpose of the factory.
For another example, suppose you build a base class named ErrorProcessor The subclass EmailErrorProcessorsends error messages to a developer and the subclass LogFileErrorProcessorlogs errormessages into a file When an error occurs, the program checks environment variables to see whether itshould log errors into a file or send email If it should send email, it checks other variables to decide towhom it should send the mail You could put all of these tests in the main program and repeat themevery time there is an error message, but it would be better to make an ErrorProcessorFactorytocreate an object from the appropriate ErrorProcessorsubclass
I have also seen programs where developers made a factory class for each concrete class that they willwant to create For example, a CustomerFactoryobject creates a Customerobject Sometimes there may
be a FactoryBaseclass from which the specific factory classes inherit The FactoryBaseclass woulddefine a CreateInstancefunction or some other method for creating an instance of the concrete class
In this scenario, you can use the factory objects to defer creating the concrete objects For example, you canpass a CustomerFactoryobject into a subroutine that may need to make a Customerobject If the routinedecides it doesn’t need a Customerobject, it doesn’t need to create one If the Customerclass is compli-cated and hard to initialize, not creating a Customercan save the program some time and memory
Similarly, you can pass a factory object into a subroutine that can later use it to create an object from theconcrete class, all without knowing what type of object it is creating In this approach, the decision aboutthe type of object was made earlier when you picked the appropriate factory class, but actually creatingthe object has been deferred
One misuse of factory classes that I’ve seen is when the program uses a set of factory classes to ately create instances of their classes For example, the program creates an InvoiceFactoryobject andthen immediately uses it to create an Invoiceobject In that case, you could just as well have movedany initialization code from the InvoiceFactoryinto the Invoiceclass’s constructors Another varia-tion would be to give the Invoiceclass a public shared function that returns an Invoiceobject Either
immedi-of these solutions avoids having to have the InvoiceFactoryclass, avoids the need to create instances
of that class, and moves logic dealing with the Invoiceclass inside that class where it belongs
Relation Patterns
Relation patterns deal with the ways in which objects are related to each other They affect the
object-oriented structure of the application The Adapter, Facade, and Interface patterns all provide front-ends
to classes They allow one class to interact with one or more other classes in as simpler, more isolatedway That can help decouple the classes so they are easier to write, debug, and maintain separately
Trang 9An Adapter class provides an interface between two classes It lets two classes cooperate when they
other-wise couldn’t because they have mismatched interfaces
For example, suppose you have written an event logging system that can log messages into a file oremail messages to a developer Now, suppose you decide you also want to be able to send messages to
a pager The method your code uses to log messages into a file or send them in an email doesn’t workthe same way as the Web Service that you would call to send the message to a pager
To make the existing classes work with the new pager class, you could write an Adapter between them.The Adapter would let the main program call methods in the same way it does now when using a log file
or email It would then pass requests along to the Web Service in whatever format it requires Figure 7-2shows the situation graphically
Figure 7-2: An Adapter allows a program to work with an incompatible class
Because the Adapter wraps up another object, in this case a Web Service, it is also sometimes called aWrapper
The Adapter pattern is closely related to the Interface and Facade patterns
Facade
A Facade is basically an interface to a large and potentially complicated collection of objects that perform a
single role For example, a program might use DataGatherer, ReportFormatter, and ReportPrinter
classes to build and print reports You can make printing reports simpler by hiding the process behind aFacade Figure 7-3 shows the idea graphically
Figure 7-3: A Facade presents a simple interface for a complex collection of objects
Application
DataGatherer
RepeatFormatterReportPrinter
PagerServiceAdapter
MessageLogger
Trang 10Note that the classes do not need to be strictly contained in the Facade For example, the DataGatherer
class could be used in other operations, perhaps to build a report for display rather than printing In that case, you might want another Facade to hide the details of report display.
Interface
An Interface is basically the same as an Adapter, but it is used for a different purpose You use an Adapter
to allow existing incompatible classes to work together You use an Interface to isolate two classes sothey can evolve independently It helps keep two classes loosely coupled
For example, suppose your application generates a series of reports with a standard format Early in theproject, you may not have completely finalized the report format or the data that will be displayed inthe reports If you make the CustomerOrderclass call the ReportGeneratorclass directly, then youwill need to change the CustomerOrdercode whenever the ReportGeneratorcode changes Whenthe data stored in the CustomerOrderclass changes, you may also need to make changes to the
to display the new data
However, the Interface lets you change CustomerOrderwithout changing ReportGeneratorrightaway The developers working on that class can continue their work independently, and process the newdata when they are ready to do so
I’ve worked on a couple of projects where tight coupling between classes made it very difficult for ers working on the classes to get anything done The first class would change and the second class would break until it was updated The next day the second class would change and first would break until it was fixed Adding an Interface between the two allowed them both to move forward more quickly.
develop-Behavior Patterns
Behavior patterns deal with the behavior of parts of the application The Abstract Base Class pattern uses
a base class to determine the properties and behavior of derived classes
Chain of Responsibility and Chain of Events allow a program to apply a series of methods to an object orevent until one of them handles it
The Command pattern lets you treat a method as an object that you can pass around to different pieces
of code Commands can be useful for implementing the Chain of Responsibility, Chain of Events, andStrategy patterns
Trang 11Delegation is a technique where one object uses other objects to provide some of its behavior The Model,View, Controller pattern allows a group of relatively simple classes to let the user view and modify data incomplex ways Both the Delegation and the Model, View, Controller patterns deal with ways classes canwork together to simplify complex behavior.
The Property Procedure pattern describes a standard method for implementing properties that makesthem easier to debug
The Snapshot pattern describes a technique for allowing code to save an object’s state and later restore it
to that state without knowing the object’s internal details
Finally, the Strategy pattern encapsulates a strategy or algorithm While the Chain of Responsibility andChain of Events apply several methods until one works, the Strategy pattern allows a program to apply
a single one of a number of different approaches
Abstract Base Class
The idea behind an Abstract Base Class is so simple that it’s almost not worth describing as a design
pattern However, it is an important idea, so it’s probably better to give it too much attention rather thannot enough
When you derive a set of child classes from a parent class, the program can treat objects from the child
classes as if they were members of the parent class That’s the object-oriented principle of polymorphism.
For example, suppose you are making a drawing application and you want to make classes representingdrawn objects such as rectangles, ellipses, polygons, stars, text, and so forth Each of these has someshared characteristics such as an (X, Y) position, width, height, fill color, outline color, and line style
If the objects are to draw themselves, they must also provide some sort of Drawmethod
To allow the main program to treat all of these objects in the same way, you can pull out these commoncharacteristics and place them in a parent class named Shape Now the program can store objects in acollection of Shapes It can loop through the collection calling each method’s Drawmethod and it canexamine each object’s size and position
While the program must be able to create objects from the ellipse, rectangle, and other child classes, itwouldn’t make much sense to create a Shapeobject The Shapeclass represents an abstraction of thechild classes, not a particular shape itself It can store a position, size, and colors, but if you called its
Drawmethod, what would it draw?
To ensure that the program doesn’t try to create a Shapeobject, you can make the class abstract An abstract class is one that cannot be instantiated In contrast, a concrete class is one that is not abstract, so
you can create instances of it In Visual Basic, you can make a class abstract by adding the MustInherit
keyword to its declaration
The following code shows an abstract Shapeclass:
‘ The abstract Shape parent class
Public MustInherit Class ShapePublic Location As RectanglePublic FillColor As Color
Trang 12Public ForeColor As Color
‘ The constructor is protected so the child classes must
‘ provide constructors for the program to use
‘ Save the location and colors
Public Sub New(ByVal shape_location As Rectangle, ByVal fill_color As Color, _ByVal fore_color As Color)
Location = shape_locationFillColor = fill_colorForeColor = fore_colorEnd Sub
The Shapeclass defines public Location, FillColor, and ForeColorvariables that are shared by all
of its child classes It then defines a constructor that uses parameters to initialize these variables
If you declare a constructor as Protected, only that class and any derived classes can use it, so the main program will not be able to use the constructor If this is the only constructor, then the program cannot make an instance of the class This essentially makes the class abstract.
Next, the Shapeclass declares a Drawmethod The MustOverridekeyword makes this is an abstract method declaration that provides no implementation (notice that there is no End Substatement) Any con-crete ancestor classes must override this method and provide a concrete implementation The declaration
in the Shapeclass defines the signature of the method In this case, the method must be a subroutine, not
a function, and must take a single Graphicsobject as a parameter
Note that a class with an abstract method must be an abstract class In Visual Basic, that means if the class has a MustOverridemethod, then it must be declared MustInherit.
Note also that only concrete child classes must provide an implementation of the abstract method If
you declare a child class MustInherit, then it doesn’t need to override the method Its descendants can do it instead.
The following code shows the concrete RectangleShapeclass derived from the Shapeclass:
‘ Rectangle
Public Class RectangleShape
Inherits Shape
‘ Constructor Delegated to the Shape class
Public Sub New(ByVal shape_location As Rectangle, ByVal fill_color As Color, _ByVal fore_color As Color)
MyBase.New(shape_location, fill_color, fore_color)End Sub
Trang 13‘ Draw the rectangle.
Public Overrides Sub Draw(ByVal gr As System.Drawing.Graphics)Using the_brush As New SolidBrush(FillColor)
gr.FillRectangle(the_brush, Location)End Using
Using the_pen As New Pen(ForeColor)gr.DrawRectangle(the_pen, Location)End Using
End SubEnd Class
The RectangleShapeclass defines a constructor that uses the Shapeclass’s constructor to save locationand color information
The class provides a concrete Drawmethod It makes a brush and fills the rectangle; then makes a penand outlines the rectangle
You can easily make other classes to draw ellipses and other shapes The EllipseShapeclass would beidentical to the RectangleShapeclass, except the Drawmethod would draw an ellipse instead of a rect-angle Other classes might need additional information For example, a PolygonShapeclass would need
to store the points it will connect ATextShapeclass would need to know what font to use and whattext to draw
The following code shows how the AbstractBaseClassexample program works:
Imports System.Collections.GenericPublic Class Form1
‘ The collection of Shapes to draw
Private m_Shapes As New List(Of Shape)Private Sub Form1_Load(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles MyBase.Load
‘ Make some Shapes
m_Shapes.Add(New RectangleShape(New Rectangle(50, 20, 50, 120), _Color.Orange, Color.Red))
m_Shapes.Add(New EllipseShape(New Rectangle(80, 60, 90, 180), _Color.Blue, Color.DarkBlue))
m_Shapes.Add(New RectangleShape(New Rectangle(20, 80, 210, 40), _Color.Lime, Color.DarkGreen))
m_Shapes.Add(New EllipseShape(New Rectangle(10, 150, 250, 50), _Color.Pink, Color.Teal))
‘ Redraw
Me.Invalidate()End Sub
‘ Draw the shapes
Private Sub Form1_Paint(ByVal sender As Object, _ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Painte.Graphics.Clear(Me.BackColor)
For Each a_shape As Shape In m_Shapesa_shape.Draw(e.Graphics)
Trang 14Next a_shapeEnd Sub
End Class
The program declares a generic List Of Shapeobjects to store drawing objects When the form loads, theprogram creates some RectangleShapeand EllipseShapeobjects and adds them to the collection
When the form’s Paintevent handler fires, the program clears the form and loops through the Shape
collection, calling each object’s Drawmethod
Figure 7-4 shows the program in action
Figure 7-4: Program AbstractBaseClassuses concrete child classes to draw rectangles and ellipses
An abstract base class is useful for specifying features that must be implemented by child classes, andwhen it doesn’t make sense for the program to create instances of the base class
Chain of Responsibility
In the Chain of Responsibility pattern, some sort of message, task, or other item that must be handled is
sent to a series of objects until one of them processes it This frees the code that must handle the itemfrom needing to know how the item must be handled
Normally, the classes that process the item provide a function that examines the item and returns Trueifthe object completely handled the item
The following code shows an abstract NumberHandlerclass The abstract WasHandledfunction returns
Trueor Falseto indicate whether the object processed an integer parameter
Public MustInherit Class NumberHandler
Public MustOverride Function WasHandled(ByVal value As Integer) As BooleanEnd Class
Trang 15Child classes must override the WasHandledfunction The following code shows a PowerHandlerclassthat displays a message if the input value is a power (greater than 1) of some other integer (for example,
16 = 2 ^ 4):
Public Class PowerHandlerInherits NumberHandler
‘ Return True if we handle this item
Public Overrides Function WasHandled(ByVal value As Integer) As Boolean
If value < 0 Then value = -value
If value < 4 Then Return FalseFor base As Integer = 2 To CInt(Sqrt(value))Dim power As Double = Log(value, base)
If CInt(power) = power Then
‘ value = base ^ power
MessageBox.Show(value.ToString() & “ = “ & _base.ToString() & “^” & power.ToString(), _
“Power”, MessageBoxButtons.OK, MessageBoxIcon.Information)Return True
End IfNext base
‘ Not a power
Return FalseEnd FunctionEnd Class
The WasHandledfunction first ensures that the input value is non-negative If the value is less than 4,the function returns False, because the values 0 through 3 are not powers of other integers
Next, the program loops through base values between 2 and the square root of the input value It takesthe logarithm of the value using the base If the result is an integer, then the value is a power of the base,
so the function displays a message and returns True
If the input value is not a power of any of the bases, it is not a power, so the function returns False
Example program ChainOfCommanduses the following code to evaluate numbers:
Public Class Form1Private m_Handlers As New List(Of NumberHandler)
‘ Make the chain of command
Private Sub Form1_Load(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles MyBase.Loadm_Handlers.Add(New PrimeHandler())
m_Handlers.Add(New PowerHandler())m_Handlers.Add(New EvenHandler())m_Handlers.Add(New UnhandledHandler())End Sub
‘ Process a number
Private Sub btnProcess_Click(ByVal sender As System.Object, _
Trang 16ByVal e As System.EventArgs) Handles btnProcess.ClickDim value As Integer = Integer.Parse(txtValue.Text)For Each handler As NumberHandler In m_Handlers
If handler.WasHandled(value) Then Exit ForNext handler
End SubEnd Class
When the program’s form loads, the code makes a List of NumberHandlerobjects and adds subclasses
of the NumberHandlerclass to the list PrimeHandlerprocesses prime numbers, PowerHandler
processes numbers that are powers of other numbers, and EvenHandlerprocesses even numbers
The UnhandledHandlerclass processes every number by displaying a message box saying the numberwas unhandled It’s usually a good idea to place a similar catch-all object at the end of the chain of com-mand to deal with any items that are not processed earlier At a minimum, this object can display anerror message to the user or log the event
If you enter a value in the ChainOfCommandexample program and click the Process button, the codeloops through the list of handlers It calls each handler’s WasProcessedfunction and exits the loop ifthe function returns True
This example tries each of its handlers in the order in which they were added to the handler list, but youcould apply the handlers in any order that makes sense For example, you might sort the handlers bypriority and apply the higher priority items first
For a concrete example, suppose you have created classes that process error messages in various ways.Perhaps you would first like to try sending an email to developers, then try writing the error into a logfile, and, finally, display a message to the user You can give each of the objects a priority and sort the list
of handlers so that the program tries them in priority order
Note that an object’s WasHandledfunction should return Trueonly if the item is completely handled so
that other handlers don’t need to look at it An object’s WasHandledfunction might take some action topartially handle the item, and then return Falseto give other objects a chance to perform additionalprocessing
For example, suppose when there’s an error you want to notify the user and log the file in some way.Then the NotifyUserErrorHandler’s WasHandledmethod could display a message and return False.Next the program would let the EmailErrorHandlerand LogErrorHandlerobjects also process themessage
Chain of Events
A Chain of Events is similar to a Chain of Command, but it is somewhat more specific A Chain of
Command sends an item to a series of handler objects to see if any of them can process the item A Chain
of Events sends an event to a series of event handlers to allow them to handle it
Trang 17The ChainOfEventsSimpleexample program uses the NumberProcessorclass shown in thefollowing code to monitor an integer value When its value is changed, the NumberProcessorraises a
NumberChangedevent
Public Class NumberProcessorPrivate m_Value As IntegerPublic Property Value() As IntegerGet
Return m_ValueEnd Get
Set(ByVal value As Integer)m_Value = value
RaiseEvent NumberChanged(m_Value)End Set
End PropertyPublic Delegate Sub NumberChangedDelegate(ByVal value As Integer)Public Event NumberChanged As NumberChangedDelegate
End Class
When the program starts, it uses the following code to register three event handlers for the
NumberChangedevent:
Private m_NumberProcessor As New NumberProcessor
‘ Add events to the NumberProcessor
Private Sub Form1_Load(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles MyBase.LoadAddHandler m_NumberProcessor.NumberChanged, AddressOf CheckPrimeAddHandler m_NumberProcessor.NumberChanged, AddressOf CheckPowerAddHandler m_NumberProcessor.NumberChanged, AddressOf CheckEvenm_NumberProcessor.Value = Integer.Parse(txtValue.Text)
End Sub
If you change the value in the program’s text box, the following code updates the NumberProcessor, itraises its NumberChangedevent, and the three event handlers execute:
‘ Set the new integer value
Private Sub txtValue_TextChanged(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles txtValue.TextChanged
If IsNumeric(txtValue.Text) Thenm_NumberProcessor.Value = Integer.Parse(txtValue.Text)End If
Trang 18determine the order in which they are executed, although in practice, they are executed in the order inwhich they were registered, so you have some control They also don’t give you the option of stoppingafter an event handler has successfully processed the event.
You can provide these extra features if you use a custom event The following code shows a new version
of the NumberProcessorclass that provides a custom NumberChangedevent:
Public Class NumberProcessor
Private m_Value As IntegerPublic Property Value() As IntegerGet
Return m_ValueEnd Get
Set(ByVal value As Integer)m_Value = value
RaiseEvent NumberChanged(m_Value, False)End Set
End PropertyPublic Delegate Sub NumberChangedDelegate(ByVal value As Integer, _ByRef was_handled As Boolean)
Private m_EventDelegates As New List(Of NumberChangedDelegate)Public Custom Event NumberChanged As NumberChangedDelegateAddHandler(ByVal value As NumberChangedDelegate)m_EventDelegates.Add(value)
End AddHandlerRemoveHandler(ByVal value As NumberChangedDelegate)m_EventDelegates.Remove(value)
End RemoveHandlerRaiseEvent(ByVal value As Integer, ByRef was_handled As Boolean)was_handled = False
For Each a_delegate As NumberChangedDelegate In m_EventDelegatesa_delegate(value, was_handled)
If was_handled Then Exit ForNext a_delegate
End RaiseEventEnd Event
End Class
A custom event handler has three sections: AddHandler, RemoveHandler, and RaiseEvent
The AddHandlersection stores information about a delegate that is being registered This exampleadds the delegate to a list
The RemoveHandlersection removes a delegate from the list
The RaiseEventsection invokes the delegates when the NumberProcessorobject raises its NumberChangedevent In this example, the NumberChangedDelegateis declared with two parameters: a valuepassed by valueand a Boolean variable named was_handledpassed by reference The invoked eventhandler should set this to Trueif it has completely processed the event
Trang 19In this example, the RaiseEventcode loops through the stored delegates, invoking each until one of thesets was_handledto True That lets the program skip any remaining event handlers.
Example program ChainOfEventsuses event handlers that check a number to see whether it is prime,
a power, or even If any of the event handlers takes action, it displays an appropriate message and sets
was_handledto Trueso that the program skips the remaining event handlers
Custom events don’t give you an easy way to prioritize event handlers (other than the fact that they areexecuted in the registration order) If you need to order the handlers more dynamically, use the Chain ofCommand pattern described in the previous section
Command
In the Command design pattern, an object represents an action The program can perform the action by
calling the object’s Executemethod
Command objects are useful when you want to pass the action around for use later, and you don’t want thecode that is carrying and using the object to know the details of what the command does This decreasesthe coupling between the code and the action, and makes working with both easier
For example, suppose a report generator class gathers data and formats a report You could pass thegenerator a command object that provides a ProcessReportmethod After it has built the report,the generator can call the command object’s ProcessReportmethod without knowing whether thereport will be printed, saved into a file, or emailed to a user
You can pass a variety of command objects to allow for contingencies For example, you could pass anorder processing routine an object to invoke if the customer has an unpaid balance, another object toinvoke if the requested items are out of stock, a third object to invoke if the order is processed success-fully, and so forth
Visual Basic provides a couple of ways you can implement the Command design pattern The more mon approach is to build a class that provides an Executemethod, instantiate the class, and pass theresulting object to the code that needs it
com-The following code shows a simple command class Its constructor takes a parameter, giving the sage that the object will later display, and saves the message in a private variable The Executemethoddisplays the following message:
mes-Public Class ExecuteClassPrivate m_Message As StringPublic Sub New(ByVal message As String)m_Message = message
End SubPublic Sub Execute()MessageBox.Show(m_Message, “ExecuteClass”, _MessageBoxButtons.OK, MessageBoxIcon.Asterisk)End Sub
End Class
Trang 20A second approach is to pass a delegate to a routine that can invoke it later With this method you don’tneed to build an extra class just to perform a simple action This is more confusing to many program-mers, however.
The Commandsexample program uses the following code to demonstrate each of these methods:
Public Class Form1
Private Delegate Sub ExecuteDelegate()Private Sub Form1_Load(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles MyBase.LoadbtnGoodDay.Tag = New ExecuteDelegate(AddressOf SayGoodDay)btnGutenTag.Tag = New ExecuteClass(“Guten Tag”)
End SubPrivate Sub SayGoodDay()MessageBox.Show(“Good Day”, “SayGoodDay”, _MessageBoxButtons.OK, MessageBoxIcon.Asterisk)End Sub
Private Sub btnGoodDay_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles btnGoodDay.ClickDim btn As Button = DirectCast(sender, Button)Dim execute_delegate As ExecuteDelegate = _DirectCast(btn.Tag, ExecuteDelegate)execute_delegate()
End SubPrivate Sub btnGutenTag_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles btnGutenTag.ClickDim btn As Button = DirectCast(sender, Button)Dim execute_object As ExecuteClass = DirectCast(btn.Tag, ExecuteClass)execute_object.Execute()
End SubEnd Class
The program defines the ExecuteDelegatetype as a reference to a simple subroutine that takes noparameters When the program starts, its Loadevent handler sets the Good Day button’s Tagproperty
to a delegate representing the form’s SayGoodDaysubroutine
Next, the program sets the Guten Tag button’s Tagproperty to a new instance of the ExecuteClass
When you click the Good Day button, the button’s event handler converts the event’s sender into thebutton that caused the event It casts the button’s Tagproperty into an ExecuteDelegateand executes
it This delegate refers to the SayGoodDaysubroutine so that routine runs and displays the message
“Good Day.”
When you click the Guten Tag button, the button’s event handler converts the event’s sender into thebutton that caused the event It casts the button’s Tagproperty into an ExecuteClassobject and callsits Executemethod That method displays the message “Guten Tag” stored by the object’s constructorwhen it was created in the form’s Loadevent handler
Trang 21Delegates are useful when you need to pass a single routine in a fairly local context and you don’t want
to create a new class Most developers find delegates more confusing than objects, however, so you mayprefer to use objects
Delegation
Like abstract base classes, delegation is a simple but important technique Delegation is the process of one
object using another to provide features It gives a result similar to those of inheritance, but withoutactually using inheritance
To provide Delegation in Visual Basic, give one class a reference to another class To provide the features
of the second class, the object calls the methods provided by its referenced object
For example, suppose you’ve built a Houseclass that has the properties NumberOfBedrooms,
NumberOfBathrooms, SquareFeet, and PricePerSquareFoot It also has a CalculateValue
function that returns the house’s estimated value based on its square feet and price per square foot.Suppose you also have a Vehicleclass that has the properties MilesPerGallon, FuelCapacity,andFuelRemaining Vehiclealso has a CalculateRangefunction that uses MilesPerGallonand
FuelRemainingto estimate the distance the Vehiclecan travel before running out of fuel
Now, suppose you want to make a MotorHomeclass that has the characteristics of both a Houseand a
Vehicle Visual Basic does not allow multiple inheritance, so MotorHomecannot inherit from bothclasses
One solution to this problem is to make MotorHomeinherit from the Houseclass Then give it a privatevariable of type Vehicle To provide the features of a Vehicle, make the MotorHomecall the propertiesand methods provided by this private object
Figure 7-5 shows the idea graphically
Figure 7-5: In Delegation, an object uses other objects to provide its behavior
MotorHome
Vehicle Object Vehicle Methods House Methods