I have recently been tasked with creating a 1,000 ft overview of Object Oriented concepts in the VB .NET world for a development staff whose experience is mostly in old school ASP and VB 6.0
Much credit to The Book of Visual Studio .NET, ISBN 1-886411-69-7 from No Starch Press written by Robert B. Dunaway which I used as a template for the document and borrowed some examples from.
What is an object?
In its most basic form, an object simply is an entity that exposes properties, methods, and events. Objects provide abstraction and encapsulation to make our job as programmers easier by exposing code in ways that are easy for a human being to organize and comprehend.
Abstraction
Abstraction is the easiest of the OOP concepts to understand and is often something we implement naturally without realizing it. In short, abstraction is the implementation of code to mimic the actions and characteristics of any realworld entity.
The most commonly-used example for describing abstraction is the abstraction of a person. Imagine that we want to create an object from a class that represents a person. A person class will need to describe its characteristics through the implementation of properties (height, weight, eye color, etc). Actions of the person class are performed by methods (Speak, Eat, Sleep, etc).
Encapsulation
We expose properties and methods through abstraction, but we implement the actual workings of our component through encapsulation. A few encapsulated actions might include data access, data validation, calculations, adding data to an array or collection, or calling other methods or other components. Exposing our component's interface while hiding the component's implementation code effectively separates interface implementation from our black box implementation. This separation helps to modularize components to perform a more specific task while requiring minimal knowledge of how the black box actually works.
One of the more useful applications of encapsulation is in making a complex component. For example, your program may require interaction with a third party system, but interaction with this system can only be achieved through a complex API. Rather than requiring all developers on a project to spend valuable time figuring out how to correctly use the third party API or even find ways to misuse it, one developer could study the API then encapsulate it in a component that exposes a less complex interface. This is a common practice that saves time and reduces potential bugs. The .NET Framework itself encapsulates various Windows APIs.
Polymorphism
Polymorphism is the ability to implement the interface of another class into multiple classes or to implement multiple interfaces on a single class. This method of implementation is referred to as interface-based programming. A vehicle is a good example of polymorphism. A vehicle interface would only have those properties and methods that all vehicles have, a few of which might include paint color, number of doors, accelerator, and ignition. These properties and methods would apply to all types of vehicles including cars, trucks, and semi-trucks.
Polymorphism will not implement code behind the vehicle's properties and methods. (That's the job of inheritance covered in the next section.) Instead, polymorphism is the implementation of an interface. If the car, truck, and semitruck all implement the same vehicle interface, then the client code for all three classes can be exactly the same.
Implementing the vehicle interface only requires the declaration of properties and methods. To create a new interface, use the Interface keyword in place of the Class keyword. The client implementing the new interface can do so by using the Implements keyword as shown in the example:
Public Interface IVehicle
Property PaintColor() As String
Property NumberOfDoors() As Integer
Function Ignition() As Boolean
End Interface
Public Class Car
Implements IVehicle
Public Function Ignition() As Boolean Implements IVehicle.Ignition
End Function
Public Property NumberOfDoors() As Integer Implements IVehicle.NumberOfDoors
Get
End Get
Set(ByVal Value As Integer)
End Set
End Property
Public Property PaintColor() As String Implements IVehicle.PaintColor
Get
End Get
Set(ByVal Value As String)
End Set
End Property
End Class
Inheritance
Inheritance is the ability to apply another class's interface and code to your own class. Remember, with polymorphism, you got the interface; however, you must apply your own code. The power of inheritance is the ability to inherit code, saving developers time. This type of inheritance is called implementation inheritance. To inherit another class, use the Inherits keyword.
In the domain manager pattern, objects are inherited from a base class called DomainObject. This means that every object in the system, whether it is a student, teacher, building, address, or any other object type a system requires will return True if asked if it is a Domain Object. The power of this type of inheritence is that it allows us to design generic functions that take an object of the type DomainObject and route them appropriately to specialized code to deal with them. This is used in the Domain Manager's call to the data layer:
Public Function Load(ByVal d As DomainObject) As DomainObject
Dim dao As Object
For Each dao In _SupportedDAOs
If dao.SupportsLoadType = d.GetType.ToString() Then
Dim hydratedObject As DomainObject
Try
dao.ConnectString = _Connection
hydratedObject = dao.Load(d.Key)
If (Not IsNothing(hydratedObject)) Then
hydratedObject.ObjectDomainMGR = Me
End If
Catch ex As Exception
Throw ex
Finally
Try
If Not IsNothing(dao.DataReader) Then
dao.DataReader.Close()
End If
Catch ex As Exception
Trace.Write(ex.Message)
End Try
End Try
Return hydratedObject
End If
Next
Throw New Exception("Load not supported for this type[" + d.GetType.ToString() + "]")
End Function
Notice how load takes any domain object as a parameter. The code then walks through a list of supported Database Access Objects (DAOs) to find the appropriate data access functionality for the object given. This means that any object in our system can be loaded just by passing it into a single function, making it easy for new developers to be able to use our pattern without having to understand the workings of the DAO classes or the domain manager. All they need to know is to call DomainManager.Load()!
Events
Raising events is a great way to "bubble up" notifications from a class to a caller. In order to create an event, simply use the Event keyword as seen in class Test below. To raise an event simply use the RaiseEvent command and the event will be passed to the caller of the object. In the example below you can see a very simple example where the class TestCaller has a variable of class Test declared with the WithEvents keyword. This keyword must be used in the declaration in order to capture events.
The sub PostSaveLogic() is declared as a handler of the TestSaved event. That is to say that whenever Test.Save() is called, the code within PostSaveLogic is called. An example of where this could be useful is in sending email notifications to users. The test object certainly doesn't need to know how to send email, email functionality should be encapsulated into an email interface which can be called when an event that requires email notification occurs.
Public Class Test
Event TestSaved()
Public Sub save()
RaiseEvent TestSaved()
End Sub
End Class
Public Class TestCaller
Private WithEvents t As New Test
Public Sub SaveTest()
t.save()
End Sub
Public Sub PostSaveLogic() Handles t.TestSaved
End Sub
End Class
Declaration Options
Here is a list of the most commonly-used declaration options in .NET with brief descriptions of each:
- Private: The Private keyword defines a variable or method as accessible only by code within the context of where the declaration occurred; outside code is not permitted access.
- Public: The Public keyword declares a property or method as accessible by anyone within the calling application or within the class itself.
- Friend: The Friend keyword defines a property or method as accessible by members within the class it is declared in.
- Protected: The Protected keyword defines a property or method as accessible only by members of its class or by members of an inheriting class.
- Default: A Default property is a single property of a class that can be set as the default. This allows developers that use your class to work more easily with your default property because they do not need to make a direct reference to the property. Default properties cannot be initialized as Shared or Private and all must be accepted at least on argument or parameter. Default properties do not promote good code readability, so use this option sparingly.
- Overloads:The Overloads property allows a function to be described using deferent combinations of parameters. Each combination is considered a signature, thereby uniquely defining an instance of the method being defined. You can define a function with multiple signatures without using the keyword Overloads, but if you use the Overloads keyword in one, you must use it in all of the function's Overloaded signatures.
- Shared:The Shared keyword is used in an inherited or base class to define a property or method as being shared among all instances of a given class. If multiple instances of a class with shared properties or methods are loaded, the shared properties or methods will provide the same data across each instance of the class. When one class alters the value for a shared property, all instances of that class will reflect the change. Shared properties of all instances of the class point to the same memory location.
- Overridable:The Overridable keyword is used when defining a property or method of an inherited class, as overridable by the inheriting class.
- Overides: The Overides keyword allows the inheriting class to disregard the property or method of the inherited class and implements its own code.
- NotOverridable: The NotOverridable keyword explicitly declares a property or method as not overridable by an inheriting class, and all properties are "not overridable" by default. The only real advantage to using this keyword is to make your code more readable.
- MustOverride: The MustOverride keyword forces the inheriting class to implement its own code for the property or method.
- Shadows: The Shadows keyword works like the Overloads keyword except that with shadows we do not have to follow rules such as implementing the same signature. The Shadows keyword does not require the consent (override ability) of the inherited class to replace the property or method's implementation code. A method does not have to be defined as overridable for the Shadows keyword to work.
