Published: 09 Jan 2008
By: Brian Mains

Brian Mains talks about abstractions.

Abstraction is a powerful technique when used properly. It can reduce the amount of coding involved, as well as make it easier to add features to existing software. However, poor abstraction can actually extend the amount of time it takes to develop software, which we'll look at in this article.

The Definition of Abstraction

The actual concept of abstraction is a very broad term, and is often mistaken for various other concepts. In Wikipedia, the definition of abstraction is "a mechanism and practice to reduce and factor out details so that one can focus on a few concepts at a time." Abstraction is exactly that; using OO techniques can help reduce the amount of code required to develop an application. In addition, good development techniques and refactoring help you to focus on one part of the puzzle so you do only have to worry about a few concepts at a time, though having the whole picture in mind really helps as well.

The Power of Abstraction

Abstraction is a powerful thing. Poor abstraction adds many hours to an application's total development time. Most people think of abstraction in terms of developing new applications, but abstraction lives through the entire lifecycle of the application, because all code has to be maintained at some time in its existence. Sometimes poor abstraction can slip through design and construction, but the true test of a great abstraction is when the application needs to be maintained by someone else other than the original architect.

With that said, some of the tips that can help with parsing through abstraction in an application:

  • Easy to understand class, property, and method names - Naming is key to a good design; poorly worded methods make it harder to interpret the reason for the existence of an object. Consistency makes it easy for others to understand type and purpose of the objects. For instance, the purpose of a method named DoThis is harder to understand compared to a method named ProcessPayroll.
  • Good namespace structure - An excellent namespace structure can logically break out certain types of objects (such as adding .Configuration sub-namespace for configuration objects of a particular namespace).
  • Documentation - Documentation, however horrible to write, is a key to understanding how something works. A polymorphic approach hides the internal implementation of a component; however, it often leaves someone wondering the question of why or how something works? Why did someone do it this way? Take a look at some of the forums out there; it's easy to see that polymorphism brings about many challenges with the .NET and other application frameworks.
  • Minimal Coding - Don't write code for the sake of writing code; if an object is useful, keep it. Otherwise, get rid of it. It makes it easier to understand an application, from a maintenance perspective, if you get rid of the code that you didn't use. Post it on a web site or blog if you don't want to get rid of it, or put it in a text file on your machine. You can also exclude code in Visual Studio 2005 by right-clicking the code and selecting "Exclude from Project."
  • Smelly Code - I believe it was Kent Beck who came up with the term "smelly code", which is a way of labeling code that often makes certain mistakes when developing applications. (See the References section for more information).
  • Object-Oriented Techniques - If you aren't familiar with object-oriented programming, using it in ASP.NET and Windows Forms projects may be very foreign to you. Though a good understanding of object reuse, object inheritance, polymorphism, encapsulation, and the like can really help decrease the amount of work required. However, too much of one of these concepts can really damper a design, especially when talking about inheritance.
  • Virtual properties and methods - When dealing with base classes, defining a property or method as abstract (requiring them to override the method; also known as the Template Method pattern) or virtual allows derived classes to change the underlying functionality of a class.
  • Centralized objects in an application - Sometimes, using the singleton pattern to create an object that is globally accessible is helpful to make certain data sets, business objects, and functions available throughout the application. I do this in one of my Windows applications to expose an API that can be tapped into.

Helpful Abstraction Techniques

Some of the techniques that can help you develop good abstraction are:

Centralized Objects

Though I mentioned the Singleton pattern above, there are other ways to centrally provide data throughout an application. For instance, in web programming, every ASP.NET page inherits from the System.Web.UI.Page class, which defines a set of features that can be used in web applications. A custom page class, which inherits from this common class, can provide a unique set of functions and data to an entire application, simply by having every ASP.NET page inherit from this new class.

Certain tasks that are common throughout an application can often be rolled up to a common static or instance class that can make replicating that object easier. For instance, suppose you had this code to do security checks in every ASP.NET page:

The above code is repeated throughout the code-behind pages in an application. Though useful, it would be better to reuse this code for the series of administrative pages. However, it's possible to put this code in a static class, so that it can be used in multiple places; however, not every single ASPX page (after all, that check is specialized for certain situations). Below is a second implementation within a static class:

The above code uses the HttpContext.Current object to access the same functions as you could with the Page class.

Reflection or Type Checking

Sometimes type checking can be handy to use. For instance, I've developed a windows API having specific objects representing the various pieces of the user interface. Because they are defined as a specific type (the type UIObject defined below), type reflection can be used to perform an appropriate action. For instance, suppose I have an object that does this:

In the above example, the model handles passing the menu item object to the correct repository based on the item's type. To make that determination, the AddItem method uses type checking to determine that it's of the type MenuItemObject, and passes it to the correct collection.

Reflection is another option; there are various ways to use reflection. Each object has a GetType method, which returns the type information and metadata (calling uo.GetType() returns a Type object with the MenuItemObject details). The Type object has methods to inspect the various methods, properties, constructors, events, and other information about the object. It's possible to determine whether a type is a subclass of another type, defines a specific interface, or has certain attributes defined, which all add to the usefulness of developing more dynamic software (attributes are a mechanism of AOP - Aspect Oriented Programming - per se).

In addition, there are several ways to load specific types. For instance, the Type object has a GetType static method that takes a string containing the type/assembly name, or strong name, of a type to dynamically load (if it can resolve the name). There are other options as well, such as loading the assembly that contains the type, and parsing the assembly for that type's information.

Design Patterns

A good application design can reduce the lines of code dramatically when compared against a poor application design. Using patterns that make sense can really aide in reducing the amount of work needed to implement something, while increasing its usability and effectiveness. If you are unfamiliar with design patterns, there are many resources on the web to learn about them.

Virtual/Abstract Properties and Methods

Virtual methods (also known as the Template Method pattern) help provide some abstraction by allowing the derived class to perform some calculation or logic. For instance, take a look at the following base class:

The above base class defines the Name and ChangeVisibility members as abstract, requiring a derivative to provide more details. In this situation, Name is used in a manner almost equivalent to the type name, as a means to determine the kind of UI Element that the object is (whether it's a toolbar, menu, etc.). The following are some possible implementations:

No matter how many derivatives, each is in charge of implementing certain functionality in this approach.

The Pain of Abstraction

Abstraction is a great development technique that is used in application development, but a poor abstraction can cause a lot of problems. Here are some things to avoid:

Don't create an abstract method per condition. For instance, say the status of a purchase order system can be one of the following values: active, on hold, completed, delayed, and in progress. It's best not to create separate abstract methods for each of these statuses, but to create an enumeration that contains all of the status values, and pass it to a single method. Rather than having a base class that defines the following:

The previous example shows a series of methods defined in a class that a developer must tap into, such as in a provider. However, it's better to define one method, such as the following:

A static (or other) class that exposes internally this class could have its own methods that breaks out each status, and does something like the following:

Therefore, it's less work to implement an interface if it's compacted in this way. Sometimes an aspect-oriented programming approach can help account for all the variations, which simply would work by defining the status type in an attribute of a method, allowing the developer to add new methods to handle new scenarios more dynamically. Sometimes this approach isn't worth the coding effort.

Inheritance is a beneficial object-oriented technique; however, it should be used in limited circumstances. For instance, too many inheritance chains make it more difficult to update code. For instance, if you have ten levels of derived objects through inheritance, and a change occurs at around the fifth level, the rest of the objects are updated with the change. This can be a good thing; after all, inheritance is meant to reduce the total amount of work that must be done.

However, I can't tell you how many times I've made a change to a class in the middle of the inheritance hierarchy only to realize that I wanted the change for 70% of my derived objects, but not for the other 30%. I'm stuck with analyzing how I can make the change, without making dramatic changes to the code. Oftentimes, I had to rewrite the inheritance hierarchy, and the solution was to compact the hierarchy as much as possible.

There are several design patterns that can help with this. First, the Composite pattern is one of the more obvious solutions; it says that you can use the same interface for multiple types of objects even if it isn't being used to its fullest. For instance, if there is the following inheritance:

In the above scenario, if inheritance is causing problems with this approach, an alternative approach would be to compact it, using the composite pattern, to the following:

This could be, depending on the inheritance situation, a better solution.

I'll make one final point about some of the OO concepts and a caveat to them. As with anything, if you don't know how something works, people don't like to use it unless they see a tremendous benefit. Some people don't even like to try something new altogether. With that said, polymorphism and encapsulation are great techniques, but if there aren't plenty of samples or plenty of documentation to explain how it works, then it makes it much harder for someone else to want to use the software you've developed, whether it's a framework or an extensible application.

This is a reason why blogs, articles, and forums are so popular; people want to know how something works, but a specific topic may not be documented well enough, and so that developer needs to consult the wide array of people on the web for help.

For Microsoft, third-party software companies, and those companies with many clients, this may not be a big deal. However, if you are trying to get people to use your framework to develop applications with, it's very critical to create a clean design that is understandable, has plenty of documentation, and has samples to show someone how to use it.

Conclusion

Though I may have made this topic too generic and broad in concept, I hope that I was able to provide you with something that is useful in your development of applications. We've looked at the general idea of abstraction, and some techniques to help create a better abstraction in developing objects, and some of the pitfalls as well.

References

<<  Previous Article Continue reading and see our next or previous articles Next Article >>

About Brian Mains

Brian Mains is an application developer consultant with Computer Aid Inc. He formerly worked with the Department of Public Welfare. In both places of business, he developed both windows and web applications, small and large, using the latest .NET technologies. In addition, he had spent many hou...

This author has published 73 articles on DotNetSlackers. View other articles or the complete profile here.

Other articles in this category


Developing a Hello World Java Application and Deploying it in Windows Azure - Part I
This article demonstrates how to install Windows Azure Plugin for Eclipse, create a Hello World appl...
Android for .NET Developers - Building a Twitter Client
In this article, I'll discuss the features and capabilities required by an Android application to ta...
Ref and Out (The Inside Story)
Knowing the power of ref and out, a developer will certainly make full use of this feature of parame...
Developing a Hello World Java Application and Deploying it in Windows Azure - Part II
In this article we will see the steps involved in deploying the WAR created in the first part of thi...
Android for .NET Developers - Using Web Views
In this article, I'll show a native app that contains a web-based view. The great news is that HTML ...

You might also be interested in the following related blog posts


Dont Repeat Yourself read more
Help Shape the Future of ASP.NET read more
Software Consulting Customer's Bill of Rights read more
Debugging Dependency Properties in WPF with Property Changed Callbacks (or: How I Learned to Stop Worrying and Love printf Debugging) read more
Beware of Fear-Driven Architecture (do you fear deployments?) read more
When designing a software masterpiece, make the first release in pencil read more
Running Silverlight 2 on Google Chrome using the Chrome Dev Channel read more
Register for DevTeach Montreal read more
Creating Software is Not Like Building read more
Pain-Driven Development: uncovering the motivation read more
Top
 
 
 

Please login to rate or to leave a comment.

Free Agile Project Management Tool from Telerik
TeamPulse Community Edition helps your team effectively capture requirements, manage project plans, assign and track work, and most importantly, be continually connected with each other.