Chapter 2. Introducing Object-Oriented Programming

After completing this chapter, you will be able to

  • Describe the key concepts of object-oriented programming.

  • Understand how these concepts are supported by C++ language constructs.

  • Understand the major development benefits of object-oriented programming.

  • Create and use simple classes.

What is object-oriented programming?

Object-oriented programming is a paradigm that provides a natural way to develop many kinds of systems. We perceive the world as consisting of objects: tables, chairs, computers, cars, bank accounts, football games, and so on. It is a natural human trait to try to organize these objects, arranging them into some form of classification and choosing to highlight certain features of objects in preference to others. So, dogs and cats are mammals, toasters and refrigerators are appliances, swimming and tennis are sports, Toyotas and Fords are cars, trucks and cars are vehicles, and so on.

There can be many levels to these categories and many ways to classify the objects in the world. How people classify things depends to a large extent on what they want to do with them as well as the relevant features of the objects themselves. For example, a retailer of household appliances is likely to use different categories—possibly deeper and richer—for its products than a typical homeowner. When grouping objects into classification schemes, we also tend to highlight certain attributes of objects in preference to others. For instance, to an engineer, a car’s color might not matter, but it might figure heavily in the mental model of car classifications used by a Ferrari salesperson.

Object-oriented programming lets us build hierarchies of objects, creating them and defining how they are related. As long ago as the 1960s, researchers realized that many computer programs modeled entities that can be named, and that their properties and behavior can be described. Examples of such objects might be bank accounts, arrays, files, and users, all of which are analogous to objects in the real world.

Object-oriented programming can crudely be characterized as identifying the objects relevant to the problem, organizing them into hierarchies, adding attributes to the objects to describe the features relevant to the problem context, and adding functions (methods) to the objects such that they can perform their required tasks. The details are a little more complicated, but essentially, it is a simple process.

Yet, simple doesn’t necessarily mean easy. A collection of objects could potentially be classified in many ways. The ability to identify the important attributes of objects and to form good abstractions and appropriate hierarchies is crucial. Even within the context of a problem domain, it’s sometimes hard to determine the correct levels of abstraction and suitable classification hierarchies. Just deciding which class or grouping an object belongs to can be very difficult. As philosopher Ludwig Wittgenstein pointed out in his 1953 book Philosophical Investigations, some objects will bear more of a family resemblance to a concept than others; for example, hockey and tennis are more obviously sports than are chess and synchronized swimming.

Features of object-oriented programming languages

I’ve already pointed out that object-oriented programming means defining and building hierarchies of objects and defining their properties and behavior. You can do this to a certain extent in any programming language, just the same as you could, theoretically, take a trip over the Rockies in a golf cart, but it is much easier to do object-oriented programming if you use a language that is designed to support object-oriented programming methods.

Object-oriented programming languages such as C++ and C# are characterized by three key features: encapsulation, inheritance, and polymorphism. These features support this natural process of identifying and classifying objects. Let’s take a closer look at each one.

Encapsulation

One of the problems faced by software developers is that the systems we are developing are becoming increasingly larger and more complex. Encapsulation helps to keep things manageable by breaking an application down into small, self-contained entities. For example, if you’re building an accounting system, you’ll probably need objects to represent accounts and invoices. After you’ve developed the Account class, you no longer need to worry about the details of the implementation of the class. You can use the class anywhere in your application in much the same way you would use a built-in type, such as an integer. The class exposes the essential features of the Account object while hiding the implementation details.

The account’s name and the state of its balance are some of the attributes of the object in which the client is interested and needs to know. Details of how the account name is stored—whether it’s an array of 50 characters or a string object, or the fact that the account’s balance is maintained as a currency variable—are irrelevant to the client. The process of hiding the data structures and implementation details of an object from other objects in the system is called encapsulation (sometimes also known as data hiding), and it prevents the other objects from accessing details about which they don’t need to know. Encapsulation makes large programs easier to comprehend; data hiding makes them more robust.

Objects can interact with other objects through only their publicly exposed attributes and methods. The more attributes and methods that are publicly exposed, the more difficult it will be to modify the class without affecting the code that uses the class. When done properly, the inner workings of a class can be changed without affecting the code that uses objects created, or instantiated, from that class. The programmer would have to worry only about the methods in the class that accessed that variable rather than worry about all the places in the application that an object instantiated from that class might be called.

Inheritance

The natural tendency for humans to classify objects into hierarchies is useful from a programmer’s perspective and is supported in object-oriented languages, including C++, by inheritance. Inheritance provides two benefits to the C++ programmer. First, and most important, it lets you build hierarchies that express the “is a” relationships between types. Suppose that you have two classes, SavingsAccount and CheckingAccount, both of which are derived from the parent Account class. If you have a function that requires an Account as an argument, you can pass it a SavingsAccount or a CheckingAccount because both classes are types of Account. Account is a general classification, and CheckingAccount and SavingsAccount are more specific types. The second benefit of object-oriented programming is that classes can inherit features from classes higher in the hierarchy. Instead of developing new classes from scratch, new classes can inherit the functionality of existing classes and then modify or extend this functionality. The parent class from which the new class inherits is known as the base class, and the new class is known as the derived class.

One of the major tasks facing developers is finding appropriate classifications for the objects and classes in their programs. For example, if you need to develop classes for a driving game, it makes more sense for you to develop a general car class and then use this class as a base class for specific car types such as sportscar or truck. These derived classes would then extend or modify the general car class by adding new attributes and methods or by overriding existing methods. Composing objects from subobjects—for example, a car consisting of an engine and a chassis—can also simplify the development effort. Doing it this way, each of the objects is simpler and therefore easier to design and implement than the collective whole.

Polymorphism

The third feature of object-oriented programming languages is polymorphism, which is Greek for “many forms.” It is quite a hard concept to define, so I’ll use some examples to show you what polymorphism is and leave the precise definitions to more academic writers.

Polymorphism essentially means that classes can have the same behavior but implement it in different ways. Consider several different types of vehicle: they all need to be started, so in programming terms, we could say that all vehicles have “start” functionality. Exactly how starting is implemented depends on the vehicle. If it is a Ford Model T, starting will mean manually cranking the starting handle at the front of the vehicle, but if it is a modern car, starting will mean turning the key in the ignition. If the vehicle is a steam locomotive, starting will be a very different and more complex process, indeed.

As another example, consider the aforementioned SavingsAccount and CheckingAccount types. All types derived from Account share certain functionality, such as the ability to deposit, withdraw, and query the balance. They might implement them in different ways because CheckingAccount might permit an overdraft, whereas SavingsAccount might accrue interest, but they all work the same way. This means that if I’m passed an Account, it doesn’t matter exactly what type of account it is; I can still deposit funds, withdraw funds, and query the balance. This functionality is useful in programming terms because it gives you the ability to work with generic object types—accounts and vehicles—when you’re not concerned with the way in which each class implements functionality.

Classes and objects

Up to this point in the chapter, the terms “class” and “object” have been used fairly interchangeably. However, classes and objects aren’t the same thing, and we need to clarify the differences between these terms. As the name implies, object-oriented programming is about objects. An object is composed of data that describes the object and the operations that can be performed on the object. However, when you create an application in C++, you define classes, not objects.

A class is a user-defined type; it encapsulates both the data and the methods that work on that data. With the exception of static functions, you cannot use classes directly. A class is like a template, which is used to create (instantiate) objects. Just as you have to declare an int variable before you can use it, you also have to instantiate an object of the class before you can use it.

For example, you would not define an Animal object. Instead, you would define an Animal class along with its attributes and methods. The class represents the concept, so the Animal class does not represent a specific animal but the class of all animals. When you want to use an Animal object, you have to instantiate an Animal object from the class. The class can be considered as the abstract representation of an entity, whereas the instantiation of the class—the object—is the concrete representation.

Benefits to the development life cycle

There are three key benefits to object-oriented programming: comprehensibility, reusability, and extensibility. Breaking code down into classes makes it more comprehensible by imposing a structure as programs grow larger and larger. The ideal is to assemble object-oriented systems from prewritten classes and to make the required modifications to support the new requirements by using inheritance and composition to derive new classes from the existing classes. The existing classes are reused as building blocks and not altered in any way. Creating systems from reusable components naturally leads to higher productivity, which is probably the most frequently cited benefit of object-oriented approaches. Object-oriented programming should also result in higher-quality systems. When you reuse classes, it means that you are using code that has already been tested and proven in earlier projects; thus, it is likely to contain fewer bugs than classes developed from scratch. Over time, any bugs that might have existed have been found and fixed in these classes, whereas code that is written from scratch has yet to pass through the same bug detection and fixing process.

The features (encapsulation, inheritance, and polymorphism) of object-oriented programming also provide benefits. Encapsulation makes it easier to scale up from small systems to large systems. For the most part, regardless of the size of the system, the developer is simply creating objects. Large systems might require more objects than small systems, but the level of complexity facing the developer is not significantly increased. Inheritance helps to improve the flexibility and extensibility of systems, hence reducing their costs to maintain. Deriving new classes from existing classes provides additional functionality and makes it possible to extend the software without altering the existing classes.

Finally, data hiding also leads to more secure systems. The state of an object can be modified only by its publicly exposed methods, which increases the predictability of object behavior.

A simple example

The following simple example should serve to show you how to create a class, instantiate objects, and access member functions and attributes.

  1. Start Microsoft Visual Studio 2012.

  2. On the File menu, point to New, and then click Project.

    The New Project dialog box opens.

  3. In the navigation pane on the left, under Templates, click Visual C++, and then click CLR.

  4. In the center pane choose CLR Console Application.

  5. Toward the bottom of the dialog box, in the Name box, type Animals.

  6. Click the Location box and select a location for the new project (or click Browse to find it), and then click OK.

    A screenshot of the New Project dialog box. In the center panel, the CLR Console Application is one of the three Visual C++ CLR project types. In the Name box, the word “Animals” has been typed. By default, the solution name is whatever you specify as the project name.
  7. The file Animals.cpp should already be open in the editor. If it is not, in Solution Explorer, in the Source Files folder, double-click the Animals.cpp file.

  8. Immediately under the using namespace System; line, add the following class definition:

    ref class Animal
    {
        int legs;
        String ^name;
    };

    To declare a class in C++, you use the keywords ref class followed by a name for the class—Animal in this example—and then you list all the member variables and functions for the class between opening and closing braces ({ and }).

    So far, you have created an Animal class with an int variable for the number of its legs and a String variable for its name. As it stands, no other application or class will be able to access these variables. The members of a class—data and methods—are private by default and can only be accessed by methods of the class itself. C++ provides three access modifiers, public, private, and protected, which you use to specify the visibility of the various members of the class.

  9. Add the keyword public followed by a colon (:) on a new line between the opening brace and the first variable declaration.

    ref class Animal
    {
    public:
        int legs:
        String ^name;
    };

    By declaring the variables after the keyword public, you make both of them accessible. However, it is not usually a good idea to allow other classes and parts of your application access to the variables of a class.

    As discussed earlier in the section on encapsulation, it’s better to keep the implementation details of a class hidden from users of that class and to control the access to the class’s data through functions. In the next step, we use the keyword private to prevent direct access to the String variable of the class. We’ll leave the int variable legs with public access, simply to show how it can then be directly accessed by the main application.

  10. Add the keyword private followed by a colon (:) between the first int variable and the second String variable.

    ref class Animal
    {
    public:
        int legs;
    private:
        String ^name;
    };

    To provide access to the private String variable, public accessor functions and methods need to be added to the class to allow other functions to manipulate its value.

  11. After the declaration of the int variable and before the private access modifier, add the following method declarations or implementation lines:

    void SetName(String ^nm)
    {
      name = nm;
    }
    
    String^ GetName()
    {
      return name;
    }

    Because these methods are small functions, it’s easiest to declare and implement them as inline functions. Inline functions are explained further in Chapter 6, when we go into classes in greater detail.

You have probably noticed the ref keyword. This C++/CLI keyword simplifies the interaction with .NET Framework components. By placing ref in front of the class keyword, the class becomes a managed class. When the object is instantiated, it is created on the Common Language Runtime (CLR) heap. The lifetime of an object instantiated from the class is managed by the .NET Framework’s garbage collector. When the object goes out of scope, the memory used by the object is garbage-collected automatically. ref classes are known as reference types because the variable does not actually contain the object; rather it is a pointer to the memory location of the object, known as a handle.

However, there are performance issues to consider when using reference types. The memory has to be allocated from the managed heap, which could force a garbage collection to occur. In addition, reference types must be accessed via their handles, affecting both the size and speed of the compiled application.

Because of these performance issues, the .NET Framework also supports value types. Value types are objects created on the stack. The variable contains the object itself rather than a handle to the object. Hence, the variable doesn’t have to be dereferenced to manipulate the object, which of course improves performance. To declare a value type class, the value keyword should be used instead of the ref keyword. In this case, the variables would have been created on the stack. Instead of declaring handles for this class and then creating the objects on the CLR heap, the objects are declared in the same way as the built-in C++ types, and the member variables are accessed by the dot operator.

Now that you have written the Animal class, your application can use it just as the application would use a built-in type.

  1. In the main function, delete the following line:

    Console::WriteLine(L"Hello World");
  2. Declare and create two Animal objects in your main function.

    Animal cat, dog;
  3. Use the member function SetName to assign the names Cat and Dog to the respective cat and dog objects, and set the legs variable for both objects to 4.

    cat.SetName("Cat");
    cat.legs = 4;
    dog.SetName("Dog");
    dog.legs = 4;

    To access the member variables and functions of an object, you use the dot operator (.). You can read this as “set the name of the cat to ‘Cat’,” with the dot operator relating the function to the object on which it is operating.

    Having created a couple of Animal objects and assigned data to them, you are now going to display that data on the screen.

  4. Add the following lines:

    Console::WriteLine("Animal 1");
    Console::Write("Name:   ");
    Console::WriteLine(cat.GetName());
    Console::Write("Legs:   ");
    Console::WriteLine(cat.legs);
    Console::WriteLine();
    Console::WriteLine("Animal 2");
    Console::Write("Name:   ");
    Console::WriteLine(dog.GetName());
    Console::Write("Legs:  ");
    Console::WriteLine(dog.legs);
    Console::WriteLine();

    Now, it’s time to build the application.

  5. On the Build menu, click Build Solution or use the keyboard shortcut F6.

    In case you’ve had any problems putting the application together from the fragments in the preceding steps, the entire application is listed here:

    #include "stdafx.h"
    
    using namespace System;
    
    ref class Animal
    {
    public:
        int legs;
    
        void SetName(String ^nm)
        { name = nm; }
    
        String^ GetName() { return name; }
    private:
        String ^name;
    };
    
    int main(array<System::String ^> ^args)
    {
        Animal cat, dog;
    
        cat.SetName("Cat");
        cat.legs = 4;
        dog.SetName("Dog");
        dog.legs = 4;
        Console::WriteLine("Animal 1");
        Console::Write("Name:   ");
        Console::WriteLine(cat.GetName());
        Console::Write("Legs:   ");
        Console::WriteLine(cat.legs);
        Console::WriteLine();
        Console::WriteLine("Animal 2");
        Console::Write("Name:   ");
        Console::WriteLine(dog.GetName());
        Console::Write("Legs:   ");
        Console::WriteLine(dog.legs);
        Console::WriteLine();
        return 0;
    }
  6. If the build was successful, run the application by clicking Start Without Debugging on the Debug menu, or use the keyboard shortcut Ctrl+F5.

    A screenshot of the console window that opens when you run the application. The output shows that Animal1 has the name “Cat” and four legs; Animal2 also has four legs and the name “Dog”. The message “Press any key to continue” appears at the bottom of the output.

Quick reference

To

Do this

Create a class.

Use the keyword class.

Control the visibility of variables and methods.

Use the access control keywords public, private, or protected, followed by a colon (:).

Declare a reference type class.

Place the ref keyword before the class specifier.

Declare a value type class.

Place the value keyword before the class specifier.

Instantiate a reference type class object.

Use the name of the class when declaring an object; for example:

Animal cat;
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.80.101