Creating a Smart Base Factory Pattern in .NET

in #utopian-io8 years ago (edited)

What Will I Learn?

In this tutorial, we will implement a slight twist on a standard factory pattern. We want to enable our client code to choose upon one of possibly many available concrete subclass implementations. The factory will provide the client code with a list of available objects that subclass our abstract base class and then the client will choose which one of these they want to use.

Requirements

.NET Framework

Difficulty

Intermediate

Creating a Smart Base Factory Pattern in .NET

Factory design patterns in .Net are a common approach used to help decouple object creation and instantiation from the client. There are various versions of the factory pattern such as abstract, simple, and builder patterns, but the basic idea is the same: the client calls upon a specialized object (the abstract) whose purpose is to create other objects (concrete objects) on behalf of the client. Depending on the specific pattern, the factory object either returns the newly created concrete object or calls upon a method of that object to do the actual work. The end result is that the client is decoupled from the instantiation of the concrete object. Over time, as applications evolve and change, the decoupled approach allows for new objects to replace some or all implementation of old objects with virtually no change to any client code. Any code change would only need to take place in the factory.

In this tutorial, we will implement a slight twist on a standard factory pattern that we’ll call the Smart Base Factory Pattern. This special factory object will combine features of abstract classes and factory patterns into a special object used in the following scenario: We want to enable our client code to choose upon one of possibly many available concrete subclass implementations. The factory will provide the client code with a list of available objects that subclass our abstract base class and then the client will choose which one of these they want to use. The real trick here is that we ultimately want the ability to simply drop new objects of this type onto the client and have them available for immediate use, all without a single recompile of the client code or the factory!

In case its unclear how this might be useful, suppose our client application needs to perform a complicated calculation. Also, suppose this calculation is at best an estimation of some degree of accuracy. Various versions of algorithms might currently exist to perform this calculation and we’ve wrapped these algorithms up into objects that inherit from our base calculation type. Each of these algorithms might take varying amounts of time to process based on their complexity and degree of accuracy. Some of the algorithms might require external data to assist in their calculation process and call upon Web Services for assistance. Our client application should be free to choose which of these objects to employ based upon their current need for speed versus accuracy tradeoffs. Maybe the user is doing some early analysis and speed is more important that accuracy. Perhaps the client is offline and is unable to use the object that requires external data and processing. Later on, the client might be doing highly specialized work requiring a high degree of accuracy and is willing to spend more time calculating to achieve these results. Based on this description, we want the client to be fully capable of choosing a calculation engine that best meets their current needs. Additionally, it is very likely that over time, new algorithms will come along that present new speed versus accuracy metrics that we would like to make available to the client application as painlessly as possible.

This description should demonstrate a situation where it would be useful to have an object Factory design that deviates slightly from the norm, but still continues to support a decoupled approach in which our client code is not locked to any one particular concrete implementation. Concrete implementations could come and go, and the client code would never require modification or a recompile. We deviate from the "factory" norm however in that the client will be making an informed decision on which object to request from the factory. Generally, this decision would be encapsulated in the factory and made on behalf of the client based on the particular method call, method parameters, or configuration. Instead, our Smart Base Factory will provide information to the client about what objects are available and then instantiate and return a specific object based on the client’s request for it. The Smart Base Factory will also be dynamically capable of instantiating and providing information on new or removed concrete objects.

We're calling the factory pattern a Smart Base Factory because it will a) act as the abstract base that our concrete objects will subclass, b) provide some shared factory methods, and c) be knowledgeable (smart) and dynamically configurable which objects are available for instantiation. It will also be smart in the fact that it will act as the object in charge. It will encapsulate the knowledge of how to instantiate its subclasses and will be the only object that is designed to do this.

To keep things a little more fun then the above mentioned "complicated calculation engines," we're going to create objects in the tutorial that represent motorcycles. Each of the motorcycles has various performance and physical characteristics, and depending upon what the client application is doing, it will need the ability to choose which cycle best meets its current needs.

First, let's create our abstract motorcycle type. For now, we’ll just create the basics of the abstract class and then come back and add the shared factory and helper methods. Let's create a project called MotorcycleFactory, and add a class called Motorcycle. This class will be our abstract class, and will initially look like this:

 Public MustInherit Class Motorcycle ' events
Public Event IsRunningChanged As EventHandler ' properties
Public MustOverride ReadOnly Property Name() As String
Public MustOverride ReadOnly Property Horsepower() As Single
Public MustOverride ReadOnly Property Torque() As Single
Public MustOverride ReadOnly Property FuelCapacity() As Single
Public MustOverride ReadOnly Property Weight() As Short Private mIsRunning As Boolean = False
Public ReadOnly Property IsRunning() As Boolean
Get
Return mIsRunning
End Get
End Property ' methods
Public MustOverride Sub Initialize(ByVal criteria As Object)
Public Sub StartEngine()
      mIsRunning = True
RaiseEvent IsRunningChanged(Me, Nothing)
End Sub
Public Sub StopEngine()
      mIsRunning = False
RaiseEvent IsRunningChanged(Me, Nothing)
End SubEnd Class

We really haven’t done too much here that couldn't have been done in a simple interface other then the StartEngine and StopEngine methods. However, we’ll assume that a real world example would provide some actual base class implementation code. When we get back to adding the shared factory and client helper methods, we’ll see why we choose to create an object instead of just an interface. The only real noteworthy item in the abstract class so far, is a virtual method called Initialize (ByVal criteria as object). We'll use this method to pass any start-up/initialization data to our subclass. As you'll see shortly, we will be using a private empty constructor in the subclasses. Therefore, we won’t be able to pass any default data to the subclasses via overloaded constructors. Instead, our base class will call this Initialize method immediately after instantiating a subclass, and will use it to pass in default data. This is an example of our base class being "Smart." It knows that this method will need to be called, it knows when to call it, and knows what type of data will need to be passed to it. Our client might not know this, and it shouldn’t need to. This is another example of decoupling logic from the client and encapsulating it in the Smart Base Factory class.

Now, let's create a few subclasses that inherit from this class. These objects will be specialized versions of motorcycles. In other words, they are concrete implementations of our abstract motorcycle. So, let's create a new project for our first concrete motorcycle and call it SportTourer. Add a reference to the MotorcycleFactory project, and then add a new class to the SportTourer project and call it K1200RS:

Public Class K1200RS 
Inherits Motorcycle Private Sub New()
End Sub Protected Overrides Sub Initialize(ByVal criteria As Object)
      mName = CType(criteria, String)
End Sub Private mName As String = ""
Public Overrides ReadOnly Property Name() As String
Get
Return mName
End Get
End Property Public Overrides ReadOnly Property Horsepower() As Single
Get
Return 130
End Get
End Property Public Overrides ReadOnly Property Torque() As Single
Get
Return 85
End Get
End Property Public Overrides ReadOnly Property FuelCapacity() As Single
Get
Return 5.4
End Get
End Property Public Overrides ReadOnly Property Weight() As Short
Get
Return 614
End Get
End Property End Class

This is actually a pretty useless hardcoded object, but we’re creating it to demonstrate the ultimate goal of the object factory. The one point of interest in this motorcycle subclass is that the constructor is marked private. This will prevent client code from instantiating concrete motorcycle objects directly. Instead, the client will need to call a shared method (which we haven’t yet created) in the Smart Base Factory. Our factory will know how to create concrete objects even though the constructor is marked private. As mentioned above, our Smart Base Factory will set some defaults and/or some business initializations upon instantiation through the Initialize method. In this example, we are just passing a string to the object that will be used to set the object’s name property. The idea here is that because the base class is also acting as a shared object factory, it may have already gone to a database, configuration file, or some other data storage during its lifetime, and may have cached information that our subclass needs upon start-up. We’ll use the Initialize method to get this kind of data to the subclass.

Next, we’ll create two more projects that will each house additional motorcycles. The reason we are creating new projects for each of these motorcycles is so each motorcycle will be part of a separate assembly. With the model we are building, we could actually create all of these objects in a single assembly. But the impact of the separate assembly approach will be more dramatic in the end. So let's create two more projects, and call them Tourer and Cruiser. These projects will each contain a single class. Add a class to Tourer called R1150RT and a class to Cruiser called R1200C. The code for each will be the same as above with the only exception being the hardcoding of the respective motorcycle metrics. For the R1150RT class, we will hardcode the following return values:

Horsepower= 95, Torque = 74, FuelCapacity = 6.6, Weight() = 641

For the R1200C class, we’ll hardcode the return:

Horsepower= 61,Torque = 71, FuelCapacity = 4.6, Weight() = 581

Now that we have created some motorcycles, let's go back to the MotorcycleFactory project. We’ll now begin to build the special factory methods. First, let’s create a method to return to the client a list of available motorcycles for instantiation. The idea here is that we want the client to be able to choose which motorcycle object to create out of a list of available objects. This function will provide the client with that list. Let's add a shared function to the abstract motorcycle class called GetAvailableMotorcycleTypes:

Public Shared Function GetAvailableMotorcycleTypes() As ArrayList Dim motorcycles As New ArrayList()
' get the name of the subdirectory containing the concrete motorcycle objects
Dim subDir As String
   subDir = System.Configuration.ConfigurationSettings.AppSettings("motorcycleDirectory") ' create a directoryInfo object representing the motorcycle subdirectory
Dim dirInfo As System.IO.DirectoryInfo
   dirInfo = New DirectoryInfo(System.AppDomain.CurrentDomain.BaseDirectory & subDir) ' get a listing of all the .dll files in the motorcycle directory.
Dim files() As System.IO.FileInfo
   files = dirInfo.GetFiles("*.dll") ' iterate through the array of assembly files and look for any Motorcycle types
Dim motorcycleAssembly As [Assembly]
Dim motorcycleType, motorcycleTypes() As Type
Dim i As Integer
For i = 0 To files.Length - 1 ' load up each assembly by its file name, and then get all of the
' types in that assembly.
      motorcycleAssembly = [Assembly].LoadFrom(files(i).FullName)
      motorcycleTypes = motorcycleAssembly.GetTypes ' add any motorcycle types in the assembly into the result ArrayList
For Each motorcycleType In motorcycleTypes
If motorcycleType.BaseType.Equals(GetType(Motorcycle))Then
            motorcycles.Add(motorcycleType)
End If
Next motorcycleType
Next i ' return an ArrayList of types containing all of the available Motorcycles
Return motorcycles
End Function


.NET really makes doing this kind of work pretty simple. With just a few lines of code, we are able to provide a dynamic list of available objects to the client. Keep in mind that the Smart Base Factory will not have a single compiled reference to any of our concrete subclasses, but yet via this small function will always be able to provide the current list of available types to the client. First, we get a name of the subdirectory in which our compiled concrete objects will be placed. We’ll store this subdirectory name as a string in our app.config (or web.config) file. We will load up each assembly in this directory and inspect the classes within each assembly to see if they are of our desired base class type. Because of this, it will be better to keep the compiled concrete objects in their own directory. Otherwise, we might be loading up and inspecting a lot of unnecessary assemblies. Once we get the name of the subdirectory from the config file, we create a DirectoryInfo object and point it to the subdirectory. Next, we get an array of all the *.dll files in the directory. We then iterate through each of these files and load the respective assembly they represent. Once we have the assembly loaded, we can get all the types (classes) in the assembly and inspect each type for its base class. If the type is of our base class, we’ll add this type to our result ArrayList. Once, we’ve inspected each type in each of the assemblies in the directory, we’ll return the ArrayList of types to the client. This ArrayList of types can then be bound to a ComboBox or presented through some other user interface means.

This method can be optimized as you see fit. I tried to keep the code to a minimum in the tutorial to demonstrate the core principals. The important thing to take away is that we are populating a list of available concrete objects from some sort of dynamic method. A common thing I do in practice is store the list in a shared variable, populate it only once, and cache it for future calls. The downside to that approach is when we drop new objects into the directory, the client will need to shut down and restart their app before they see the new objects as available.

Next, we’ll create the method that the client will call to actually instantiate a new motorcycle object. Earlier I mentioned that each of the concrete motorcycle classes has a private constructor to prevent direct client instantiation. This function will provide the client the means to create their desired motorcycle object, despite the private constructor. Once the client has selected which object to instantiate, they will need to call on this method to perform that instantiation.

Add another shared function to the abstract Motorcycle class called NewMotorcyle:

Public Overloads Shared Function NewMotorcycle(ByVal requestedMotorcycle As Type) As Motorcycle ' instantiate the requested object
Dim cycle As Motorcycle
   cycle = CType(Activator.CreateInstance(requestedMotorcycle, True), Motorcycle) ' initialize the new object with any required start-up data
Dim criteria As String
   criteria = requestedMotorcycle.Name
   cycle.Initialize(criteria)
Return cycle
End Function

This is another great example of .NET making our life easy. Literally with a couple lines of code, we are able to accomplish a pretty significant feat. Our client app and our Smart Base Factory have no compiled references to any concrete motorcycle objects, yet the client is able to get a dynamic list of available objects, select one to use, and then call this method to instantiate a new object. To accomplish this, we’ll use the System.Activator’s CreateInstance method. The purpose of the Activator class is to create instances of objects both locally and remotely. In this case, we will be calling an overloaded version of the CreateInstance method that allows us to create a local object by passing the requested type as an argument as well as a flag indicating that it’s OK to use a default private constructor. Generally, we would not be able to instantiate an object directly with a private constructor, but this class makes it possible, and our Smart Base Factory knows that it will need to do this. Finally, once we have an instance of this object, we call its Initialize method, passing it any necessary initialization criteria (in this case the name property.)

Next, we'll create an overloaded version of the NewMotorcycle method that will let us create a motorcycle with just its name string instead of its type. Based on the type of user interface interacting with the factory, it might be necessary (or at least convenient) to only provide the name of the requested object. Add another function to the abstract Motorcycle class also called NewMotorcyle:

Public Overloads Shared Function NewMotorcycle(ByVal motorcycleName As String) As Motorcycle ' this overload allows the client to request a motorcycle by name
Dim motorcycleType As Type
For Each motorcycleType In GetAvailableMotorcycleTypes()
If motorcycleType.Name.ToLower = MotorcycleName.ToLower Then ' call the overloaded NewMotorcycle method
Return NewMotorcycle(motorcycleType)
End If
Next
Throw New ApplicationException("The requested Motorcycle doesn't exist.")
End Function

First, we call upon our GetAvailableMotorcycleType method to get a list of the available types. We then iterate through the list and compare each type’s name to the name string passed in to us. Once we find the matching type, we’ll use that type to call the original NewMotorcycle function.

At this point, we’ve achieved our original goals. We’ve created an abstract base class that contains a few shared factory methods that enable it to act like a Smart Base Factory. The object is “Smart” in that it is able to provide the client with a dynamic list of available objects through the GetAvailableMotorcycleTypes method. It also encapsulates the overall process of object instantiation including the knowledge of any default data that needs to be passed to the object through the NewMotorcycle method. The client is completely decoupled from this process, relying on the factory for the list of available types and the entire object’s instantiation/initialization process. We can even make brand new object types available to the client without requiring a recompile of existing code anywhere, not even in the factory! All we do is create an object that inherits from the Motorcycle base class, and then drop the new object's assembly in the Motorcycle subdirectory that we've named in the config file.

To use this Smart Base Factory, we’ll create a small Windows Form application. Create a new project called MotorcycleClient, add a new Windows Form called Main.vb, and remove the Form1.vb file. Within MotorcycleClient, add a reference to the MotorcycleFactory project, but not to the individual motorcycle projects. Next, we’ll add a ComboBox (cboMotorcycles — that we’ll eventually populate with the list of available motorcycles), and we’ll add a bunch of ReadOnlyTextBoxes used to display the properties of the selected motorcycles. We’ll populate the ComboBox in the form's Load event handler.

Private Sub Main_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load ' populate the combo list of available motorcycle objects
   cboMotorcycles.DataSource = Motorcycle.GetAvailableMotorcycleTypes
   cboMotorcycles.DisplayMember = "DisplayName"
End Sub

Because the hard work has already been done in the Smart Base Factory, we don’t have much code at all to write in the user interface. We simply set cboMotorcycle's datasource to the results of the shared factory method GetAvailableMotorcycleTypes.Next, when the user selects a motorcycle from the ComboBox, we’ll call upon the shared factory method called NewMotorcycle to instantiate this object. Then we’ll bind the text boxes on the form to the newly instantiated object.

Private Sub cboMotorcycles_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)          Handles cboMotorcycles.SelectedIndexChanged ' Get the requested cycle type from the ComboBox
Dim cycleType As Type
   cycleType = CType(cboMotorcycles.SelectedItem, Type)  ' call upon the shared factory method to create our requested cycle type
   mCycle = Motorcycle.NewMotorcycle(cycleType) ' we’ve got a new object and we need to rebind our controls.
   BindData()
End Sub

Once again, because the hard work is all done in the factory, there isn’t much code to write here either. All we do is get the selected item from the ComboBox and pass this to the shared factory method called NewMotorcycle. Now that we have a new Motorcycle object, we need to rebind our forms controls to the object, which we’ve encapsulated in a method called DataBind.

Now that we’ve finished adding the factory and helper methods to the motorcycle base class and we have a client tester application, we’ll need to do a little housework. First, because we added the factory methods to the MotorcycleFactory project after creating the individual motorcycle objects, we'll need to recompile the MotorcycleFactory. Next, we’ll need to recompile each of the individual concrete motorcycle objects (Cruiser, SportTourer, and Tourer). Earlier, I mentioned that we will place the concrete objects in a separate folder within the client’s application. This will allow us to easily get a list of the available concrete objects without the need to inspect all of the assemblies in a given application’s path. So, let’s add a subfolder to the MotorcycleClient’s bin directory called Motorcycles. Then manually copy each of our three concrete object assemblies (Cruiser.dll, SportTourer.dll, and Tourer.dll) into the "Motorcycles" subdirectory.

At this point, we should be able to compile and execute the MotorcycleClient application. If all went well, the ComboBox should be populated with the list of available motorcycle objects. As we select a motorcycle, it should be dynamically instantiated for us, and the TextBoxes should represent it’s specific metrics. The beauty of all of this is that we do not have references to concrete motorcycle objects. Instead, we have a Motorcycle Factory where we call shared methods, and it does all of the dynamic work for us. All we do when a new motorcycle is available is drop the respective assembly file(s) containing the newly compiled Motorcycle(s) objects in the Motorcycle subdirectory (the name of which is configured in the app.config file.), and the new object types are instantly available to the client application. 



Posted on Utopian.io - Rewarding Open Source Contributors

Sort:  

Hey @carver I am @utopian-io. I have just upvoted you!

Achievements

  • You have less than 500 followers. Just gave you a gift to help you succeed!
  • Seems like you contribute quite often. AMAZING!

Suggestions

  • Contribute more often to get higher and higher rewards. I wish to see you often!
  • Work on your followers to increase the votes/rewards. I follow what humans do and my vote is mainly based on that. Good luck!

Get Noticed!

  • Did you know project owners can manually vote with their own voting power or by voting power delegated to their projects? Ask the project owner to review your contributions!

Community-Driven Witness!

I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!

mooncryption-utopian-witness-gif

Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x

Thank you for the contribution. It has been approved.

You can contact us on Discord.
[utopian-moderator]

Coin Marketplace

STEEM 0.04
TRX 0.31
JST 0.074
BTC 63723.62
ETH 1680.06
USDT 1.00
SBD 0.42