Chapter 6. Object-Oriented Programming

Windows and web programs are enormously complex programs that present information to users in graphically rich ways, offering complicated user interfaces, complete with drop-down and pop-up menus, buttons, listboxes, and so forth. Behind these interfaces, programs model complex business relationships, such as those among customers, products, orders, and inventory. You can interact with such a program in hundreds, if not thousands, of different ways, and the program must respond appropriately every time.

To manage this enormous complexity, programmers have developed a technique called object-oriented programming. It is based on a very simple premise: you manage complexity by modeling its essential aspects. The closer your program models the problem you are trying to solve, the easier it is to understand (and thus to write and to maintain) that program.

Programmers refer to the problem you are trying to solve and all the information you know that relates to your problem as the problem domain. For example, if you are writing a program to manage the inventory and sales of a company, the problem domain would include everything you know about how the company acquires and manages inventory, makes sales, handles the income from sales, tracks sales figures, and so forth. The sales manager and the stock room manager would be problem domain experts who can help you understand the problem domain.

A well-designed object-oriented program is filled with objects (things) from the problem domain. For example, if the problem domain is an Automatic Teller Machine for banking, the things (objects) in your domain might include customers, accounts, monthly statements, and so forth.

At the first level of design, you’ll think about how these objects interact and what their state , capabilities, and responsibilities are:

State

A programmer refers to the current conditions and values of an object as that object’s state. For example, you might have an object representing a customer. The customer’s state includes the customer’s address, phone number, and email, as well as the customer’s credit rating, recent purchase history, and so forth.

Capabilities

The customer has many capabilities , but a developer cares only about modeling those that are relevant to the problem domain. Thus, a customer object might be able to make a deposit, transfer funds, withdraw cash, and so forth.

Responsibilities

Along with capabilities come responsibilities . The customer object is responsible for managing its own address. In a well-designed program, no other object needs to know the details of the customer’s address. The address might be stored as data within the customer object, or it might be stored in a database, but it is up to the customer object to know how to retrieve and update his own address. (The monthly-statement object should not know the customer’s address, though it might ask the customer object for the customer address. This way, when the customer moves, the responsibility for knowing the new address is located in a single object: the customer.) This ability for an object to own responsibility for its own internal state and actions is known as encapsulation .

Of course, all of the objects in your program are just metaphors for the objects in your problem domain.

Humans are model-builders. We create models of the world to manage complexity and to help us understand problems we’re trying to solve. You see models all the time. Maps are models of roadways. Globes are models of the Earth. Atomic models are models of the interaction of subatomic particles.

Models are simplifications. There is little point to a model that is as complex as the object in the problem domain. If you had a map of the United States that had every rock, blade of grass, and bit of dirt in the entire country, the map would have to be as big as the country itself.[2] Your road atlas of the U.S. eschews all sorts of irrelevant detail, focusing only on those aspects of the problem domain (such as the country’s roads) that are important to solving the problem (getting from place to place). If you want to drive from Boston to New York City, you don’t care where the trees are; you care where the exits and interchanges are located. Therefore, the network of roads is what appears in the atlas.

Albert Einstein once said: “Things should be made as simple as possible, but not any simpler.” A model must be faithful to those aspects of the problem domain that are relevant. For example, a road map must provide accurate relative distances. The distance from Boston to New York must be proportional to the actual driving distance. If one inch represents 25 miles at the start of the trip, it must represent 25 miles throughout the trip, or the map will be unusable.[3]

A good object-oriented design is an accurate model of the problem you are trying to solve. Your design choices influence not only how you solve the problem, but in fact they influence how you think about the problem. A good design, like a good model, allows you to examine the relevant details of the problem without confusion or distraction.

Classes and Objects

We perceive the world to be composed of things. Look at your computer. You do not see various bits of plastic and glass amorphously merging with the surrounding environment. You naturally and inevitably see distinct things: a computer, a keyboard, a monitor, speakers, pens, paper. Things.

More importantly, even before you decide to do it, you’ve categorized these things. You immediately classify the computer on your desk as a specific instance of a type of thing: this computer is one of the type computer. This particular pen in my pocket is an instance of a more general type of thing, pens. It is so natural you can’t avoid it, and yet the process is so subtle, it’s difficult to articulate. When I see my dog Milo, I can’t help also seeing him as a dog, not just as an individual entity.

The theory behind object-oriented programming is that for computer programs to accurately model the world, the programs should reflect this human tendency to think about individual things and types of things. In C#, you do that by creating a class to define a type and creating an object to model a thing.

A class defines a new type of thing. The class defines the common characteristics of every object of that new type. For example, you might define a class Car. Every car will share certain characteristics (wheels, brake, accelerator, and so forth). Your car and my car both belong to the class of Cars; they are of type Car.

An object is an individual instance of a class. Each individual car (your particular car, my particular car) is an instance of the class Car, and thus is an object. An object is just a thing.

When you define a class, you describe the characteristics and behavior of objects of that type. In C#, you describe characteristics with member fields.

    class Dog
    {
     private int weight; // member field
     private String name; // member field

Member fields are used to hold each object’s state. For example, the state of the Dog is defined by its current weight and name. The state of an Employee might be defined by (among other things) her current salary, management level, and performance rating. Chapter 8 includes a full discussion of member fields .

You define the behavior of your new type with methods. Methods contain code to perform an action:

    class Dog
    {
     private int weight;
     private String name;public void Bark( )    // member method
     {
     // code here to bark
     }

A class typically defines a number of methods to do the work of that class. A Dog class might contain methods for barking, eating, napping, and so forth. An Employee class might contain methods for adjusting salary, submitting annual reviews, and evaluating performance objectives.

Methods can manipulate the state of the object by changing the values in member fields, or a method could interact with other objects of its own type or with objects of other types. This interaction among objects is crucial to object-oriented programming.

For example, a method in Dog might change the state of the Dog (for example, a Feed method might change the Dog’s weight), interact with other Dogs (Bark and Sniff), or interact with People (BegForFood). A Product object might interact with a Customer object, and a Video object might interact with an EditingWindow object.

Designing a good C# program is not unlike forming a good team; you look for players—or objects, in the case of a program—with different skills to whom you can delegate the various tasks you must accomplish. Those players cooperate with one another to get the job done.

In a good object-oriented program, you will design objects that represent things in your problem domain. You will then divide the work of the program among your objects, assigning responsibility to objects based on their ability.

Class Relationships

The heart of object-oriented design is establishing relationships among the classes. Classes interact and relate to one another in various ways.

The simplest interaction is when a method in one class is used to call a method in a second class. For example, the Manager class might have a method that calls the UpdateSalary method on an object of type Employee. We then say that the Manager class and the Employee class are associated. Association among classes simply means they interact.

Some complicated types are composed of other types. For example, an automobile might be composed of wheels, engine, transmission, and so forth. You might model this by creating a Wheel class, an Engine class, and a Transmission class. You could then create an Automobile class, and each automobile would have four instances of the Wheel class and one instance each of the Engine and Transmission class. This is commonly called the has-a relationship. Another way to view this relationship is to say that the Automobile class aggregates the Wheel, Engine, and Transmission classes, or that the Car class is composed of Wheel, Engine, and Transmission objects.

This process of aggregation (or composition) allows you to build very complex classes by assembling and combining relatively simple classes. The .NET Framework provides a String class to handle text strings. You might create your own Address class out of five text strings (address line 1, address line 2, city, state, and zip). You might then create a second class, Employee, which has as one of its members an instance of Address.

Object-oriented programming is built on three pillars: encapsulation , specialization, and polymorphism.

Each class should be fully encapsulated; that is, it should fully define the state and responsibilities of that type. Specialization allows you to establish hierarchical relationships among your classes. Polymorphism allows you to treat a group of hierarchically related objects in a similar way and have the objects sort out how to implement the programming instructions.

The first pillar of object-oriented programming is encapsulation. The idea behind encapsulation is that you want to keep each type or class discreet and self-contained, so that you can change the implementation of one class without affecting any other class.

A class that provides a method that other classes can use is called a server. A class that uses that method is called a client. Encapsulation allows you to change the details of how a server does its work without breaking anything in the implementation of the client.

This is accomplished by drawing a bright and shining line between the public interface of a class and its private implementation . The public interface is a contract issued by your class that consists of two parts. The first part says, “I promise to be able to do this work.” Specifically, you’ll see that a public interface says, “call this method, with these parameters, and I’ll do this work, and return this value.” The second part says “You are allowed to access these values (and no others).” C# implements this second part of the interface through properties (discussed in Chapter 7).

A client can rely on a public interface not to change. If the public interface does change, then the client must be recompiled and perhaps redesigned.

On the other hand, the private implementation is, as its name implies, private to the server. The designer of the server class is free to change how it does the work promised in the public interface, so long as it continues to fulfill the terms of its implicit contract: it must take the given parameters, do the promised work, and return the promised value and allow access to the public properties.

For example, you might have a public method NetPresentValue( ) that promises as follows: “Give me a dollar amount and a number of years, and I’ll return the net present value.” How you compute that amount is your business; as long as you return the net present value given a dollar amount and number of years, the client doesn’t care if you look it up in a table, compute the value, or ask your friend who is really good at math.

You might implement your Net Present Value interface initially by keeping a table of values. Some time later, you might change your program to compute the net present value using the appropriate algebra. That is encapsulated within your class, and it does not affect the client. As long as you don’t change the public interface (that is, as long as you don’t change the number or type of parameters expected, or change the type of the return value), your clients will not break when you change the implementation.

Specialization

The second pillar of object-oriented programming , specialization , is implemented in C# through inheritance ; specifically by declaring that a new class derives from an existing class. The specialized class inherits the characteristics of the more general class. The specialized class is called a derived class, while the more general class is known as a base class.

The specialization relationship is referred to as the is-a relationship. A dog is a mammal; a car is a vehicle. (Dog would be derived from the base class Mammal and Car from the base class Vehicle.)

For example, a Manager is a special type of Employee. The Manager adds new capabilities (hiring, firing, rewarding, praising) and a new state (annual objectives, management level, etc.). The Manager, however, also inherits the characteristics and capabilities common to all Employees. Thus, a Manager has an address, a name, and an employee ID, and Managers can be given raises, can be laid off, and so forth.

Specialization allows you to create a family of objects. In Windows, a button is a control. A listbox is a control. Controls have certain characteristics (color, size, location) and certain abilities (can be drawn, can be selected). These characteristics and abilities are inherited by all of their derived types, which allows for a very powerful form of reuse. Rather than cutting and pasting code from one type to another, the derived type inherits the shared fields and methods. If you change how a shared ability is implemented in the base class, you do not have to update code in every derived type; they inherit the changes.

You’ll see specialization at work in Chapter 11.

Object-Oriented Analysis and Design

Before you program anything, other than a trivial demonstration program, you need to take two steps: analysis and design . Analysis is the process of understanding and detailing the problem you are trying to solve. Design is the actual planning of your solution.

With trivial problems (such as computing the Fibonacci series), you may not need an extensive analysis period, but with complex business problems, the analysis process can take weeks, or even months. One powerful analysis technique is to create what are called use-case scenarios , in which you describe in some detail how the system will be used. Among the other considerations in the analysis period are determining your success factors (how do you know if your program works?) and writing a specification of your program’s requirements.

Once you’ve analyzed the problem, you design the solution. Imagining the classes you will use and their inter-relationships is key to the design process. You might design a simple program on the fly, without this careful planning; but in any serious business application, you will want to take some time to think through the issues.

There are many powerful design techniques you might use. How much time you put into design[4] before you begin coding will depend on the philosophy of the organization you work for, the size of your team, and your background, experience, and training.[5]



[2] Steven Wright joke: “I have a model of the United States. One inch equals one inch. I live at E5.”

[3] Okay, not strictly true. When you flatten a globe into a map, something has to be distorted. The Mercator projection represents the lines of longitude as straight vertical lines, at the cost of grossly distorting size near the poles, and thus are of use to their intended audience (sailors). Unfortunately, these maps are ubiquitous in school rooms, leading generations of children to believe that Greenland is bigger than the United States (when, in fact, the U.S. is more than four times as large).

[4] See The Unified Modelling Language User Guide, Second Edition, by Grady Booch, Ivar Jacobson, and James Rumbaugh (Addison Wesley, 2005).

[5] See Agile Software Development Principles, Patterns, and Practices by Robert C. Martin (Prentice Hall, 2003).