Virtual Developer Workshop: Containerized Development with Docker
Usually, when you want to create a new object, you just use VB's trusty New keyword along with the class you want to instantiate, as follows:
Dim Emp As Employee Emp = New Employee()
However, the client application taking such direct control of the instantiation process sometimes isn't the best approach. For example, suppose an application accepts user input and uses it to create a new employee object. The only tricky thing is that there are a few different kinds of employees, represented by a few different kinds of classes:
- The SalariedEmployee contains a Salary property.
- The HourlyEmployee class contains a HourlyRate property.
- The Contractor class contains a BillingRate property.
Each of these classes inherits from Employee, which has properties and methods common to all employees, such as the employee's first and last names, his or her SSN, and so forth.
The form where the user enters his or her information is simple: It accepts first name, last name, SSN, and employee type. (None of the more specific information is collected on this form.) It also contains a dropdown listbox that asks which type of employee to create. The dropdown list retrieves its options from a database, but you can bet it includes Salaried Employee, Hourly Employee, and Contractor.
The user enters the new employee information, selects an employee type, and clicks the Add New Employee button on the form. What happens? Probably something like this:
Dim newEmp As Employee If empTypeCombo.SelectedItem.ToString() = "Salaried Employee" Then newEmp = new SalariedEmployee() ElseIf empTypeCombo.SelectedItem.ToString() = "Hourly Employee" Then newEmp = new HourlyEmployee() Else newEmp = new Contractor() End If newEmp.FirstName = firstName.Text newEmp.LastName = lastName.Text ...
This code is pretty straightforward, but it ties itself to the Employee class a little too tightly—what object-oriented folks would refer to as coupled. To understand this issue better, imagine that a new employee type is added—say, Intern. The following would need to be updated:
- The Intern class has to be written and inherited from Employee, just like all the rest of the classes.
- The database (where the dropdown listbox retrieves its options) has to be updated.
- The code in the form under the button click event (listed above) has to be modified.
There's no avoiding the first two, but the last one has big implications because other applications probably work with employees as well. So, you'd have to revisit any application that uses the Employee class (or one of its descendants) to see whether it needs to be updated because of this change.
This is what coupling means: Two classes are so tightly bound that a change in one necessitates a change in the other. Although no class can be completely decoupled from all other classes (they have to work together and communicate somehow!), it is good object-oriented programming practice to reduce these dependencies as much as possible.
In this example, if you can strike that third bullet point and make it so that an added employee type requires only the recompiling of the employee classes, you are in much better shape for maintenance down the road. But how do you do that? Obviously, at some point you've got to verify what type of employee the user wants to create and instantiate it, right? Right! But, if you can move that code into the Employee class itself (where you're already making a change), the impact can be minimized and the form is decoupled from the class!
The Factory Factor
Probably the best way to work this solution is by implementing a simple factory. A factory, in object-oriented parlance, is a way to take control of the instantiation of an object. Factories come in all shapes and sizes. In some systems, all classes have corresponding factory classes that handle all the instantiations of their corresponding classes. To keep this example simple, the factory will be no more than a shared method added to the Employee class:
Public Class Employee Public Shared Function CreateEmployee(ByVal empType As String) _ As Employee Dim newEmp As Employee If empType = "Salaried Employee" Then newEmp = New SalariedEmployee() ElseIf empType = "Hourly Employee" Then newEmp = New HourlyEmployee() Else newEmp = New Contractor() End If Return newEmp End Function ... End Class
Look familiar? The code has essentially just been moved from the form to the Employee class. Why? Because that's where it belongs! Now, a change, such as the addition of a new employee type, is much less likely to impact the client code.
An important point to remember is the function must be shared. This allows the client to call the function on the class itself, before the object is instantiated. Obviously, that's important because instantiating the object is exactly what this function does!
The form's button click event is pretty simple now:
Dim newEmp as Employee newEmp = Employee.CreateEmployee(empTypeCombo.SelectedItem.ToString()) newEmp.FirstName = firstName.Text newEmp.LastName = lastName.Text ...
No New! All the dirty instantiation details are left to the CreateEmployee factory function.
And now, to add the Intern class, you just have to:
- Write the Intern class and inherit from Employee, just like all the rest of the classes.
- While you're modifying the file with the Employee classes in it, update the CreateEmployee factory function to add another conditional.
- Update the database (where the dropdown listbox retrieves its options).
The Employee classes are recompiled, but all the client code is good to go.
Protect Your Class
One more detail to consider is that this solution does not stop a client application from instantiating an Employee class using the New keyword. It simply provides another, better option. If you want to stop any users from directly instantiating your class, the simple way to do that is this:
Public Class Employee Protected Sub New() End Sub ...
If you don't specify a constructor for your class, the .NET Framework creates one automatically for you. So, what's the point of creating an empty constructor? The default constructor is Public, which enables anyone to instantiate the class by using the New keyword. The above code uses the Protected constructor instead.
Typically for methods and variables, Protected indicates that no one outside the given class can access the method/variable. And that's what it is doing here, too. But, you don't call the constructor directly. It gets called when you instantiate the class. So, if you make the constructor for a class Protected, no code outside your class can instantiate it! Of course, your CreateEmployee is okay because it is a method of this class. However, if you write code like this in the form button's click event:
Dim newEmp = New Employee
You'll get this error:
'Employee.Protected Sub New()' is not accessible in this context because it is 'Protected'.
By making the constructor Protected, you now force anyone using this class to call your factory method—there's no other way to instantiate the class!
About the Author
Bill Hatfield is the bestselling author of half a dozen books on software development, including ASP.NET 2 For Dummies. He works as a Course Developer for programming courses at Avanade (in Seattle), but is fortunate enough to work from his home in Indianapolis. That way he gets the benefits of high tech work AND snow.