http://www.developer.com/

Back to article

Thinking in Objects


April 30, 2004

Introduction

I wrote The Object-Oriented Thought Process for anyone who needs to understand the basic object-oriented concepts before jumping into the code. A basic understanding of object-oriented theory is very important because, quite often, programmers do jump right into the code. Most of the software professionals whom I teach have attempted to make the shift from structured to object-oriented without changing the way that they think. This is a mistake.

Object-oriented development is a design technique rather than a coding convention. In developing an OO model, you must focus much more on the design than the code (this is true to a certain degree in structured development as well). To create a solid design, you must first understand the concepts involved in designing the object model.

One of the first thought process skills you must master is how to actually think in terms of objects. This may be easier than you think because in the real world you do think in terms of objects. It is simply a matter of changing your software development paradigm to think in terms of objects rather than thinking of a program as simply code and data. In this installment, we will consider how you start thinking in terms of objects.

How to Think in Terms of Objects

As with most things in life, there is no single right or wrong way to approach a problem. There are usually many different ways to tackle the same problem. So, when attempting to design an OO solution, don't get hung up in trying to do a perfect design the first time. What you really need to do is brainstorm and let your thought process go wild. Do not try to conform to any standards or conventions when trying to solve a problem, because the whole idea is to be creative. Thus, before you start to design a system, or even a class, think the problem through and have some fun! In this article, we explore the fine art and science of OO thinking.

The move from the procedural world to an OO world is not trivial. Changing from FORTRAN to COBOL, or even to C, requires that you learn a new language; however, making the move from COBOL to C++, C# .NET, Visual Basic .NET, or Java requires that you learn a new thought process. This is where the overused phrase OO paradigm rears its ugly head. When moving to an OO language, you must go through the investment of learning OO concepts and the corresponding thought process first. If this paradigm shift does not take place, one of two things will happen: Either the project will not truly be OO in nature (for example, it will use C++ without using OO constructs), or the project will be a complete object-disoriented mess.

Three important things you can do to develop a good sense of the OO thought process are covered in this article:

  • Know the difference between the interface and implementation
  • Think more abstractly
  • Give the user the minimal interface possible

We have already met some of these concepts in Article 1, and here we will now go into much more detail.

Knowing the Difference Between the Interface and the Implementation

As we saw in Article 1, one of the keys to a strong OO design is to understand the difference between the interface and the implementation. Thus, when designing a class, what the user needs to know and what the user does not need to know are of vital importance. The data hiding mechanism inherent with encapsulation is the means by which nonessential data is hidden from the user.

Caution: Do not confuse the concept of the interface with terms like graphical user interface (GUI). Although a GUI is, as its name implies, an interface, the term interfaces, as used here, is more general in nature and is not restricted to a graphical interface.

Remember the toaster example in Article 1? The toaster, or any appliance for that matter, is simply plugged into the interface, which is the electrical outlet—see Figure 1. All appliances have access to electricity by complying with using the correct interface: the electrical outlet. The toaster doesn't need to know anything about the implementation, or how the electricity is produced. For all the toaster cares, a coal plant or a nuclear plant could produce the electricity—the appliance does not care which, as long as the interface works.



Click here for a larger image.

Figure 1: Power plant revisited.

As another example, consider an automobile. The interface between you and the car includes components such as the steering wheel, gas pedal, brake, and ignition switch. For most people, aesthetic issues aside, the main concern when driving a car is that the car starts, accelerates, stops, steers, and so on. The implementation, basically the stuff that you don't see, is of little concern to the average driver. In fact, most people would not even be able to identify certain components, such as the catalytic converter and gasket. However, any driver would recognize and know how to use the steering wheel, because this is a common interface. By installing a standard steering wheel in the car, manufacturers are assured that the people in their target market will be able to use the system.

If, however, a manufacturer decided to install a joystick in place of the steering wheel, most drivers would balk at this, and the automobile might not be a big seller (except for some eclectic people who love bucking the trends). On the other hand, as long as the performance and aesthetics didn't change, the average driver would not notice if the manufacturer changed the engine—part of the implementation—of the automobile.

It must be stressed that the interchangeable engines must be identical in every way—as far as the driver's perceptions go. Replacing a four-cylinder engine with an eight-cylinder engine would change the rules, just as changing the current from AC to DC would affect the rules in the power plant example.

The engine is part of the implementation, and the steering wheel is part of the interface. A change in the implementation should have no impact on the driver, whereas a change to the interface might.

What Users See

Interfaces also relate directly to classes. End users do not normally see any classes—they see the GUI or command line. However, programmers would see the class interfaces. Class reuse means that someone has already written a class. Thus, a programmer who uses a class must know how to get the class to work properly. This programmer will combine many classes to create a system. The programmer is the one who needs to understand the interfaces of a class. Therefore, when we talk about users in this article, we primarily mean designers and developers—not necessarily end users. Thus, when we talk about interfaces in this context, we are talking about class interfaces, not GUIs.

Properly constructed classes are designed in two parts—the interface and the implementation.

The Interface

The services presented to an end user comprise the interface. In the best case, only the services the end user needs are presented. Of course, which services the user needs might be a matter of opinion. If you put 10 people in a room and ask each of them to do an independent design, you might receive 10 totally different designs—and there is nothing wrong with that. However, as a rule of thumb, the interface to a class should contain only what the user needs to know. In the toaster example, the user only needs to know that the toaster must be plugged into the interface—which in this case is the electrical outlet.

Identifying the User

Perhaps the most important issue when designing a class is identifying the audience, or users, of the class.

The Implementation

The interface services' implementation details are hidden from the user. One goal regarding the implementation should be kept in mind: A change to the implementation should not require a change to the user's code. This might seem a bit confusing, but this goal is at the heart of the design issue. If the interface is designed properly, a change to the implementation should not require a change to the user's code. Remember that the interface includes the syntax to call a method and return a value. If this interface does not change, the user does not care whether the implementation is changed. As long as the programmer can use the same syntax and retrieve the same value, that's all that matters.

Recall that in the toaster example, although the interface is always the electric outlet, the implementation could change from a coal power plant to a nuclear power plant without affecting the toaster. There is one very important caveat to be made here: The coal or nuclear plant must also conform to the interface specification. If the coal plant produces AC power, but the nuclear plant produces DC power, there is a problem. The bottom line is that both the user and the implementation must conform to the interface specification.

An Interface/Implementation Example

Let's create a simple (if not very functional) Oracle database reader class. We'll write some Java code that will retrieve records from the Oracle database. As we've discussed, knowing your end users is always the most important issue when doing any kind of design. You should do some analysis of the situation and conduct interviews with end users, and then list the requirements for the project. The following are some requirements we might want to use for the database reader:

  • We must be able to open a connection to the database.
  • We must be able to close the connection to the database.
  • We must be able to position the cursor on the first record in the database.
  • We must be able to position the cursor on the last record in the database.
  • We must be able to find the number of records in the database.
  • We must be able to determine whether there are more records in the database (that is, if we are at the end).
  • We must be able to position the cursor at a specific record by supplying the key.
  • We must be able to retrieve a record by supplying a key.
  • We must be able to get the next record, based on the position of the cursor.

With these requirements in mind, we can make an initial attempt to design the database reader class by creating possible interfaces for these end users.

In this case, the database reader class is intended for programmers who require use of a database. Thus, the interface is essentially the application programming interface (API) that the programmer will use. These methods are, in effect, wrappers that enclose the functionality provided by the database system. Why would we do this? We will explore this question in much greater detail later in the article; the short answer is that we might need to customize some database functionality. For example, we might need to process the objects so that we can write them to a relational database. Writing this middleware is not trivial as far as design and coding go, but it is a real-life example of wrapping functionality.

Figure 2 shows a class diagram representing a possible interface to the DataBaseReader class.

Figure 2: A Unified Modeling Language class diagram for the DataBaseReader class.

Note that the methods in this class are all public (remember that there are plus signs next to the names of methods that are public interfaces). Also note that only the interface is represented; the implementation is not shown. Take a minute to determine whether this class diagram generally satisfies the requirements outlined earlier for the project. If you find out later that the diagram does not meet all the requirements, that's okay; remember that OO design is an iterative process, so you do not have to get it exactly right the first time.

Public Interface

Remember that if a method is public, a programmer can access it, and thus, it is considered part of the class interface. Do not confuse the term interface with the keyword interface used in Java and C#—this term is discussed later.

For each of the requirements we listed, we need a corresponding method that provides the functionality we want. Now you need to ask a few questions:

  • To effectively use this class, do you as a programmer need to know anything else about it?
  • Do you need to know how the Oracle code actually opens the Oracle database?
  • Do you need to know how the Oracle code physically positions itself over a specific record?
  • Do you need to know how the Oracle code determines whether there are any more records left?

On all counts, the answer is a resounding no! You don't need to know any of this information. All you care about is that you get the proper return values and that the operations are performed correctly. In fact, the application programmer will most likely be at least one more abstract level away from the implementation. The application will use your classes to open the database, which in turn will invoke the proper Oracle API.

Minimal Interface

Although perhaps extreme, one way to determine the minimalist interface is to initially provide the user no public interfaces. Of course, the class will be useless; however, this forces the user to come back to you and say, "Hey, I need this functionality." Thus, you add interfaces only when it is requested. Never assume that the user needs something.

Creating wrappers might seem like overkill, but there are many advantages to writing them. To illustrate, there are many middleware products on the market today. Consider the problem of mapping objects to a relational database. There are OO databases on the market today that are perfect for OO applications. However, there is one itty-bitty problem: Most companies have years of data in legacy relational database systems. How can a company embrace OO technologies and stay on the bleeding edge when its data is in a relational database?

First, you can convert all your legacy, relational data to a brand-new OO database. Anyone who has suffered the acute (and chronic) pain of any data conversion knows that this is to be avoided like the plague.

Second, you can use a middleware product to seamlessly map the objects in your application code to a relational model. This is a much better solution as long as relational databases are so prevalent. There might be an argument stating that OO databases are much more efficient for object persistence than relational databases.

Object Persistence

Object persistence refers to the concept of saving the state of an object so that it can be restored and used at a later time. An object that does not persist basically dies when it goes out of scope. For example, the state of an object can be saved in a database.

However, in the current business environment, relational-to-object mapping is a great solution. For brand-new OO applications that need to create new data stores, using an OO database might be a viable choice. However, OO databases have not gained wide acceptance.

Standalone Application

Even when creating a new OO application from scratch, it might not be easy to avoid legacy data. This is due to the fact that even a newly created OO application is most likely not a standalone application and might need to exchange information stored in relational databases (or any other data storage device, for that matter).

Let's return to the database example. Figure 2 shows the public interface to the class, and nothing else. Of course, when this class is complete, it will probably contain more methods, and it will certainly contain attributes. However, you as a programmer using this class do not need to know anything about these private methods and attributes. You certainly don't need to know what the code looks like within the public methods. You simply need to know how to interact with the interfaces.

What would the code for this public interface look like? Let's look at the open() method:

public void open(String Name){
   /* Some application-specific processing */
   /* call the Oracle API to open the database */
   /* Some more application-specific processing */
};

In this case, you, wearing your programmer's hat, realize that the open method requires String as a parameter. Name, which represents a database file, is passed in, but it's not important to explain how Name is mapped to a specific database for this example. That's all we need to know. Now comes the fun stuff—what really makes interfaces so great!

Just to annoy our users, let's change the database implementation. Last night we translated all the data from an Oracle database to an SQLAnywhere database (we endured the acute and chronic pain). It took us hours—but we did it.

Now the code looks like this:

public void open(String Name){
   /* Some application-specific processing */
   /* call the SQLAnywhere API to open the database */
   /* Some more application-specific processing */
};

To our great chagrin, this morning not one user complained. Even though the implementation changed, the interface did not! As far as the user is concerned, the calls are still the same. The code change for the implementation might have required quite a bit of work (and the module with the one-line code change would have to be rebuilt), but not one line of application code that uses this DataBaseReader class needed to change.

Code Recompilation

Dynamically loaded classes are loaded at runtime—not statically linked into an executable file. When using dynamically loaded classes, like Java does, no user classes would have to be recompiled. However, in statically linked languages such as C++, a link is required to bring in the new class.

By separating the user interface from the implementation, we can save a lot of headaches down the road. In Figure 3, the database implementations are transparent to the end users, who see only the interface.



Click here for a larger image.

Figure 3: The interface.

Conclusion

In this installment of the object-oriented thought process series, we began the process of starting to think in terms of objects. The though process is different when designing systems in an object-oriented manner. In the next column, we will continue this process by refining how we think in terms of objects and their messages.

References

Fowler, Martin. UML Distilled. Addison-Wesley Longman, 1997.

Gilbert, Stephen, and Bill McCarty. Object-Oriented Design in Java. The Waite Group, 1998.

Meyers, Scott. Effective C++. Addison-Wesley, 1992.

About the Author

Matt Weisfeld is a faculty member at Cuyahoga Community College (Tri-C) in Cleveland, Ohio. Matt is a member of the Information Technology department, teaching programming languages such as C++, Java, and C# .NET as well as various Web technologies. Prior to joining Tri-C, Matt spent 20 years in the information technology industry gaining experience in software development, project management, business development, corporate training, and part-time teaching. Matt holds an MS in computer science and an MBA in project management.

The articles in this series are adapted from The Object-Oriented Thought Process (published by Sams Publishing). Matt has published two other computer books, and more than a dozen articles in magazines and journals such as Dr. Dobb's Journal, The C/C++ Users Journal, Software Development Magazine, Java Report, and the international journal Project Management. Matt has presented at conferences throughout the United States and Canada.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date