Object-Oriented Programming (OOP) is here to stay. It’s a programming paradigm that emulates the human way of seeing things. Microsoft has pledged support for OOP by incorporating two major changes to the latest version of Visual Studio .NET. First, C# has been introduced as a brand new object-oriented language specifically designed for .NET. Second, Visual Basic has matured as a full-fledged Object-Oriented Programming (OOP) language.
This article will explore the significance of these changes to the Microsoft development community and offer tips for programming in VB.NET. Specific questions to be addressed include:
- What exactly is an object-oriented language?
- What OOP constructs can we expect to be writing in VB.NET and C#?
- Are there any tools or notations that can help us design OOP applications?
- What best practices can we apply to produce more efficient code and designs that promote reuse?
- What is the next big thing in OOP?
I. What Exactly Is an Object-Oriented Language?
OOP first appeared as a major implementation with SmallTalk in the early 1980’s and has been formally managed by the Object Management Group (OMG) since 1989. OMG provides industry guidelines for object-oriented software development by supporting a Unified Modeling Language (UML) as a standard notation for modeling applications.
Today, many organizations have adopted UML as a standard for specifying, visualizing, constructing, and documenting the artifacts of software systems. The standard defines a set of diagrams that can be used to model different aspects of a system: use cases and requirements, business logic, dataflow, states and activities, static and dynamic views, and so forth.
One view, the class diagram (see the following example), represents the static structure of an application. That is, the things that exist (which will ultimately show up in your code), their internal structure, and their relationship to one another.
Each programming language has its own way to implement static structures. C# is a new language similar to Java and C++. If you’re used to these languages, you shouldn’t have any trouble getting into C# and its related OOP implementation details. On the other hand, if you’re used to writing code in VB 3, 4, 5, or 6, you’ll probably have a harder time getting into the full-fledged OOP world of VB.NET. Concepts such as inheritance, overloading, overriding, and namespaces are new to VB.NET, but the advantages they bring are not to be taken lightly.
In order to understand the power of OOP, consider, for example, form inheritance, a new feature of .NET that lets you create a base form that becomes the basis for creating more advanced forms. The new “derived” forms automatically inherit all the functionality contained in the base form. This design paradigm makes it easy to group common functionality and, in the process, reduce maintenance costs. When the base form is modified, the “derived” classes automatically follow suit and adopt the changes. The same concept applies to any type of object. Have a look at http://msdn.microsoft.com/library/en-us/vbcon/html/vbconFormInheritance.asp for more information on form inheritance.
II. What OOP Constructs Can We Expect to be Writing in VB.NET and C#?
This section outlines the following six constructs that help define the relationship between UML models and the accompanying implementation code in VB.NET code and C#. These include classes, interfaces, inheritance, overriding and overloading, aggregation, and composition as well as other associations.
Classes
In a UML static structure diagram, the class is the main element. The two other main elements are interfaces and associations. A class is said to have attributes and behaviors. An attribute is simply some piece of data that is bound to a class. Attributes are often referred to as class variables or properties. The behaviors are the actions that the class can perform. They are referred to as operations or methods (subs and functions in VB).
In OOP, classes are the actual containers for all of your coding. Classes are also held in another container called a package. Packages allow you to logically group the different classes that make up your system. You’re allowed to insert a class of the same name in two different packages so that you can have, for example, the following result:
- System.Database Invoice
- System.Business.Invoice
- System.GUI.Invoice
As you can see, there are three Invoice classes, each accomplishing something different (relating to an invoice) and each contained within its own package. This would be ideal in a three-tier system. Programming languages such as C# and VB.NET implement the notion of packages through namespaces.
C# was based entirely on classes from the ground up. VB programmers have been defining classes since version 5.0. Although you were able to use classes in previous VB releases, the classes were defined only as simple units. They could not take advantage of advanced concepts such as inheritance (see below). To use a class, you usually need to create an instance of it. Each instance of a class can hold its own (different) values for attributes. The behaviors will act upon that particular set of data.
Sometimes it’s necessary to share an attribute (or a behavior) over each instance. In this case it is declared as static (underlined in UML notation). To access a static attribute or behavior you do not need to create a class instance. To understand how you could use static attributes or behaviors, imagine a class named LoggedOnUser, which has a static attribute named TotalLoggedOnUsers. When such a class is created, it would increment the static attribute, and when the class is destroyed, it would decrement the attribute.
The following UML diagram shows a class and its associated attributes and operations:
Here’s what the code looks like in VB and C#:
VB.NET Code Snippet | C# Code Snippet |
Public Class Customer Public ID As Integer Public name As String Public phone As String Public Function GetLastOrder() As Order Return New Order() End Function End Class |
public class Customer { public int ID; public string name; public string phone; public Order GetLastOrder() { return new Order(); } } |
In this example, Customer is the name of the class. ID, Name and Phone are attributes of the class, and getLastOrder is the name of an operation that returns an object of type Order.
Interfaces
You define interfaces to model common actions. For all of you who have done a little Microsoft COM programming, you’ve probably heard of interfaces referred to as contracts between two entities. In UML, you draw an interface just like a class but with the interface stereotype (a stereotype allows you to categorize items).
Let’s say you have three objects: an Order, an Invoice, and a SalesReport. Each of these classes might have a behavior that allows it to print itself on the local printer, which would make sense. If each class had a behavior called Print, you could call the Print method on instances, and they would print themselves. Unfortunately, calling Print on an Order instance and Print on an Invoice instance are two different actions for the compiler, so reuse of the Print method is not possible. What’s more, if the person who implemented the Print method on the Invoice class named it PrintMe rather than Print, the opportunity for reuse is lost.
You can resolve these problems by creating an interface called IPrintable (interface names should be prefixed by a capital “I”) with one method called Print. Now any class that can be printed has only to implement the IPrintable interface (realize is the term used in UML), and voilà! Any existing piece of code that knows how to use the IPrintable interface (it’s used just as if it were a class), automatically knows that if it calls Print, any underlying class will print itself. This is a powerful way to increase the abstraction level of your system design.
In UML, there are two ways to represent the association between an interface and a class. You can attach a small lollipop to the class, with the interface name written beside it, or you can use a dotted line with a closed arrow pointing to the interface item.
VB programmers are probably already accustomed to using interfaces. Have you ever heard of IDispatch, IUnknown, or ITransaction? VB 6.0 defined an interface as a class. VB.NET introduces the Interface keyword, which gives you the ability to create a true interface, not an imitation.
In this example, an interface is both defined and realized in UML:
Here’s how the code defines an interface:
VB.NET Code Snippet | C# Code Snippet |
Public Interface IPerson Function GetName() As String Function GetPhone() As String End Interface |
public interface IPerson { string GetName(); string GetPhone(); } |
This code snippet realizes an interface:
VB.NET Code Snippet | C# Code Snippet |
Public Class Customer Implements IPerson Public ID As Integer Public name As String Public phone As String Public Function GetLastOrder() As Order Return New Order() End Function Public Function GetName() As String Implements IPerson.GetName Return name End Function Public Function GetPhone() As String Implements IPerson.GetPhone Return phone End Function End Class |
public class Customer : IPerson { public int ID; public string name; public string phone; public Order GetLastOrder() { return new Order(); } public string GetName() { return name; } public string GetPhone() { return phone; } } |
Inheritance
Inheritance describes the hierarchical relationship between classes. You can usually identify these kinds of relationships if the phrase “is a kind of” applies between two classes. For example, a Cat is a kind of Mammal, and a Mammal is a kind of Animal. In the latter example, the Cat is a subclass of Mammal, and Mammal is a superclass of Cat. It’s a good practice to limit a class to only one superclass.
In UML, inheritance is noted by a solid line with a closed arrow that points to the superclass. The relationship is called a generalization.
You might ask, “What purpose does inheritance serve?” It allows you to expose, extend, or even alter the attributes and behaviors of the parent class. For example, a Square is a kind of Rectangle in which the height and width must be equal. The Rectangle class already has the height and width attribute, so the Square class specializes the behavior of the Rectangle to make sure both attributes are identical.
The following UML diagram illustrates a class hierarchy with inheritance. The code snippet that follows implements the diagram.
VB.NET Code Snippet | C# Code Snippet |
Public Class Person Public name As String Public phone As String End Class Public Class Customer Inherits Person Public ID As Integer Public AccountBalance As Decimal Public Function GetLastOrder() As Order Return New Order() End Function End Class |
public class Person { public string name; public string phone; } public class Customer : Person { public int ID; public Decimal accountBalance; public Order getLastOrder() { return new Order(); } } |
Overriding and overloading
One of the harder concepts to grasp in the object-oriented paradigm is inheritance behavior overriding and overloading. Overloading is simply using the same behavior to treat different kinds of data. For example, a MathHelper class might have a behavior that allows it to do something with two integers, for example, add them. If the MathHelper class were to be extended to work with floating point numbers, the same behavior could be reused with a different input data type.
Overriding is the hardest of the two terms to master. The basic premise is that if you are building a shopping cart for a Web site, the Book class (one of the kinds of items you are selling) should derive from a more general concept, probably a CatalogItem, and the subclasses should never be used directly.
This kind of abstraction allows you to build most of your system using the methods exposed by CatalogItem because every CatalogItem should have a price, a description, and a label. The system doesn’t know that when it’s getting the price for a CatalogItem; the call is actually delegated to the subclass Book, which is calling Amazon.com to retrieve the current price. If the system is getting the description for a CD, it still reads the description attribute from the CatalogItem, but the call is once again delegated to the CD class, which contacts CDDB.com to retrieve the last album review.
It’s true that this could be implemented using interfaces, as seen earlier. But overriding allows you to use one extra trick, which you can’t do using interfaces. It lets you reuse the parent’s default behavior. For example, a Rectangle class, which is a Shape, has for a subclass ShadedRectangle. When the system calls the draw behavior, the call is delegated to the most derived class, hence ShadedRectangle. It can simply call the draw behavior of its parent (which will draw just a rectangle) and then finish by drawing the actual shadow.
The following UML diagram represents overriding and overloading. The code that follows implements the diagram.
VB.NET Code Snippet | C# Code Snippet |
Public Class Person Public birthDay As DateTime Public Sub SetBirthDay(ByVal year As Integer, ByVal month As Integer, ByVal day As Integer) SetBirthDay(New DateTime(year, month, day)) End Sub Public Overridable Sub SetBirthDay(ByVal datetime As DateTime) birthDay = datetime End Sub End Class Public Class Customer Inherits Person Public Overloads Sub SetBirthDay(ByVal datetime As DateTime) MyBase.SetBirthDay(datetime) Calendar.Instance().AddEvent("Customer Birthday", datetime, Me) End Sub End Class |
public class Person { public DateTime birthDay; public void SetBirthDay(int year, int month, int day) { SetBirthDay(new DateTime(year, month, day)); } public virtual void SetBirthDay(DateTime date) { birthDay = date; } } public class Customer : Person { public override void SetBirthDay(DateTime date) { base.SetBirthDay(date); Calendar.Instance().AddEvent("Customer BirthDay", date, this); } } |
Aggregation and composition
Aggregation and composition are two kinds of relationships that exist between classes. They form a whole-part relationship that you can use to decompose objects into more manageable entities. For example, a class named Airplane would have two Wings. The difference between aggregation and composition is very simple although sometimes difficult to identify. Aggregation means that the Wing class can exist and be used independently of the Airplane class. Aggregation is represented by a non-filled lozenge at the end of the link. Composition is a stronger relationship, in which the part has no meaning or usage outside of the relationship. It is represented by a filled lozenge at the end of the link.
Some of these associations can be identified by using the “has a” phrase. Take, for example, a Vehicle class has an Engine. More specifically, the Vehicle has one Engine, and this extra information is usually written as a numerical identifier on the proper side of the link between the classes on the UML diagram. You can use the identifiers as a hint on how to implement the relationship when you develop the code. For example, if an Airplane has two Wings, the Airplane class could have an attribute that is an array of two Wing classes. If the numerical identifier is a star “*”, you could implement the association using the System.Collections.ArrayList object, which supports a variable number of items.
The following UML diagram illustrates the concepts of aggregation and composition. The snippet of code that follows implements the diagram.
VB.NET Code Snippet | C# Code Snippet |
Public Class Order Protected orderItems As ArrayList Protected shippingAddress As Address Protected Class OrderStatus Public isShipped As Boolean Public isPaid As Boolean End Class Protected status As OrderStatus End Class |
public class Order { protected ArrayList orderItems; protected Address shippingAddress; protected class OrderStatus { public bool isShipped; public bool isPaid; } protected OrderStatus status; } |
Other associations
Sometimes, when looking at a static class UML diagram, you will simply see links between classes and qualifiers attached to them. This kind of diagram usually exists to express the relationships between the objects. For example, a class named TicketGenerator could be linked to a class named GeneralAdmissionTicket by the Creates qualifier. This is a hint and expresses the fact that GeneralAdmissionTicket class instances are probably created by a behavior of the TicketGenerator class.
III. Tools That Can Help
By jumping on the OOP bandwagon, VB.NET provides us with a considerable number of new features. C# is certainly a language that will get a lot of attention. But OOP doesn't come for free. Modeling tools are needed to help document the software requirements and model the classes and interactions. Tools such as Microsoft Visio for Enterprise Architects, Rational Rose, and the new Rational XDE that integrates with Visual Studio.NET will certainly come in handy—f not prove necessary—to provide a solid foundation for application development and documentation. You also can use their basic code generation features to synchronize your model with your code. Other software vendors provide specialized solutions that allow for the generation of greater amounts of code, be it business logic code or the architecture code of an application.
IV. Applying Best Practices
Design patterns are what could be called the body of knowledge for object-oriented software development. They give you a standard way in which to attack common problems that occur in Object-Oriented Designs (OOD) and their implementations.
Design patterns aren't invented; rather, they are found. Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, the so-named "Gang of Four (GOF)," have put together and documented more than 20 design patterns. Of course, there are many more patterns available. Every time a problem is solved, and the solution is documented appropriately, that solution has the potential of becoming a pattern if it's adopted as the way to resolve the problem.
To name a few design patterns, there are Singleton, Facade, Abstract Factory, Flyweight, Adapter, and so forth. These patterns are fully documented on the Internet and in some very good books. To demonstrate the usefulness of these patterns, let's take a look at the Singleton pattern and how to implement it.
The Singleton pattern is very simple. Its purpose is twofold: to assure that within the lifetime of a running application, a class will have a maximum of one instance at a time available and to provide a global point of access to it.
Sometimes it's necessary to implement the Singleton pattern. For example, when an application has an event log, there should be a single log, not two or three. Another example is a printing system. Although there are many printers attached to the print spooler, there can only be one spooler available.
As you can see, there are many ways to implement the same design pattern. Here's an example of the simplest implementation. A more complex implementation might include separating the Singleton logic from the object's logic.
VB.NET Code Snippet | C# Code Snippet |
Public Class EventLogger Protected Shared uniqueInstance As EventLogger Protected events As ArrayList Protected Sub New() events = New ArrayList() End Sub Public Shared Function Instance() As EventLogger If uniqueInstance Is Nothing Then uniqueInstance = New EventLogger() Return uniqueInstance End Function Public Sub LogEvent(ByVal eventToLog As Object) events.Add(eventToLog) End Sub End Class |
public class EventLogger { static protected EventLogger uniqueInstance; protected ArrayList events; protected EventLogger() { events = new ArrayList(); } static public EventLogger Instance() { if (uniqueInstance == null) uniqueInstance = new EventLogger(); return uniqueInstance; } public void LogEvent(object eventToLog) { events.Add(eventToLog); } } |
V. The Next Big Thing
The next big thing that will certainly change the way we develop applications is Model Driven Architecture (MDA). This initiative, led by the OMG, defines a way to separate business logic from implementation. Companies that adopt this paradigm will shield themselves from the problems related to changes in technology. An application written for DAO, RDO or ADO that you want to port to ADO.NET today requires meticulous find and replace everywhere in the code where the old technologies were used. In concert with an MDA capable tool, OOP will prove even more powerful by providing loose coupling at the application design stage.
OOP is definitely here to stay. As the examples demonstrate, it's a programming paradigm that emulates the human way of seeing things. Now that VB.NET fully supports OOP, the development abstraction goes one level higher. And if you haven't looked at C# yet, do so, and you'll see that it's not too different from what you're used to.
About the Authors
Yan Locas is a senior technical consultant working at Codagen Technologies Corp. (www.codagen.com), the maker of Codagen Architect, a patent-pending MDA-capable software development tool that reduces development lifecycles, speeds time-to-market, and minimizes testing and re-coding efforts. He has occupied technical and management positions and has been working in the IT industry for the last 9 years. Yan is an MCSD, MCSE, MCT, and CLP. He can be reached at ylocas@codagen.com.
Erik Renaud is a senior software engineer for Codagen Technologies Corp. He is currently working on MDA-compliant code-generation techniques for the Microsoft .NET framework. Erik has also a vast C++ experience in interconnecting disparate systems and designing multi-tier distributed systems used for the leisure and transport industry. He occasionally gives OOP training and can be contacted at erenaud@codagen.com.