Ïðèãëàøàåì ïîñåòèòü
×àðñêàÿ (charskaya.lit-info.ru)

Top-Down Approach to Software Development

Table of Contents
Previous Next

Top-Down Approach to Software Development

Ever since we began to think about science, we have tried to categorize, define, and formulate everything that exists within our world. When it comes to programming, this is no exception since computers stem from a strong background in mathematics and logic. The beauty of object-oriented programming is that it allows us not only to put code and data where it belongs, but we can actually categorize and define our programs the way we think about real entities in our world. It's much easier to think about problems in a very general manner before we dive into the details. This makes it easier to assess the time, risks, and various resources involved on a project.

When we develop our applications, we can divide our programs into parts, or large modules, such as different presentation layers, database access objects, search engines and security components. When developing our modules as large, singular units, we are guaranteed to ensure that changes to one object will not affect another. Likewise, we may be able to reuse components across several applications. We can also break these modules down even further by using sub-modules and even further to single objects, which is the smallest component in an object-oriented program. Let's take a look at the smallest component in an object-oriented program now, which is the class.

Classes

A class is the definition or representation of a specific type of data and classes serve as a way to model all the different types of objects in our system. When we want to define a new object, we would first use the class keyword to define it before we can use it in our PHP scripts. A clear distinction between a class and an object is that classes define the objects that we use in our programs. Before we talk about how to build a class, I want you to start thinking that a class is a representation of one single idea. It's important that when you design your classes, they serve one purpose and provide all the behavior that would expected from that single idea.

A class in PHP contains three main components: members (referred to as data or attributes), methods, and constructors. A member is a piece of data that the object contains. Objects can have any number of members. For example, if we were to model a car using a class, a steering wheel or transmission unit must be defined as a member of the Car class. Methods are the services that the object provides to its clients that use and manipulate its internal members. For example, a Car class could provide a method to turn the vehicle and utilize its internal steering wheel.

A constructor is a special method that initializes the object into its ready state. There can only be one constructor for an object in PHP. In a Car class, it would make sense to add its body, engine, tires, transmission, seats, and so on to the car. When clients want to use the methods on an object, the constructor ensures that each method will carry out the operation successfully and return the expected result. For example, to turn on the radio inside your car, there has to be radio installed. In this instance, the constructor is responsible for ensuring that the radio has been installed before it's used.

Outside of initializing the object to a valid ready state, another key difference is that a constructor has no explicit return value. All constructors return a newly allocated variable to use in your program. Therefore, it is illegal to return a value in the class's constructor. We will talk more about using objects in your programs in the next section.

Ensuring objects and their constructors are designed properly is a problem that many developers often face. When the class forces programmers to set members of the object prior to using its methods or when the class forces the programmer to follow a specific ordering when calling the object's methods, it creates confusing and somewhat obfuscated code. We use OOP to avoid this from happening all together. If the class is engineered to not take advantage of its constructor to initialize key parts of the class then the flaw is in the poor design. Don't fall into the same trap.

Important 

A well designed class removes a lot of programming, debugging, and maintenance hurdles.

Let's take a look at the general syntax for the class in PHP, which illustrates the use of the three types of components:

    class ClassName [extends ParentclassName]
    {
        var $member1;
        var $member2;
        ...
        var $memberN;

        // Constructor
        function ClassName()
        {
        }

        function method1()
        {
        }

        function method2()
        {
        }
    ...

        function methodN()
        {
        }
    }

As you can see, a class is nothing but a set of defined members (variables) and methods (functions). Members can be either primitive data types, such as integers or strings, or more complex types such as arrays or other objects. Since PHP does not require you to define types, you can just name your variables at the top of the class as shown above.

With PHP you can create variables on the fly in your functions; they will work as intended. However, it's not considered good practice to do this. This is because when other programmers look at your class, they should immediately know all of its members before looking at the implementations of the functions.

Methods are simply all the services that this class guarantees to provide to its clients. The clients can be other programs, other objects, a script, and so on.

Let's create the code for a Car class. In this example we start defining our class, using the class keyword in the second line. It's good software engineering practice to capitalize the first letter of all your class names to distinguish them from variables and functions.

Programmers have been doing this for years in various other languages. It's easy to identify the constructor from various other methods in the class. It's also a good habit to name your files with the class name, such as Car.php. A file should contain one class only. If you have many classes that relate to each other, such as a collection of classes of basic data types, you should place them in a subdirectory under your main application. If you are working on a large project, this is a required practice.

Important 

As systems become larger, it will be a requirement that you use a tree-like directory structure to hold all the classes that are used in your web application. You should use include_once() or require_once() to add classes to your source files as you need them.

    <?php
    //Car.php
    class Car
    {

In an incredibly simple model of a car, the class contains engine and a key representation to start the car. An actual car would have a body, doors, a pedal and a brake, a steering wheel, transmission, and lots more, but this is just for demonstration:

        var $engine;
        var $requiredKey;

Our car also has a constructor that sets up its engine and a key to start the car. If we did not initialize these elements of the car, any future calls to start() and stop() would fail to work and would return errors. As mentioned before, the job of the constructor is to initialize all the elements of the object to ensure all its services can be used whenever required.

Notice that if you want to reference a member of the class, you must put a $this-> keyword in front of the member name. This differs from Java or C++ where it's optional. This is because PHP is not very good at handling the scope of variables. In PHP, there are three levels of namespaces where variables are stored (a namespace is basically a collection of variable names).

The lowest level namespace is used for local variables inside functions or methods. Any variable created at this level is added to the local namespace. The next namespace contains all the members of an object. The highest level namespace is used for global variables. The $this keyword tells PHP that you want the variable from the object's namespace (the middle level). If you forget to include the $this keyword, you'll be creating an entirely new variable in the local namespace. Since this references an entirely different variable from that intended, you will have some difficult logic errors to debug.

Important 

Make sure you turn up error reporting, which is discussed in the next chapter, and add some assertions to protect you from this common error when developing your classes.

The start() method will start the car for the user, supplying their key. If the key is correct, the car object will tell the engine to start up:

        // Constructor
        function Car()
        {
            $this->requiredKey = new DefaultKey();
            $this->engine = new Engine();
        }

        function start($key)
        {
            if ($key->equals($this->requiredKey)) {
                $this->engine->start();
                return true;
            }
        return false;
        }

The stop() method is similar in construction. It checks to see if the engine is running, and if it is, it'll stop the car. Notice the check to see if the engine was running could have been made in the stop() function in the engine object, avoiding us even have to think about it. You'll have many questions to ask yourself about where logic should go. That's the basis of developing good, successful architectures:

        function stop()
        {
            if ($this->engine->isRunning()) {
                $this->engine->stop();
            }
        }

        // ... Several other methods such as moving and turning, and so on.
    }
    ?>

Now let us see how we could use this object in our programs.

Objects

An object in our program is an instance of a class. The reason why it's called an instance is because we can create multiple objects (or instances) of the same class just like there can be many cars on the road of the same class. To create two new cars, all we would need to do is to execute these lines of code in our program:

    <?php
    $car1 = new Car();
    $car2 = new Car();

We use the new keyword to construct new instances of a class, that is, create a new object. When we create an object or instance of a class, we say that the object has been instantiated. The reference to the newly instantiated object is then placed into the variables $car1 and $car2, respectively. Now we have two car objects which are available for us to use. If we wanted to create ten cars, we could use an array of objects like this:

    $cars = array();
    for ($i = 0; $i < 10; $i++) {
        $cars[$i] = new Car();
    }

If we want to start a car, we would call its start() method like this:

    $carHasStarted = $car1->start($myKey);

    if ($carHasStarted) echo("Car has started.");

And if we wanted to stop the car, we would do the following:

    $car1->stop();
    ?>

You'll notice that this object has an easy interface to use. You don't have to know how it's been developed. As a programmer, all you have to know is the services that are provided by an object. This program could very well be instructing a physical car to start and stop, but the complexity behind the methods and the details of its members are entirely unknown. This idea of creating easy-to-use objects is leading us to our next section, Encapsulation. For now, let us talk more about creating object instances using factory methods.

Factory Methods

Sometimes it's convenient to ask an object to create a new object for you rather than calling the new operator yourself. These classes are called factories and the methods that create the objects are called factory methods. The word factory stems from a production facility metaphor. For example, an engine factory owned by General Motors that produces vehicle engines is very similar to an object factory that produces objects of a specific type. Without getting into too much detail about complex object models at this time, let's see how we can use object factories in some areas of web application development. Here are a few examples:

  • You may want to create a FormControlFactory that produces various form elements (such as text fields, radio groups, submit buttons, and so on) to be placed on an HTML form such as the one that is implemented in the eXtremePHP library (a PHP open source library found at http://www.extremephp.org/)

  • You may want to create a factory for inserting new rows into a database table and return the appropriate data access object for that particular row

Now, let's see how to create a factory class and its corresponding methods by creating TextField and SubmitButton objects (from eXtremePHP) inside a FormControlFactory class.

Here we include two class files that we will assume have been previously made. The TextField.php file includes the code for the TextField class and the SubmitButton.php contains the code for the SubmitButton class. As you will see shortly, they require a name and a value to be passed in their constructors when new instances are created:

    <?php
    include_once("./TextField.php");
    include_once("./SubmitButton.php");

A good practice when developing factory classes is to append the word "Factory" to the end of the class name. The word ‘Factory’ has become a common convention in the object-oriented world and it will help other programmers identify what the class is doing simply from the common terminology:

    // FormControlFactory.php
    class FormControlFactory
    {

This is our first factory method, createTextField(). It simply creates a new instance of the TextField class by passing the $name and the $value supplied by the client:

        function createTextField($name, $value)
        {
            return new TextField($name, $value);
        }

The createSubmitButton() method is defined in a similar manner. It is also a common convention to append the word "create" to the beginning of the factory method to signify that it's returning a new object. This will establish common terminology throughout your application and will increase the understanding of your code and its level of traceability:

        function createSubmitButton($name, $value)
        {
            return new SubmitButton($name, $value);
        }
    }

Now, rather than instantiating TextField and SubmitButton objects using the new operator, we can use the FormControlFactory to do this for us:

    $formControlFactory = new FormControlFactory();
    $firstNameField =
        $formControlFactory->createTextField('firstname', 'Ken');
    $lastNameField =
        $formControlFactory->createTextField('lastname', 'Egervari');
    $submitButton =
        $formControlFactory->createSubmitButton('submit', 'Submit Name');
    ?>

Here, we create a new FormControlFactory instance and create three new classes using its factory methods. The first two calls to createTextField() create text fields that store a first and last name. The next call creates a submit button with the caption "Submit Name". At this point, our application can do anything it needs to with these new objects. The importance is not the meaning of the application, but the structure and understanding of what factory methods are and how to use them in your web applications.

Factory classes are not limited to creating methods alone. You can add other methods that make sense to the factory model such as find methods that look up objects in the factory and return them, and delete methods that can scrap objects inside the factory. These implementations are project specific and are up to you, the application designer. Now, let us turn our attention to the principles of encapsulation and information hiding.

Encapsulation

When you take your pain reliever for your headache, you probably don't know what it contains. All you care about is its ability to remove your headache. This is so very true when programmers use objects supplied to them. When we started using our Car object, we didn't know about the transmission, exhaust system, or the engine in the vehicle. All we wanted was to turn the key and start the car. The goal should remain the same when designing your objects.

Include all the complex data and logic within the object and provide users only with meaningful services that they would expect to interact with that object. Here you are, in effect, encapsulating the complex data and logic details inside the object. If done properly, we gain the benefit of information hiding, which we are going to illustrate next.

As we mentioned before, it's important for users of the class to be completely unaware of the data members within the class.

Important 

Although it is perfectly valid in PHP to modify the members of an instantiated object at any time, doing so is considered bad practice.

Here is an example that illustrates a few disastrous events that may happen if we modify the object's members without going through the object's interface. In this example we assume that there is a method to set the speed of a car, called setSpeed($speed), which will fail if you set it over 200km/h or when the speed is less than 0km/h. Let's also assume that our constructor does not initialize the engine and the required key to start the car:

    $myKey = new Key('Key of my Porsche');
    $car = new Car();
    $car->engine = new Engine();

    $car->speed = 400;
    $car->start($myKey);

    $car->engine = 0;
    $car->stop();

There are many errors in this code that will either fail to interpret, or even worse, will work but will fail to behave properly. In the first three lines, we fail to set the $requiredKey member of our $car object since this is not done by our constructor.

The key is not required until we actually start the car, so no errors will result from this. So everything checks out to be okay after the first few lines of code. As an aside, let's look at the line when we construct the Engine object. What if we had written $car->Engine = new Engine() instead (notice the capital "E" on the word Engine)? The car would fail to start because the engine would not be initialized either. Sure you can catch these bugs quite easily, but they should never happen in the first place. Next we try to start the car:

    $car->speed = 400; // should have been $car->setSpeed(400); to cause
                       // a failure
    $car->start($myKey);

When the car starts it will move forward and climb up to 400km/h speed. This could cause an accident and kill many people on (or off) the road. That's obviously not what we want to happen.

Also, how does the car know what kind of key it needs in order for it to start? It's going to compare our properly constructed key with a variable that doesn't even exist (resulting in a value of 0) and eventually will fail to start the engine. A comparison such as this will go right pass the interpreter checks since it's the input $key that checks for equality and not the member. It would be rather weird for a car owner to buy a new vehicle, only to find out that the key supplied by the dealer never worked. Let's look at what might happen if we go to stop the car when we set the engine to 0:

    $car->engine = 0;
    $car->stop();

When the stop() method is invoked, we'll run into a runtime error since the Engine object doesn't even exist because we forced it to the integer value of 0. As you can see, setting the members from outside the class could possibly result in a lot of problems. In a world where you have multiple programmers working on a project, you have to expect others to read and most likely use your code.

So what are the lessons learned from this example? Using members outside the object (object violations) can:

  • Break the assurance that services provided by the object will work as intended.

  • Break the integrity of the object's data members (or the object's state) in either of the following ways:

    • Violating the rules of the data

    • Avoiding initializing members

  • Create more complex interfaces than you really need.

  • Put more burden on programmers to remember more about the object and the way data interacts with its services.

  • When it comes time to reuse the object, you might have to modify the members again. Sometimes, out of forgetfulness, you'll create new errors on the next project. That's exactly what we want to avoid.

Important 

A good rule of thumb is to design your class such that it has services to accomplish everything you intend to do with the object. Never access members outside the class and always encapsulate your classes properly to reap the benefits of information hiding. Some languages offer the ability to disallow access to members altogether by making them private (or protected) to the class only. At present PHP does not have this feature but following good coding practices definitely helps.

Inheritance

Now that we've talked about the basic building blocks of an object-oriented program and some good heuristics, let's get into utilizing the features that object orientation offers us to help create clean strategies to solve complex problems.

Let's say we wanted to organize and manage an Internet media store like amazon.com. We may want to be able to sell compact discs, software, VHS, and DVDs, as well as books. When using traditional, functional programming, we might want to make a traditional structure to hold this media data, like this:

Top-Down Approach to Software Development

Obviously, there are many differences between books, movies, CDs, and software, so we might want to create some extra structures to store data for the specific types of media.

Click To expand

Now, if we wanted to write a program to print all the media items from an array to the screen, we would do this:

    <?php
    // create a small array filled with 2 records
    $mediaItems = array();
    $books      = array();
    $cds        = array();

    $item->id      = 1;
    $item->type    = "book";
    $item->name    = "Professional PHP 4";
    $item->inStock = 33;
    $item->price   = 49.95;
    $item->rating = 5;
    $mediaItems[] = $item;

    $book->isb n          = 1234556768456;
    $book->author        = "Ken Egervari, et. al. ";
    $book->numberOfPages = 500;
    $books[$item->id] = $book;

    $item->id      = 2;
    $item->type    = "cd";
    $item->name    = "This Way";
    $item->inStock = 120;
    $item->price   = 16.95;
    $item->rating  = 4;
    $mediaItems[] = $item;

    $cd->serialNo       = 323254354;
    $cd->artist         = "Jewel";
    $cd->numberOfTracks = 13;
    $cds[$item->id] = $cd;

    // Display the media items to the screen
    foreach ($mediaItems as $item) {
        echo("Name: " . $item->name . "<br>");
        echo("Items in stock: " . $item->inStock . "<br>");
        echo("Price: " . $item->price . "<br>");
        echo("Rating: " . $item->rating . "<br>");

        switch ($item->type) {
        case 'cd' :
            echo("Serial No: " . $cds[$item->id]->serialNo . "<br>");
            echo("Artist: " . $cds[$item->id]->artist . "<br>");
            echo("# of Tracks: " . $cds[$item->id]->numberOfTracks . "<br>");
            break;

        case 'software' :
            // echo software specific items
            break;

        case 'movie' :
            // echo movie specific items
            break;

        case 'book' :
            // echo book specific items
            break;
        }
    }

What if we were to add another media type? We'd have to go back into this code, add another case block to the switch statement, and probably update various others within our program (we will probably have many others like it). OOP offers a feature called inheritance where we can put the details of similar types of objects in their own respective locations and also consolidate the similarities in a base object. With this feature, we can avoid our switch statement altogether.

In our media store example, we could encapsulate all the similarities between media types in a media object. This object is called a base class, parent class, or super class. This forms the most abstract implementation (data and methods) that applies to any of the media items that we want to include later on. Here is a fictitious implementation of a Media class:

    <?php
    define("MIN_RATING", 0);
    define("MAX_RATING", 5);

    // Media.php
    class Media
    {
        var $id;
        var $name;
        var $inStock;
        var $price;
        var $rating;

        function Media($id, $name, $inStock, $price, $rating)
        {
            if ($inStock < 0) $inStock = 0;
            if ($price < 0) $price = 0;
            if ($rating < MIN_RATING) $rating = MIN_RATING;
            if ($rating > MAX_RATING) $rating = MAX_RATING;

            $this->id = $id;
            $this->name = $name;
            $this->inStock = $inStock;
            $this->price = $price;
            $this->rating = $rating;
        }

        function buy()
        {
            $this->inStock--;
        }

        function display()
        {
            echo("Name: " . $this->name . "<br>");
            echo("Items in stock: " . $this->inStock . "<br>");
            echo("Price: " . $this->price . "<br>");
            echo("Rating: " . $this->rating . "<br>");
        }

        // more methods
    }
    ?>

Now that we have a base class, we can use the extends keyword to inherit the properties of our Media class as well as add some specialized members and methods pertaining to a specialized media item, such as a book or a movie. A specialized class of a parent is either called a child class or a subclass. Here is the Book subclass of the Media class. The rest of the classes can be implemented in a similar manner.

Important 

It's good practice to subtype classes based on a member that adds a lot of code paths in your program like our type field in the Media class that caused us to write a complex switch block. By eliminating the type field altogether and creating classes based on the type of media, we can greatly reduce the complexity of the logic within our application.

    <?php
    // Book.php
    class Book extends Media
    {
        var $isb n;
        var $author;
        var $numberOfPages;

        function Book($id, $name, $inStock, $price, $rating,
                      $isb n, $author, $numberOfPages)
        {
            // It's important to call the parent constructor first, and
            // then set any members after it's been initialized

            $this->Media($id, $name, $inStock, $price, $rating);
            $this->isb n = $isb n;
            $this->author = $author;
            $this->numberOfPages = $numberOfPages;
        }

        function display()
        {
            Media::display();

            echo("ISB N: " . $this->isb n. "<br>");
            echo("Author: " . $this->author. "<br>");
            echo("Number of Pages: " . $this->numberOfPages. "<br>");
        }

        // methods
    }
    ?>

When our Book class inherits the Media class, it also contains all the members and methods of the Media class. To construct a new book object, we reuse the constructor of the parent class, Media(), and also set the new members at the same time. This is a very elegant design since it saves us from writing all the assignment code all over again, especially the rule logic that had to maintain the integrity of the data. For instance, $inStock shouldn't be below 0 and the $price should not be a negative value. By putting this into the Media class, we can rely on the foundation provided and build upon it as and when we need it, thus ensuring all subtypes will have the same integrity.

Let us turn our attention to the display() method in our Book class. Here, we provide a new implementation to display a Book object. In this new method, we output the members of the Media object (using the class-function call operation found in the next section) as well as the new members $isb n, $author, and $numberOfPages. This new display() method is said to override the display() method in the base class, Media.

Since our media subclasses share a common interface provided by the Media class, we can use the code in the same manner making them more maintainable and easier to use. Here is a code excerpt that displays a Book and a Cd object:

    $book = new Book(0, 'PHP Programming', 23, 59.99, 4,
    '124-4333-4443', 'Ken Egervari', 1024);
    $book->display();

    $cd = new Cd(1, 'Positive Edge', 1911, 16.99, 5,
    'Ken Egervari', 10, $trackNamesArray);
    $cd->display();

Notice that all of our media items behave in the same way after construction. Both display() methods takes no arguments and will display each different media item appropriately because they internally know what type of object they are. This makes it easy to provide common interfaces for different types of objects. How does this help in solving the problem with printing all the media items in our store? We'll see how to do this in the Polymorphism section, but for now, let us take a look at some interesting topics that deal with inheritance.

The Class-Function Call Operator

Now let us look at a new operator, the double-colon "::", which calls a function on a particular class without having to instantiate a new object. Thus the class will not have any members or constructors available within the function. The syntax for this operator is:

    ClassName::functionName();

This statement will simply execute the function called functionName() on the given ClassName. If the function does not exist within the class, the PHP interpreter will return an error.

If we go back to our Book class example in our display() method, we call:

    Media::display();

This calls the method display() inside our Media class. Since our specialized Book class (also Cd, Movie, and Software) extends the Media class, we are effectively reusing the display() method with the class-function call technique. This will display the base Media object to screen and the Book object will use echo statements for its members afterwards.

When the ClassName is not a parent class of the current object, it will call it statically. That is, it executes the function name on a particular class, but the member variables within ClassName will not exist. This is useful for grouping functions that are similar inside one class. For example, let's say we wanted to group PHP's math functions. We could create a Math class that could contain floor(), ceil(), min(), and max(). This is a trivial example but nonetheless, it illustrates the possibility of grouping common functions that you may want to create that should be grouped together. Instead of calling:

    $c = floor(1.56);

we could use a Math class like this:

    $c = Math::floor(1.56);

The advantage is that it helps you group similar functions together, making it much easier to modify your existing code since you can trace it easily. Your code will also become more "object-oriented" giving you all the benefits of using OOP.

The disadvantage is that your statements will become considerably larger since you have to add the extra class name before the function call.

Issues Concerning Code Reuse

Inheritance provides us with a good way to reuse code, but its main purpose is to specialize an object by adding behavior to the parent class. Code reuse through inheritance was not the intention of inheritance in OOP, although it is one of the benefits when we specialize our parent classes. The main intention was to allow you to use these subclasses in similar ways. Don't fret, however, as we will look at another way to reuse code later in the Delegation section.

Important 

A good practice is to not use inheritance for simply reusing code.

All inherited classes (subclasses such as Book, Cd, or Movie) will either have the same amount of data or functionality, or extra behavior in comparison to their parent class. In other words, inherited classes are often "fatter" in their functionality with respect to their parents.

Polymorphism

Polymorphism is an object-oriented feature that gives us the ability to treat an object in a generic manner. We can invoke a behavior on existing classes and any new classes defined in the future, leaving the details and differences to the interpreter at run time. Polymorphism makes it very easy to add new types to a system without breaking existing code.

Looking back to the media store example in the last section, when a user requests the list of all the new arrivals, it shouldn't matter if it is a book, movie, or compact disc. This is where polymorphism comes into the picture. It allows us to treat all these media objects in a similar manner. We don't have to test for the differences ourselves using if statements or switch constructs. We just leave this to the PHP interpreter.

Polymorphism is easy to understand once you know inheritance, since the only way polymorphism can exist is if we use inheritance. Inheritance allows us to create an abstract or parent class and then extend the functionality of that class to create subtypes or child classes. In our example, all the subtypes contain the different ways of displaying data. Our base class tells us that the display() method will display a media item, guaranteeing that all subclasses possess the same method. Let's take a look at the code:

    <?php
    $mediaItems = array(new Book(...), new Cd(...), new Book(...), new Cd(...));

    foreach ($mediaItems as $item) {
        $item->display();
        echo("<br><br>");
    }

This code will display every media item sequentially, regardless of it being a CD, a movie, a book, or a software application. Even though there are differences in all the media subtypes, we can treat them similarly because they all have a display() method. Here the PHP script engine will figure out what needs to be done and display accordingly.

This code gives a clean, elegant looking solution when printing all the media items in our store. It's much nicer than our bulky functional version we saw earlier. Let's say we had to create a new subclass, called ConsoleGame. We would add a new subtype from the Media class and add some new instances of the ConsoleGame class to the $mediaItems array, and our solution to display all the media items above doesn't have to change at all:

    $mediaItems[] = new ConsoleGame(...);

    foreach ($mediaItems as $item) {
        $item->display();
        echo("<br><br>");
    }
    ?>

This will print out the existing list and also the newly added ConsoleGame object. Notice how the foreach statement did not have to change. Polymorphism enables us to write such maintainable code. Inheritance is really no good on its own. We use inheritance to take advantage of polymorphism to give us clean, maintainable code. Inheritance is not only a method for reusing code as stated above, but is also used to achieve polymorphism in application code. When we get into design patterns, we'll see that a lot of problems can be simplified using inheritance and polymorphism.

Abstract Methods

When we use inheritance, often times the parent class will contain methods that have no code because it is impossible to specify common behavior. Therefore, we use a concept called an abstract method to indicate that a method contains no code and the implementer of any possible subtypes must implement the behavior for that method. If the method is not overridden (as discussed in the Inheritance section), it will continue to provide no behavior.

If you fail to define an abstract method and your subtypes do not override it, you will receive a PHP runtime error specifying that the method does not exist in the object. Therefore, it is important that you define all empty methods that you intend to have no behavior.

Important 

In object-oriented terminology, abstract methods usually force the implementer to override the method. However, in PHP this is not the case due to limitations in its object-oriented features. Implementers of subtypes should look at the documentation for the class they want to create subtypes for to see what abstract methods are needed to be overridden to have a properly implemented subtype.

Although there are no keywords to specify an abstract method in PHP, there is a common notation that programmers use to specify that a method is abstract. Usually it is a good idea to include some commenting to indicate that a method is abstract to help out implementers. To indicate that a method is abstract using code, we use an empty method as indicated by the bolded method below in an Employee class:

    <?php
    // Employee.php
    class Employee
    {
        var $firstName;
        var $lastName;

        function Employee($firstName, $lastName)
        {
            $this->firstName = $firstName;
            $this->lastName = $lastName;
        }

        function getFirstName() {
            return $this->firstName;
        }

        function getLastName() {
            return $this->lastName;
        }

         // Abstract method
        function getWeeklyEarnings() {}
    }
    ?>

In our Employee class above, we have defined an empty getWeeklyEarnings() method to say that it is abstract. This is because we cannot define how a specific employee receives pay. If there was a company that had managers, sales managers, engineers, and production workers, each would be paid differently. For instance, let's take a look at how a manager may be paid with a weekly salary:

    <?php
    require_once("Employee.php");

    // Manager.php
    class Manager extends Employee
    {
        var $salary;

        function Manager($firstName, $lastName, $salary)
        {
            Employee::Employee($firstName, $lastName);

            $this->setSalary($salary);
        }

        function setSalary($salary)
        {
            if ($salary < 0) $salary = 0;

            $this->salary = $salary;
        }
        function getWeeklyEarnings()
        {
            return $this->salary;
        }
    }
    ?>

As you can see, we override the abstract method getWeeklyEarnings() from the Employee class with behavior specific to managers. In this example, we simply return the salary member for this manager in the getWeeklyEarnings() method.

A salesman might get paid a weekly salary, but may also receive commission on how much revenue they are able to bring in for the company each week. Here is the implementation of the SalesManager class that satisfies these requirements:

    <?php
    require_once("Manager.php");

    define("DEFAULT_COMMISSION", .15);

    // SalesManager.php
    class SalesManager extends Manager
    {
        var $salary;
        var $commission; // values range from 0 to 1
        var $amountSold; // double

        function SalesManager($firstName, $lastName, $salary,
                              $commission, $amountSold)
        {
            Manager::Manager($firstName, $lastName, $salary);
            $this->setCommission($commission);
            $this->setAmountSold($amountSold);
        }

        function setCommission($commission)
        {
            if ($commission < 0 || $commission > 1)
                $commission = DEFAULT_COMMISSION;

            $this->commission = $commission;
        }

        function setAmountSold($amountSold)
        {
            if ($amountSold < 0) $amountSold = 0;

            $this->amountSold = $amountSold;
        }

        function getWeeklyEarnings()
        {
            return Manager::getWeeklyEarnings() +
                            ($this->commission * $this>amountSold);
        }
    }
    ?>

In our SalesManager class, we utilize the functionality in the Manager class and add two members, $commission and $amountSold. Commission is a percentage of how much the sales manager receives on the goods and services that he sells, and the amount sold is the value the sales manager has sold in a particular week. As you can see in our getWeeklyEarnings() method, the logic to calculate the earnings is different from that for a manager.

The same may be done with engineers and other employees that get paid hourly. Here is a possible getWeeklyEarnings() method for an hourly worker:

    function getWeeklyEarnings()
    {
        return $this->hoursWorked * $this->amountPerHour;
    }

As you can see from these examples, the Employee class could not specify logic for getWeeklyEarnings() in the base class, therefore it was left to its subclasses. This is the concept of having abstract classes and how they are used in PHP.

Cohesion and Coupling

Now that we've talked about keeping the data inside the object and utilized more than one class to solve problems, let's see what kind of data and methods are put into an object. This is where the terms cohesion and coupling come into play.

Cohesion is determining how closely the elements and functionality are related within the object. Are your methods and data closely associated, providing a lot of synergy to the object, or does it accomplish a hundred different things?

Some programmers, although doing a good job at developing software using objects, actually don't realize the potential of OOP. Constructing a module and wrapping it up in a class file does not make it truly object-oriented. The objects thus created are called God classes, since they pretty much accomplish the entire program in one class. Let's take a look at an example, where the method implementations are omitted, to illustrate a highly non-cohesive program that is simply wrapped up as a God class. In this example, we are going to create a form engine that will validate, output, style, and create JavaScript code for forms:

    class FormEngine {
        var $forms;
        var $formElements;
        var $formStyles;
        var $form;
        ...

        function createForm() {}
        function addFormElement($form, $name, $value, $properties) {}
        function validateForm($form) {}
        function validateFormElement($formElement) {}
        function getJavaScriptFormCode($formElements) {}
        function getJavaScriptFormElementCode($formElement) {}
        function displayForm($form) {}
        function displayFormElement($formElement) {}
        function setStyleToForm($form) {}
        ...
    }

From this code, all you can see is an object that contains some structures and some methods that act on everything to do with forms. Even in many of the functions, you'll be having switch statements with over ten items each, depending on the complexity of the form elements that you want to support, as well as some special combinations like file choosers, dates, and so on. You'll also have to provide switch statements for styling the forms as well. What makes this solution any different from making global variables instead of member variables and making real functions over the methods? Absolutely nothing.

As you may see from this example, non-cohesive objects create God classes and are a bad solution to the problem. The above example does not take advantage of OOP. When you write your classes, think about minimizing each object as if it were a function. As functions are better off doing one thing and doing it well, so should all of your objects. When you need to maintain the code, you'll know exactly where the underlying code lies. This helps immensely in fixing the problem easily instead of tracing it for several days through thousands of lines within a file. Take care when architecting your classes and separate the data and logic appropriately since it's worthwhile developing architectures that take advantage of highly cohesive objects. Now let's turn our discussion to coupling.

Coupling is the degree of relation that exists among two or more objects. When you have two or more objects that know about each other, you have strong coupling. This is very similar to establishing poor communication channels when trying to exchange information among several people. Here everybody has a reference to almost every other person:

Top-Down Approach to Software Development

In this diagram, there are five objects, where each object knows about at least one other object. The arrows tell us if there is one-way communication or two-way communication between objects. Object D contains code that knows about objects A, C, and E. Likewise, objects A, C, and E all contain code that knows about object D. Since object D is communicating in two-way with three other objects, you can easily see that it was designed very poorly. Let's say we could solve the problem by adding another class to separate the coupling like this:

Top-Down Approach to Software Development

By moving some functionality out of object D into X, we have reduced the coupling in our program. You could probably guess now that X is our main program, as it coordinates actions between the other classes. Most likely, object D was accomplishing two tasks instead of one; so there is a correlation between coupling and cohesion. Loose coupling is good design and tight coupling is deficient. When modules are loosely coupled, our chances of reusing them greatly increase because they become highly cohesive. Design your modules and applications carefully, so when it comes to developing a similar project, you'll spend much less time developing the same software components.

Important 

Writing highly cohesive, yet loosely coupled modules, should be your absolute goal when developing highly reusable and maintainable applications.


Table of Contents
Previous Next