Let's start consolidating your code by creating another class module called BusinessBase in the NorthwindUC project. Because you are taking duplicate code and adding it to this class, this will be an easy and painless process (well, mostly painless). Copy the code for each of the methods and properties that have no differences in Table 8-1 to the new BusinessBase class. When you are done, your BusinessBase class should look like the class in Listing 8-1.
Listing 8-1: The BusinessBase Class
Option Explicit On Option Strict On Public Class BusinessBase Private mblnDirty As Boolean = False Public Loading As Boolean Private WithEvents mobjRules As BrokenRules Public Event ObjectChanged(ByVal sender As Object, _ ByVal e As ChangedEventArgs) Public Event BrokenRule(ByVal IsBroken As Boolean) Public ReadOnly Property IsDirty() As Boolean Get Return mblnDirty End Get End Property Public ReadOnly Property IsValid() As Boolean Get If mobjRules.Count > 0 Then Return False Else Return True End If End Get End Property Private Sub mobjRules_RuleBroken(ByVal IsBroken As Boolean) _ Handles mobjRules.RuleBroken RaiseEvent BrokenRule(IsBroken) End Sub End Class
Before doing anything else, you need to make one decision—do you want to allow a developer to instantiate this class? In this case the answer is easy—no, you do not. The reason is that this is a helper class, and it cannot operate on its own. So before going any further, add the MustInherit keyword between the words Public and Class in the class declaration.
Now you have to change some of the declarations so that your variable and method scopes are OK. For every method or property you need to access in your subclassed object, you need to change scope declarations from Private to Protected. In this case, you need to access only two variables: the mblnDirty variable and the mobjRules variable. So, change the scope from Private to Protected for these two variables. Next, alter just the Region class for right now by having it inherit from the BusinessBase class:
Public Class Region Inherits BusinessBase
This immediately causes several things to be underlined as errors. Do not worry. If you look at the Task List, you will notice that the only errors it shows are that different variables, methods, and events conflict with each other. Simply delete them. Every piece of code you moved to the BusinessBase class needs to be deleted from the Region class because it inherits from the BusinessBase class. Once you are done with this, you will find out that you still have a problem. The error you will see in the Task List is the following:
Derived classes cannot raise base class events.
Uh-oh. How do you fix this? The answer is you cannot, but you can work around it. You can get around it by creating a method in the base class that you can call and have it raise the event for you. In the base class, create a method called CallChangedEvent and code it as follows:
Protected Sub CallChangedEvent(ByVal sender As Object, _ ByVal e As ChangedEventArgs) RaiseEvent ObjectChanged(sender, e) End Sub
Next, in the Save method of the Region object (because that is where the errors occurred), change the two RaiseEvent statements to look like the following:
If intID = 0 Then CallChangedEvent(Me, New _ ChangedEventArgs(ChangedEventArgs.eChange.Added)) Else CallChangedEvent(Me, New _ ChangedEventArgs(ChangedEventArgs.eChange.Updated)) End If
This simple change eliminates your problem, so you are good to go.
Now, perform all of these steps for the Territory class (with the exception of the intID, which is not needed in the Territory class). Your base business class now consists of 27 lines of code that you never need to write again. And you have already, with just these two classes, reduced the amount of code by 27 lines. Although this does not seem like a lot, consider a project with 100 classes (which is still a fairly small amount of classes)—this would then have reduced the number of lines of code by 2,700 lines! That is a big difference. But you can do better still.
The GetBusinessRules method is redundant. Although it does call a different remote object, that is just a value that you append onto the end of the REMOTEOBJECTS path. If you look at the GetBusinessRules method in the Region class, you will see the code in Listing 8-2.
Listing 8-2: The GetBusinessRules Method of the Region Class
Public Function GetBusinessRules() As BusinessErrors Dim objIRegion As IRegion Dim objBusRules As BusinessErrors objIRegion = CType(Activator.GetObject(GetType(IRegion), _ AppConstants.REMOTEOBJECTS & LISTENER), IRegion) objBusRules = objIRegion.GetBusinessRules objIRegion = Nothing Return objBusRules End Function
You have used the IRegion interface to get a reference to the GetBusinessRules function of the remote objects. You definitely want to consolidate this call because it operates on an object and is not really a part of an object. To do this, you need to make a change to your Region and Territory interfaces. Go to the Interfaces code module in the NorthwindShared project. Listing 8-3 shows the Region and Territory interfaces.
Listing 8-3: The Region and Territory Interfaces
Public Interface ITerritory Function LoadProxy() As DataSet Function LoadRecord(ByVal strID As String) As structTerritory Function Save(ByVal sTerritory As structTerritory) _ As BusinessErrors Sub Delete(ByVal strID As String) Function GetBusinessRules() As BusinessErrors End Interface Public Interface IRegion Function LoadProxy() As DataSet Function LoadRecord(ByVal intID As Integer) As structRegion Function Save(ByVal sRegion As structRegion, ByRef intID As Integer) _ As BusinessErrors Sub Delete(ByVal intID As Integer) Function GetBusinessRules() As BusinessErrors End Interface
The similarities are immediately obvious upon comparing the two interfaces. Both the LoadProxy and GetBusinessRules methods are identical. To consolidate these methods, add a new Interface called IBaseInterface and add the method signatures to it as shown in Listing 8-4.
Listing 8-4: IBaseInterface
Public Interface IBaseInterface Function LoadProxy() As DataSet Function GetBusinessRules() As BusinessErrors End Interface
Next, alter the IRegion and ITerritory interfaces so they both inherit from IBaseInterface. Delete the LoadProxy and GetBusinessRules methods from each of these interfaces. At this point you should not have any errors. Now, what does this have to do with consolidating the GetBusinessRules method in your objects? Now that the GetBusinessRules method is included in a base class, your call on the GetBusinessRules method can be performed on the IBaseInterface interface instead of on the IRegion or ITerritory interfaces. This allows you to create a generic routine that can make this call as long as the interface inherits from the IBaseInterface interface.
First, add the following two Imports statements to the top of the BusinessBase code module:
Imports NorthwindTraders.NorthwindShared.Interfaces Imports NorthwindTraders.NorthwindShared.Errors
Move the GetBusinessRules method to the BusinessBase class and alter the code so that it is identical to the code in Listing 8-5.
Listing 8-5: The GetBusinessRules Method
Public Function GetBusinessRules() As BusinessErrors Dim objInterface As IBaseInterface Dim objBusRules As BusinessErrors objInterface = CType(Activator.GetObject(GetType(IBaseInterface), _ AppConstants.REMOTEOBJECTS & LISTENER), IBaseInterface) objBusRules = objInterface.GetBusinessRules objInterface = Nothing Return objBusRules End Function
Now you still have one problem here—the Activator.GetObject line, in particular the Listener constant. You do not have a listener constant in the base class and you do not have a value to put into it at this point anyway. You need to make this dynamic, but how? The answer lies in the constructor that you will create for this class. Add the following private variable and constructor to the BusinessBase class as shown in the following code:
Private mstrListener As String Public Sub New(ByVal RemotePath As String) mstrListener = RemotePath End Sub
And alter the Activator.GetObject line to read as follows:
objInterface = CType(Activator.GetObject(GetType(IBaseInterface), _ AppConstants.REMOTEOBJECTS & mstrListener), IBaseInterface)
This causes several errors also, so let's go back and edit the constructors for the Region object so they read as follows:
Public Sub New() MyBase.New(LISTENER) mobjRules = New BrokenRules() mobjRules.BrokenRule("Region Description", True) End Sub Public Sub New(ByVal intID As Integer) MyBase.New(LISTENER) mobjRules = New BrokenRules() mintRegionID = intID End Sub
Notice that both methods now have a new first line that reads as follows:
MyBase.New(LISTENER)
Make the same change for the Territory object so that the first line of its constructors now reads as follows:
MyBase.New(LISTENER)
The last two changes are fairly simple: Delete the GetBusinessRules method from the Region and Territory classes and move the mobjRules = New BrokenRules() statement from the constructors in the Region and Territory classes to the constructor in the BusinessBase class. At this point you should be able to run the application with no problems at all. Your count of reusable code lines is now up to 41. And with that, you are done consolidating the user-centric classes.
Note | The purpose of doing this is not just so that you reuse code. Although that is a laudable goal itself, it also allows you to place code that does one thing in one place. But more importantly than that, it gives your application a true, overall architecture. Every type of object in your application will now behave in substantially the same way. This makes maintenance a lot easier, and it makes adding new functionality to the entire application easier. |