Imagine the following class, which implements the IFileAccess interface: Public Class SalesInfo Implements IFileAccess ' Code omitted.. Your classes can imple-ment both, and clients will
Trang 1This design ensures that you can reuse common pieces of functionality (like the code for the Connect() method) without allowing a programmer to inadvertently create a meaningless object (like an instance of the base
DBRecord class)
MustOverride
In the previous section, you saw how an abstract class can allow you to share code with different derived classes, while remaining safely inactive Abstract
classes also play another role as class templates.
To understand how this works, you need to realize that there are some members that you might want to declare in a MustInherit class even though you can realistically supply the code For example, when you’re designing the
DBRecord class, you might decide that all the classes that derive from DBRecord
should have basic SaveData() and LoadData() methods, which gives them the ability to update or retrieve a single record However, you can’t actually write the code to perform this task, because it depends on the type of record.Here’s where the MustOverride keyword fits in The MustOverride keyword indicates a method whose implementation must be provided by the derived class In other words, a MustOverride method has no code! For that reason, a
MustOverride method can only be placed inside a MustInherit class Here’s an example:
Public MustInherit Class DBRecord Public Sub Connect(ByVal ConnectionString As String) ' Code goes here.
End Sub Public MustOverride Sub LoadData() Public MustOverride Sub SaveData() End Class
In this example, we assume that the Connect() method, which is used to open a database connection, is standard enough that it can be coded directly into the DBRecord class However, the other declared methods, which retrieve and save data from the database, have no default implementation that can be given because they depend upon the contents of the database in question and their types Therefore we leave them to be overriden (actually, imple-mented) by methods in derived classes
When you define a method in an abstract class with MustOverride, you do not specify any code other than the method declaration You don’t even include a final End Sub statement The derived class must implement all
MustOverride methods declared in its parent It can’t ignore any of them (unless it too is a MustInherit class)
The approach illustrated in this example is a powerful one It ensures consistency, and it allows you to use classes with different code (for example,
an EmployeeRecord and an OrderRecord) in the same way, using common
Trang 2methods like Connect(), SaveData(), and LoadData() However, in NET it’s more common to create reusable class templates in a different way—using interfaces, which are presented later in this chapter.
Multiple-Level Inheritance
Visual Basic 2005 allows you to use unlimited layers of inheritance For ple, we could create a new class called DemocratPolitician, or even President, that inherits from the Politician class, which in turn inherits from the Person
exam-class Some classes pass through many levels of inheritance to build up all their features For example, every NET type originates from the ultimate base type System.Object which is enhanced by a number of subsequent derived classes Figure 6-3 shows the inheritance diagram for a common Windows form
Figure 6-3: The lineage of a Windows form
Of course, the architects of the NET class library are experienced OO developers, and multiple-level inheritance is used to great effect in the class library In general, however, levels of inheritance should be viewed with cautious skepticism As a rule of thumb, you should try to keep the levels of inheritance to as few as possible (perhaps just a single level), particularly if the intermediate levels are not used For example, if your application will only ever use Politicians, it’s best to create only a Politician class, rather than
a base Person class and a derived Politician class
Visual Basic 2005 does not allow you to inherit from more than one class
at the same time (This is a limitation of all NET languages.) If you have multiple classes whose features you want to include in a new class, it’s often best to create a compound class that brings together different subobjects through its properties These objects can “plug in” to instances of the new
Trang 3type to provide more features For example, you could create a Person class that can contain an instance of an Occupation class to specify job-related infor-mation and a Car object that describes the primary vehicle used by that person.
Is Inheritance a Good Idea?
Inheritance can be a bit tricky to use properly, and with overuse it can lead to more problems than it’s worth A common problem arising with inheritance
is fragile classes These can emerge when you have a complex hierarchy of
objects and multiple layers of inheritance In such a situation, it’s often extremely difficult to change any characteristics of your base classes, because the changes would affect countless derived classes In other words, your program reaches an evolutionary dead end, because any enhancement would break existing classes and require a cascade of changes that would be difficult to track down and deal with
When using inheritance, you should ask yourself if there are other able solutions to your problem In some cases, you can share code by creating
avail-a utility clavail-ass with avail-appropriavail-ate functions For exavail-ample, you might redesign the DBRecord data object described earlier by placing file access routines into a common class or code module Another way to avoid inheritance is to design objects that can contain other objects, which in turn provide the desired
functionality This technique is called containment, and it’s usually used in combination with a technique called delegation.
For example, suppose you want to create an OrderRecord object with a
Connect() method that opens a database connection You could use ment and delegation to implement this functionality, without inheriting it from a parent class, as follows First, a DBAccess class is designed whose instances can be used to manage communication with the database The definition of the OrderRecord class then includes an internal variable of this type When the OrderRecord.Connect() method is called, it uses the contained
contain-DBAccess object in the appropriate way to make the connection In other words, OrderRecord delegates the responsibility of connecting to DBAccess Here’s a rough outline of the code:
Public Class OrderRecord Private objDB As New DBAccess() Public Sub Connect(ByVal ConnectionString As String) objDB.Connect(ConnectionString)
End Sub End Class
Using Inheritance to Extend NET Classes
This chapter has concentrated on using inheritance with business objects Business objects tend to model entities in the real world, and they usually consist of data (properties and variables) and useful methods that allow you
to process and manipulate that data
Trang 4Inheritance also allows you to acquire features and procedures from the NET class library for free You’ve already seen how to do this with Windows forms, but we haven’t yet discussed the full range of possibilities This section provides two quick examples designed to illustrate the power of inheritance.
Visual Inheritance
Every form inherits from System.Windows.Forms.Form However, you can also make a form that inherits from another form Here’s how to do it:
1 Start a new Windows project Rename the form you start off with to
BaseForm This is the form you’ll use as the standard for other forms
2 Before going any further, add a couple of buttons to BaseForm Then, right-click your project in the Solution Explorer, and choose Build
3 Now, choose Project Add Windows Form to add a second form But instead of starting with the standard blank template, choose the Inherited Form option shown in Figure 6-4
Figure 6-4: Adding an inherited form
4 Name your new form DerivedForm, and click OK
5 The Inheritance Picker dialog box will show you all the forms in your project (and any other components you’re using) You need to choose the form you want to inherit from In this case, it’s BaseForm, as shown in Figure 6-5
6 Click OK
Trang 5Figure 6-5: Choosing the base form
Your new form, DerivedForm, will contain all the controls you created on
BaseForm In fact, DerivedForm will look exactly the same as BaseForm, because
it will have inherited all of BaseForm’s controls and their properties In the designer, you’ll see a tiny arrow icon next to each inherited control (see Figure 6-6)
Figure 6-6: An inherited form in the designer
What’s more, any time you make changes to BaseForm, DerivedForm will be updated automatically (although you may have to build the project before Visual Studio will update the display) None of the code will be repeated in the DerivedForm form class code, but it will all be available For example, if you include a button click event handler in BaseForm, it will take effect in
DerivedForm as well
Trang 6The only difference between BaseForm and DerivedForm is that you won’t be able to move or alter the controls on DerivedForm However, you can still add new controls to DerivedForm, and you can also change form-level properties (like the form caption or dimensions).
If you’re curious to take a look behind the scenes (and confirm that itance really is at work), you need to dive into the designer code file for the form First, select Project Show All Files to reveal it in the Solution Explorer Then, expand the DerivedForm.vb node to show the DerivedForm.Designer.vb code file (Chapter 4 has more on the designer code file, which has the auto-matically generated code that Visual Studio creates.)
inher-In the DerivedForm.Designer.vb file, check out the class declaration Instead of seeing this:
Public Class DerivedForm Inherits System.Windows.Forms.Form
you’ll see this:
Public Class DerivedForm
Inherits BaseForm
In other words, the DerivedForm class inherits from the BaseForm class (which itself inherits from the Form class) As a result, the DerivedForm is a
DerivedForm, a BaseForm, and a plain old Form, all rolled into one
Visual inheritance is a strict and somewhat limiting tool However, if you need to create several extremely similar windows, such as a series of windows for a custom wizard, you can make good use of it
Subclassing a Control
You can use a similar technique to extend a NET control The following example creates a customized text box that accepts only numeric input (It’s included as the NumericTextBox project with the samples.) To create it yourself, add the following class to a Windows application:
Public Class CustomTextBox Inherits System.Windows.Forms.TextBox ' Override the OnKeyPress method, which fires whenever ' a key is pressed.
Protected Overrides Sub OnKeyPress(ByVal e As KeyPressEventArgs) ' Call the base method (which raises the KeyPress event).
MyBase.OnKeyPress(e) ' Check if the just-typed character is numeric ' or a control character (like backspace).
If Char.IsControl(e.KeyChar) = False And _ Char.IsDigit(e.KeyChar) = False Then ' If it isn't, set the Handled property to ' tell the TextBox to ignore this keypress.
Trang 7e.Handled = True End If
End Sub End Class
This is a customized version of the common text box It inherits everything that the TextBox control has to offer, and overrides one of the existing methods, OnKeyPress() The OnKeyPress() method is always called when a key is pressed, just before the character appears in the text box Here you have the chance to examine the character that was typed, and (optionally) refuse it by setting the KeyPressEventArgs.Handled property to True
controls provide an OnXxx() method for each event they provide For example, a button has a Click event, so you can assume it also has an OnClick() method that fires just before the event is raised If you want to react to this action, you can create an event handler (as you saw in Chapter 4), or you can derive a new class and override the related method (as with the custom text box example) Both approaches are functionally equivalent The difference is in where you place the code and how you can reuse it.
To use this class, begin by compiling your application Then, switch
to the design surface of a form You’ll see the CustomTextBox control appear
in the Toolbox automatically (see Figure 6-7) This is a convenience that Visual Studio provides automatically—it searches your code for all classes that derive (directly or indirectly) from System.Windows.Forms.Control and makes them readily available
Now you can drop your custom text box on
a form and run your application You’ll notice that you can only type numeric characters into the text box Letters and symbols are ignored
This raises an interesting question If you want to create a text box that rejects certain characters, is it a better idea to handle an event like KeyPress in your form, or to create a whole new custom control? Generally, it’s better to prevent cluttering your application with extra classes, so it’s simpler to keep all your code in the form However, if you have any complex keystroke-handling logic that you need to share
in several different forms, the custom control approach becomes a lot more attractive Using this technique, you write the code once, and reuse it to your heart’s content You can even share your control in several separate applica-tions by placing your class in a class library (.dll) project and sharing the component with other programmers
Figure 6-7: A custom control
in the Toolbox
Trang 8The interface is a cornerstone of object-oriented design, particularly for
large-scale applications that need to be deployed, maintained, and enhanced over long periods of time Interfaces require a bit of extra effort to use, and they are often avoided because they provide few immediate benefits However, over the long term, they help solve common problems that make an appli-cation difficult to extend and enhance
The goal of an interface is to allow you to separate a class’s definition from its implementation An interface defines a small set of related prop-erties, methods, and events By convention, interfaces always start with the
Sub LoadData() Sub SaveData() End Interface
Note that interfaces don’t use the Public or Private keyword in the larations of their members All the elements in an interface are automatically public You’ll also notice that interfaces don’t define their members—in particular, they leave out the End statement and only include the signatures
dec-of properties and methods
An interface contains absolutely no real code It’s a bare skeleton that describes the members needed to support a specific feature or procedure The
IFileAccess interface requires a class to provide its functionality—opening and closing a file, and loading and saving data In that respect, an interface is very similar to an abstract MustInherit class that is entirely composed of empty
MustOverride methods
To use an interface in a class, you use the Implements statement A class can implement as many interfaces as it needs However, the class needs to provide its own code for every member of the implemented interface Con-sider this example:
Public Class PersonData Implements IFileAccess End Class
Trang 9As soon as you enter this information in Visual Studio, the IntelliSense feature will underline the word IFileAccess to indicate that your class cannot
be considered complete, because one or more member declared in the
IFileAccess interface has not been defined in PersonData (see Figure 6-8)
Figure 6-8: An unimplemented interface
To fully implement an interface, you need to provide code for every method Here is an example of how you would implement the Open() method:
Public Sub Open(ByVal FileName As String) Implements IFileAccess.Open
' (Code omitted.) End Sub
Implement line, and then press ENTER It will automatically fill in every required method for you, with the correct accessibility, data types, and so on Of course, it’s still
up to you to add the code.
Inheritance Versus Interfaces
The difference between inheritance and interfaces is that inheritance is used
to share code, whereas interfaces are used to standardize it Interfaces antee that several classes all present a standard set of methods to their clients, even when the implementation details are class-specific In the IFileAccess
guar-example, interfaces are a good idea, because while saving data to and loading data from a file is a common operation that several classes need to support, the code for the SaveData() and LoadData() methods will depend on the type of data being saved and loaded, and vary from class to class (Of course, these methods might themselves use a common NET component or a custom file access class to take care of some of the heavy lifting and reuse some code.)
Trang 10Using Interfaces
If you are new to interfaces, you’re probably wondering why you should use them at all when they clearly require so much work Unlike inheritance, interfaces do not let you reuse blocks of code; in order to share code, you have to make careful use of utility functions or inheritance in addition to interfaces Interfaces also have the drawback of inflexibility (you always have
to implement every member of the interface) and extra syntax (every member
definition requires an additional Implements statement to match it to the method, property, or event that it implements) So what benefit does an interface provide?
In fact, interfaces aren’t nearly as crazy as they look First of all, you need
to understand that interfaces are not designed to solve problems of code reuse Instead, an interface is a contract that guarantees that a class offers a certain set of features A client program may not know anything about a new SalesInfo
class, but as long as it knows that the class implements the IFileAccess face, it knows that the class provides LoadData() and SaveData() methods (and how they are used) Or, consider a Microwave and a ToasterOven object, both of which use a similar control panel to cook food This could be modeled as an interface (ICookFood), which would allow a client to make dinner without necessarily needing to use any microwave-specific or toaster oven–specific functions
inter-Inheritance provides a similar standardization mechanism As you learned earlier, you can cast an object to its parent’s type to provide a get access to a basic set of functions and features (For example, you can treat any Person-derived class as a Person.) The same is true with interfaces
Imagine the following class, which implements the IFileAccess interface:
Public Class SalesInfo Implements IFileAccess ' (Code omitted.) End Class
The SalesInfo class is standardized according to the IFileAccess interface
As a result, you could pass a SalesInfo object to any method that understands the IFileAccess interface Here’s an example:
Public Sub PrintFileInfo(DataObject As IFileAccess) ' Interact with the object (which is a SalesInfo in this example) ' through the IFileAccess interface.
DataObject.Open() DataObject.LoadData() ' (Printing code omitted.) DataObject.Close()
End Sub
Trang 11In other words, objects of any class that implements IFileAccess can be cast
to IFileAccess This allows generic functions to be created that can handle the file access features of any class For example, the PrintFileInfo() method could handle an EmployeeInfo, CustomerInfo, or ProductInfo object, as long as these classes all implement the IFileAccess interface
Interfaces and Backward Compatibility
Interfaces are designed as contracts Therefore, once you specify an ace, you must be extremely careful how you change it One fairly innocent change is to add a new method Although you’ll need to track down all the classes that implement the interface and add the new method, you won’t
interf-need to change the code that uses these objects, because all the original
methods are still valid
However, it’s much more traumatic to remove existing methods from an interface or to change their signatures For example, if you decide you need
to replace the LoadData() and SaveData() methods with versions that require different parameters (for example, a version that takes an additional para-meter specifying a key for data encryption), existing programs that use the original IFileAccess interface may stop working The problem is that the orig-inal version of the method—the one the code is attempting to use—won’t be around anymore (To avoid this problem, keep to the first rule—add new methods, but don’t remove the original versions of the method.)
Some developers choose to never change anything about an interface
once it’s released into the wild They won’t even make “safe” changes (like adding new methods) If they absolutely have to modify the interface, they will create a whole new interface, like IFileAccess2 Your classes can imple-ment both, and clients will have a choice of which to use:
Public Class SalesInfo Implements IFileAccess, IFileAccess2 ' (Code omitted.)
End Class
In such a situation, when two interfaces overlap (i.e., they each declare a method with the same name and signature), you can create a single method that satisfies both interfaces:
Public Sub Open(ByVal FileName As String) _ Implements IFileAccess.Open, IFileAccess2.Open ' (Code omitted.)
End Sub
All this interface-based programming may seem like a lot of extra work, and it sometimes is You may therefore be tempted to forget about backward compatibility and just recompile all the programs that use a class whenever you change one of the interfaces it uses In truth, that isn’t such a bad idea
In many cases it may even be the best idea However, this is only valid when
Trang 12you are developing a class for use in programs over whose development and maintenance you have total control Once your class is distributed as a stand-alone component and used in third-party applications, you can’t modify it without possibly breaking client applications.
Using Common NET Interfaces
Interfaces aren’t just used for backward compatibility They also allow a class
to standardize a range of feature sets from which clients may choose ber, a class can only inherit from one parent class, but it can implement all the interfaces it needs
allows different developers to code each class independently As long as developers of the class and of its clients both stick to the rules of the interfaces and interact only through these interfaces, their code is guaranteed to be compatible.
Interfaces are used extensively in the NET Framework, and they represent the handiwork of master OO designers For example, arrays implement the ICloneable interface (to provide every array with a Clone()
method that copies the elements of an array), the IList and ICollection faces (which allow the array to act like a collection), and the IEnumerable
inter-interface (which allows the array to provide For Each enumeration support) The next few sections present some of NET’s most useful interfaces
NOTE To see these interfaces in action, try out the InterfaceTester project with the code for this
chapter.
Cloneable Objects
Each instance of every class that you create has the built-in intelligence to create a copy of itself It acquires this feature from the MemberwiseClone()
method, which every class inherits from the base System.Object class
However, client code can’t access this method directly, because it is marked as Protected, which means it’s available to code inside the class, but not to code using the class This is designed to impose some basic restrictions
on object copying As you’ll see, the way the MemberwiseClone() method works isn’t suitable for all classes, so NET forces you to go through a little bit of extra work to enable it
You could create your own public class method that uses MemberwiseClone() The recommended approach, however, is to use the ICloneable interface, which is designed for just this purpose By using the ICloneable interface, you guarantee that other pieces of code will know how to copy your objects, even if they’ve never seen them before
The ICloneable interface defines a single method, Clone() Your object vides the code for this method, which should use the internal MemberwiseClone()
pro-method to perform the copy (and possibly add some additional logic)
Trang 13Here’s a Clone() method that could be used for the Person class:
Public Class Person Implements ICloneable ' (Other code omitted.) Public Function Clone() As Object Implements ICloneable.Clone ' Return a duplicate copy of the object using the
' MemberwiseClone() method.
Return Me.MemberwiseClone() End Function
End Class
Clients of a Person object can then call its Clone() method and use the CType()
function to cast the result to the appropriate type
NOTE The ICloneable interface and the Clone() method need to be flexible enough to work
with any class For that reason, the Clone() method returns a generic Object This design allows it to support every type of object, because no matter what object you create, you can always cast it to the Object type However, it also forces the client using the
Clone() method to do a little extra work and cast the object back to the correct type It’s a small price to pay to have a standardized object-cloning system.
Dim CurrentPerson As New Person("Matthew", "MacDonald") Dim NewPerson As Person
' Clone the author.
NewPerson = CType(CurrentPerson.Clone(), Person)
Cloning Compound Objects
One reason the MemberwiseClone() feature is hidden from client use is the difficulty involved in cloning a compound object For example, consider the following cloneable Family class:
Public Class Family Implements ICloneable ' (Other code omitted.) Public Mother As Person Public Father As Person Public Function Clone() As Object Implements ICloneable.Clone Return Me.MemberwiseClone()
End Function End Class
Trang 14When this object is cloned, the Mother and Father object references will be copied That means that a duplicate Family class will be created that refers to the same Mother and Father To modify this behavior so that the contained
Mother and Father objects are also cloned (as shown in Figure 6-9), you would need to add additional code:
Public Function Clone() As Object Implements ICloneable.Clone Dim NewFamily As Family
' Perform the basic cloning.
NewFamily = Me.MemberwiseClone() ' In order for this code to work, the Person object ' must also be cloneable.
NewFamily.Father = Me.Father.Clone() NewFamily.Mother = Me.Mother.Clone() Return NewFamily
End Function
Here’s an example that tests the Family.Clone() method:
' Create the family, with two parents.
Dim TestFamily As New Family() TestFamily.Mother = New Person("Lucy", "Smith") TestFamily.Father = New Person("Joe", "Xamian") ' Clone the family This also duplicates the two referenced ' Person objects (the mother and father).
Dim FamilyCopy As Family = CType(TestFamily.Clone(), Family)
Figure 6-9: Two ways to clone
Family Object
Cloned Family Object
Family Object
Cloned Family Object
DEFAULT CLONING
Father
Object
Mother
Object
Trang 15NOTE In this example, it’s fairly obvious that the Family class is a compound object
Com-pound objects are not always this recognizable For example, an object might store information in a collection or an array Collections and arrays are both reference types, which means that MemberwiseClone() will copy the reference, not duplicate the object You need to be extra vigilant when adding cloning capabilities to a class in order to avoid this sort of subtle error.
Disposable Objects
You’ll remember from the last chapter that because of the nondeterministic nature of garbage collection, you can’t count on code that runs when an object is destroyed to be executed immediately If your class uses a limited resource (like a file or a database connection) that should be released as soon as an object is done using it, you should provide a method that allows this resource to be released explicitly The recommended, standardized way
to do this is to implement the IDisposable interface, which contains a single method called Dispose()
Public Class PersonFile Implements IDisposable Public Sub New() ' (Add code here to open the file.) End Sub
Public Sub Dispose() Implements IDisposable.Dispose ' (Add code here to release the file.)
End Sub ' (Other code omitted.) End Class
There’s not much to using IDisposable Just remember that the calling code needs to call the Dispose() method It will not be invoked automatically
A good safeguard is to use VB’s new Using block with any disposable object Here’s an example:
Dim PFile As New PersonFile() Using PFile
' Perform normal tasks with the PFile object here.
End Using ' When your code reaches this point, PFile.Dispose() is ' called automatically.
The nice thing about the Using block is that it guarantees Dispose() will be called (if the object implements IDispose; otherwise, the Using block doesn’t
do anything) Even if an unhandled exception occurs inside the Using block, the Dispose() method is still called, ensuring proper cleanup This is a great way to make sure limited resources (like database connections or file handles) are always released, even in the event of an unexpected error
Trang 16The only disadvantage to the Using block is that if you have several objects you need to dispose of, this can lead you to write a stack of confusingly nested Using statements In this case, a better solution is to use the Finally
section of an exception-handling block (see Chapter 8) to perform your disposal
For even more compact code, you can create an object at the same time you start your Using block:
Using PFile As New PersonFile() ' Perform normal tasks with the PFile object here.
End Using
Comparable Objects
By implementing the IComparable interface, you allow NET to compare objects based on your class One common reason that you might want to implement IComparable is to allow your classes to be sorted in an array or collection
The IComparable interface has a single method called CompareTo() In the
CompareTo() method, your code examines two objects based on the same class and decides which one is greater The CompareTo() method then returns one
of three numbers: 0 to indicate equality, –1 to indicate that the compared object has a value less than the current object, or 1 to indicate that the compared object is greater than the current object It’s up to you to decide what it means for one class to be “greater” than another For example, you might compare numeric data, strings, some other data, or a combination of variables
Following is an example that shows how you can implement custom comparisons involving Person objects In this case, the programmer decided that the criterion for comparing Person objects would be their age Thus, in a sorted Person list the youngest people would appear first, and the oldest would appear last
Public Class Person Implements IComparable ' (Other code omitted.) Public Function CompareTo(ByVal Compare As Object) As Integer _ Implements IComparable.CompareTo
Dim ComparePerson As Person = CType(Compare, Person)
If ComparePerson.BirthDate = Me.BirthDate Return 0 ' Represents equality.
ElseIf ComparePerson.BirthDate < Me.BirthDate ' The compared object's age is greater than the current object ' (Remember, the greater the birth date, the smaller the age.) Return 1
ElseIf ComparePerson.BirthDate > Me.BirthDate ' The compared object's age is less than the current object Return -1
End If
Trang 17End Function End Class
If you want, you can use the CompareTo() method directly in code ever, the built-in Sort() method in the Array class recognizes the IComparable
How-interface and uses the CompareTo() method automatically Here’s an example that puts it to work:
' Define birth dates for a 45, 28, and 5 year old.
DateTime BirthDate1, BirthDate2, BirthDate3 As DateTime BirthDate1 = DateTime.Now.AddYears(-45)
BirthDate2 = DateTime.Now.AddYears(-28) BirthDate2 = DateTime.Now.AddYears(-5) ' Create an array with three people.
Dim GroupOfPeople() As Person = {New Person("Lucy", "Smith", BirthDate1), _ New Person("Joe", "Xamian", BirthDate2), _
New Person("Chan Wook", "Lee", BirthDate3)}
' Sort the array, so that the people are ordered from youngest to oldest: Array.Sort(GroupOfPeople)
this case, you need to create separate sorting classes (like SortByName , SortByDate , and
so on) Each of these sorting classes will implement the IComparer interface This face is similar to IComparable and provides one method, CompareTo() , that compares two objects and returns an integer indicating 0, 1, or –1 To use a special sorting method with the Array class, use the overloaded Sort() method that allows you to specify an additional parameter with the appropriate IComparer object.
inter-Collection Classes
Inheritance is a relatively strict type of relationship, referred to as an is-a
relationship For example, most would agree that a Politician is a Person However, there are many other types of relationships in the world of objects
One of the most common is the has-a relationship—and as many can attest,
in the materialistic world of today, what a person has is often more important than who they are The same is true for classes, which can contain instances
of other classes, or even entire groups of classes This section discusses the
latter case, and introduces the collection class, which is an all-purpose tool for
aggregating related objects, particularly for inclusion in a class
A collection is similar to an array, but much more flexible An array requires that you specify a size when you create it A collection, on the other hand, can contain any number of elements, and allows you to add or remove items as you see fit An array requires that you specify the data type of the information it will contain (or specify Object, if you want the array to hold variables of different data types) A collection can contain any type of object Lastly, while an array uses an index to identify its elements, a numeric index
Trang 18arbitrarily Instead, when you need to find a specific item in a collection, you either search through the collection until you find it or refer to it by a key that you specified for the item when you added it to the collection.
called dictionaries, because they store information under specific key headings, like a dictionary.
A Basic Collection
Here’s a code snippet that creates and uses a simple collection:
' Create a Person object.
Dim Person1 As New Person("Lucy", "Smith") ' Create a collection.
Dim People As New Collection()
' Add the Person to the collection with the key "First"
People.Add(Person1, "First") ' Display the number of items in the collection (currently 1).
MessageBox.Show(People.Count) ' Retrieve the Person from the collection Dim RetrievedPerson As Person = People("First")
A NuclearFamily Class
Now it’s time to jump right into a full-fledged example: the NuclearFamily
class I’ll break down the elements of this example to explain how it uses a collection
Here is the definition for our NuclearFamily class:
Public Class NuclearFamily Public Father As Person Public Mother As Person Public Children As New Collection() Public Sub New(ByVal Father As Person, ByVal Mother As Person) Me.Father = Father
Me.Mother = Mother End Sub
Public Function FindYoungestChild() As Person Dim Child As Person
Dim Youngest As Person For Each Child In Children
If Youngest Is Nothing Then Youngest = Child ElseIf Youngest.BirthDate < Child.BirthDate Then
Trang 19Youngest = Child End If
Next Return Youngest End Function End Class
Here’s the rundown:
The NuclearFamily class has a Father and a Mother variable, which point to corresponding Person objects These variables are defined without the New
keyword This means that blank Father and Mother objects aren’t created when you create a NuclearFamily Instead, you must assign preexisting
Person objects to these variables (In the interest of shorter code, our example takes a shortcut by using variables instead of full property procedures.)
All the children are contained in a collection called Children This lection is defined with the New keyword, because it needs to be created before any Person objects can be added to it
col-A single constructor is used for the class It requires parameters identifying both parents In other words, you won’t be able to create a NuclearFamily
without a Father and Mother Notice that the names of the parameters in the constructor are the same as the names of the variables in the class This may seem confusing, but it’s actually a common convention In this case, the parameter name has priority over the class name, so you need
to use the Me keyword to refer to the class in order to directly access the instance variables from within the function
A FindYoungest() function searches through the Children collection and compares each child until it finds the one with the most recent BirthDate
(and hence, the youngest age)
To use the NuclearFamily class, you need to create and add family members:
' Create four distinct people.
Dim Lucy As New Person("Lucy", "Smith", 43) Dim John As New Person("John", "Smith", 29) Dim Anna As New Person("Anna", "Smith", 17) Dim Eor As New Person("Eor", "Smith", 15) ' Create a new family.
Dim TheFamily As New NuclearFamily(John, Lucy) ' Add the children.
TheFamily.Children.Add(Anna) TheFamily.Children.Add(Eor) ' Find the youngest child.
MessageBox.Show("The youngest is " & TheFamily.FindYoungestChild.FirstName)
Trang 20The result of all this is shown in Figure 6-10.
There isn’t much new material in this example What’s important is the way everything comes together The NuclearFamily class is a compound class that contains two Person objects and its own collection
Figure 6-10: Finding the youngest member
A traditional structured program would probably model a family by creating a bunch of different information Maybe it would keep track of only the children’s birthdays, or even hard-code a maximum number of children Along the way, a structured program would probably also introduce fixed assumptions that would make it difficult to expand the program and keep it clear The NuclearFamily class, on the other hand, is built out of Person objects Every family member is treated the same way, and the family is broken down into equivalent objects You could even create multiple NuclearFamily classes that assign the same family member (Person object) different roles—for example, as a child in one class and a parent in another
In short, everything is consistently well organized We change the Person
class, and any part of our code that uses it automatically benefits Code that deals with people is contained inside the Person class, so we always know where
to find it Life is good
To try out the NuclearFamily class, run the CollectionTester project from the sample code (see Figure 6-11)
Figure 6-11: The NuclearFamily test utility
Trang 21Specialized Collections
The basic Collection class is really a holdover from Visual Basic 6 However, NET developers often use one of several more specialized collections, which can be found in the System.Collections namespace Some popular collection classes include:
ArrayList
This is similar to the Collection class, but it doesn’t support keys It’s just
a collection that dynamically grows (when you use the Add() method) and shrinks (when you call the Remove() method)
Queue and Stack
A Queue is a first-in, first-out collection You call Enqueue() to insert an item
at the end of the queue, and Dequeue() to retrieve the oldest-added item
A Stack uses the same principle, but in reverse You call Push() to add an item to the top of the stack and Pop() to get the item that’s currently topmost
Hold on—before you head off to the System.Collections namespace to check out these specialized collection classes, it’s worth noting that NET 2.0
added yet another set of collections, this one in the System.Collections.Generic
namespace You’ll learn about these collections in the following section
Generic Collections
One drawback with our NuclearFamily.Children collection is that it isn’t safe Though our respectful program only adds children into the collection, poorly written code could easily throw in strings, numbers, and other objects, which could cause all sorts of problems Remember, when creating a com-ponent, you should always imagine that it is a separate program that may be thrust out into the world on its own and used by a variety of programmers, who may have little knowledge about its inner workings For that reason, a class has to carefully protect itself against incorrect input
type-One way to do this is through property procedures, as you saw in ter 5 In the NuclearFamily class, we could create a property procedure or a special method that accepts an object, checks its type, and then adds it to the
Chap-Children collection if it is a Person However, this approach has a significant drawback Because the collection is no longer directly exposed, the client
doesn’t have any way to iterate through it (Iteration is the handy process that
allows a programmer to use a For Each block to move through all the items in
Trang 22a collection without worrying about indexes or keys.) Another solution is to create a custom collection class This is fairly easy because NET provides a
System.Collections.CollectionBase class from which you can derive subclasses
It has all the tricky stuff built in, so you have relatively little code to add
In fact, The Book of VB NET (No Starch Press, 2002) demonstrated a
custom collection class for Person objects, which you can examine with the downloadable code for this chapter
In VB 2005, you don’t need to take either of these steps That’s because
.NET 2.0 adds a new feature called generics that allows developers to build
more flexible classes One of the first beneficiaries of this change are the collection classes
Thanks to generics, it’s possible to create collections that can support any data type but are instantly “locked in” to the data type you choose when you create them The only trick is that you need to specify that data type using a slightly unusual syntax For example, imagine you want to use the generic List collection class You would change the NuclearFamily class by modifying this line:
Public Children As New Collection()
to this:
Public Children As New System.Collections.Generic.List(Of Person)
The Of Person part in parentheses explains that you want your Children
collection to only accept Person objects From this point on, you won’t be able
to add any other type of object For example, if you write this code:
Children.Add("This is some text, not a Person object.")
You’ll get an error Best of all, you’ll receive the error when you try to compile your application (not later on, when it’s running merrily) That way, your invalid code is stopped cold before it can cause a problem
Another benefit is that you can pull a Person object out of the List tion without needing to use CType() to cast the object For example, with an ordinary collection you need the following code to get the first Person object:
collec-Dim FirstPerson As Person FirstPerson = CType(Children(0), Person)
But with a generic collection, the following simpler code works fine, because the compiler knows your collection is limited to Person objects:
Dim FirstPerson As Person FirstPerson = Children(0)
Trang 23The List class is the generic version of the ArrayList class—it’s a tion that doesn’t use keys You’ll also find a few more generic collections in the System.Collections.Generic namespace They include:
collec-Dictionary
This is a name-value collection that indexes each item with a key, similar
to the Hashtable collection
What Comes Next?
This chapter has discussed a wide range of object-oriented techniques ever, now that you have the knowledge, you still need the experience to learn how to implement object-oriented designs
How-A substantial part of the art of using objects is deciding how to divide
a program into classes That question has more to do with the theory of application architecture than with the Visual Basic 2005 language
For example, you may be interested in learning the basics about three-tier
design Three-tier design is the idea that applications should be partitioned
into three principal levels: the user interface, known as the presentation tier; the business objects or data processing procedures, known as the business tier;
and a back-end data store (a relational database or a set of XML files, for
example), known as the data tier Figure 6-12 shows a diagram of this model.
Three-tier design has caught on because it allows extremely scalable applications With clever design, all three levels can be separated, upgraded
or debugged separately, and even hosted on different computers for a tial performance boost The concepts of three-tier design are also important when you are creating simpler client-server or desktop applications By remem-bering that database access, data processing, and user interface are three different aspects of a program, you can get into the habit of separating these functions into different groups of classes For example, your form classes all reside at the user interface level This means that they shouldn’t contain any code for processing data; instead, they should make use of a business object Similarly, a business object shouldn’t display a message directly on the screen,
poten-or access a fpoten-orm It should be completely isolated from the user interface, and should rely on receiving all the information it needs through method
Trang 24parameters and properties Understanding three-tier design can help you ensure that even your most straightforward programs are more encapsulated and easier to enhance and troubleshoot.
Figure 6-12: Three-tier design
Presentation Tier
Business Tier
Data Tier
Application Window ApplicationWindow
Business Object Business
Object
Database Data Files
Trang 25A S S E M B L I E S A N D C O M P O N E N T S
Some of the most remarkable changes to the way VB developers do business in the NET world stem from the introduction of
assemblies, NET’s catch-all term for executable
application files and compiled components In Visual Basic 6, creating and reusing a component was often tricky, especially if you wanted to share your work with
applications coded in other programming languages Registration hassles and versioning conflicts—which occur when two programs expect different versions of the same component—appear when you least expect them and can take hours to resolve In this chapter, you’ll learn how NET clears out these headaches and offers a better model for sharing components You’ll also learn enough to prepare yourself for Chapter 14, which explores how you can create customized setup programs to deploy your applications