Extending Objects


So far, we have seen that object-oriented programming is a powerful way to encapsulate data into new types. We created a Product class, which has a representation of all the "inherent" properties we find in a product being sold in our fictional boating supply web applicationits name, description, PID, price, and so on.

Extending Existing Classes

However, imagine now that we wanted to have a sale on a few products and offer a certain percentage discount on them. It would be nice if we did not have to add a whole bundle of logic in our web application to manage this, but instead have the Product class be aware of possible discount situations. On the other hand, we do not want to burden the Product class with this extra code, given that most of our products are not on sale.

PHP lets us solve this problem by creating a new class that extends the Product class. This new class, which we might call DiscountedProduct, has all of the data and methods of its parent class but can extend or even change their behavior.

To declare a class that extends another, you use the extends keyword:

 <?php   class DiscountedProduct extends Product   {     protected $discount_percentage = 0.0;   } ?> 

In this example, our new DiscountedProduct class extends or inherits from what is typically referred to as the base class, or parent class (Product). So far, it has one new member variable, $discount_percentage, which only it and classes that in turn extend it can see. (Instances of the parent Product class cannot see the member variable.)

Visibility Revisited

As we mentioned in the earlier section "Visibility: Controlling Who Sees Things," member variables or methods on the base class that are declared public or protected are fully available to any inheriting classes, while properties or methods that are declared as private are not. The one exception to this is if a member function on our base class is protected or publicin that case, we can call it from our inheriting class. The function would then be able to access any private properties or methods:

 class ABC {   private $abc;   protected function not_private()   {     return $abc;   } } class DEF extends ABC {   protected function call_me();   {     //     // when we call this function (which is on our base class)     // it will have no problems accessing $abc.     //     $this->not_private();   } } 

Reimplementing Methods from Our Base Class

One of the things we would like our new DiscountedProduct class to have is an updated constructor that takes in the discount as a parameter. PHP permits us to do this and even to continue to use the base class's own constructor by using a keyword called parent. This keyword, combined with the scope resolution operator (::), lets us call methods belonging to our base class. The act of creating a "new" version of a member function in an inheriting class is often referred to as overriding a method. The overridden method in the base class is replaced by the new one in the extending class.

 <?php   public DiscountedClass extends Product   {     protected $discount_percentage = 0.0;     // etc.     // one new parameter for the discount.     public function __construct    (       $in_prodid,       $in_prodname,       $in_proddesc,       $in_price_pu,       $in_location,       $in_discount_pct    )    {       parent::__construct($in_prodid,                           $in_prodname,                           $in_proddesc,                           $in_price_pu,                           $in_location);       $this->discount_percentage = $in_discount_pct;    }   } ?> 

By doing this, we do not have to duplicate the work done by our parent's constructor. More importantly, we do not have to worry about the Product class's implementation changing and ours divergingwe have taken code reuse to a whole new level.

Note that PHP will now make sure we call the constructor for DiscountedProduct with six parameters, not just the five that the base class requires. We can similarly create a new version of the destructor:m>

 <?php   public DiscountedClass extends Product   {     // etc.     public function__destruct()     {       // it is always a good idea to call our parent's destructor       parent::__destruct();     }   } ?> 

To make our Discounted Product class work, we will want to override one more methodthe get_PricePerUnit method:

 <?php   public DiscountedClass extends Product   {     // etc.     public function get_PricePerUnit()     {       return parent::get_PricePerUnit()               * ((100.0 - $this->discount_percentage) / 100.0);     }     // etc.   } ?> 

Making Classes Act the Same: Polymorphism

So far, we have shown how to take our little code library for products and turn it into a class, and shown how to encapsulate the properties and operations on a product. However, there is one aspect of our product class that still seems unfortunate: In the implementation of many of the product class's member functions, it still has to perform a comparison check of where the product comes from and then execute separate code paths. Worse, if we want to add new product sources (for example, if we were to team up with a boat manufacturer to start selling boats), we would need to dig into the code and add more cases and code paths to handle the new locations.

Ideally, we would like some way to create a series of classes that have the same public interface (defined as the publicly accessible variables and methods on a class) but have separate implementations that know about the various locations from which products come.

Class Hierarchies

Forunately, we can use inheritance to accomplish these goals; PHP even provides a few extra pieces of functionality to make it easier to use. Instead of having one Product class that knows about a number of sources, we can have a number of classes inheriting from an abstraction of what a product is. Also, each class can handle a different implementation while acting like the class it is abstracting. We will now have three classes, as shown in Figure 4-3.

Figure 4-3. Our new class hierarchy permitting cleaner separation of code.


People will still be writing code as if they were using Product objects, but the actual underlying object will be one of the LocalProduct or NavigationParternProduct classes. The ability to work with a class as a particular type, even if the underlying classes are of varying subtypes, is often referred to as polymorphism. To declare our Product class as merely containing a template to which its inheriting classes must adhere, we will prefix its class keyword with the keyword abstract.

 <?php   abstract class Product   {     // body goes here.   } ?> 

By adding this new keyword, we have indicated that you cannot create an instance of the Product classyou must instantiate and create an inheriting subclass.

Our other requirement was that all of our products had to have the same public interface, or set of publically available methods and properties that it exposes. For our Product class, we want subclasses to provide new implementations for the get_number_in_stock and as the ship_product_units methods.

We could declare these methods on the Product class and leave them without a meaningful implementation in their bodies, but there is an even better solution available to us: We can declare functions as abstract and not even provide a body for them. Our Product class will now start to look as follows:

 <?php   abstract class Product   {     // etc.     //     // subclasses must implement these!     //     abstract public function get_number_in_stock($in_num_desired);     abstract public function ship_product_units($in_num_shipped);     // etc.   } ?> 

If you declare a class as abstract, then you cannot create new instances of it, and will only be allowed to use the new operator on subclasses of this class. If you declare any functions in your class as abstract, then the class must also be declared as abstract; otherwise, PHP will give you an error message:

 <?php   class TestClass   {     abstract public function iamabstract();   } ?> Fatal error: Class TestClass contains 1 abstract methods and must    therefore be declared abstract (ABC::iamabstract) in   /home/httpd/wwwphpwebapps/src/chapter04/abstract.php on line 8 

Our two new classes, LocalProduct and NavigationPartnerProduct, will not contain very much. They will both inherit directly from Product and will override and reimplement the two key methods mentioned earlier to do their work. You implement a method in your new subclass declared as abstract in your base class by declaring and implementing the method without the abstract keyword. Our three classes will look as shown in Listing 4-1.

Listing 4-1. Our Class Hierarchy for Products
 <?php   abstract class Product   {     protected $id;     protected $name;     protected $desc;     protected $price_per_unit;     public function __construct     (       $in_prodid,       $in_prodname,       $in_proddesc,       $in_price_pu     )     {       $this->id = $in_prodid;       $this->name = $in_prodname;       $this->desc = $in_proddesc;       $this->price_per_unit = $in_price_pu;     }     public function __destruct()     {     }     //     // subclasses must implement these!     //     abstract public function get_number_in_stock($in_num_desired);     abstract public function ship_product_units($in_num_shipped);     public function get_ProductID()     {       return $this->pid;     }     public function get_Name()     {       return $this->name;     }     public function get_Description()     {       return $this->desc;     }     public function get_PricePerUnit()     {       return $this->price_per_unit;     }   }   class LocalProduct extends Product   {     public function get_number_in_stock($in_num_desired)     {       // go to our local dbs and see how many we have left.         // return -1 on a failure of some sort.     }     public function ship_product_units($in_num_shipped)     {       // go to our local dbs and mark $in_number units as no       // longer available.  TRUE == success, FALSE == failure.      }   }   class NavigationPartnerProduct extends Product   {     public function get_number_in_stock($in_num_desired)     {       // go to our navigation equipment partner's servers       // and see how many are left.  return -1 on failure.     }     public function ship_product_units($in_num_shipped)     {       // go to our navigation equipment partner's servers        // and mark $in_number units as no longer available.       // Return FALSE on failure, TRUE on success.     }   } ?> 

Our Product class still contains some implementation we would like to share with subclassesit has a number of member variables that contain data about the product, and there is no reason to waste them (although subclasses should be allowed to override some if they wish), or the methods to get at them. However, you will have noticed that we no longer need the $location member variable anymore since the subclasses naturally know where to get the product and worry about all the possibilities.

Also, you will have noticed that we do not need to define a new constructor in either of those classes since the constructor for our base class is sufficient for our inheriting classes. PHP will make sure that the programmer passes in five parameters to the constructor when creating instances of these types.

The nice thing about our hierarchy is that once the objects are created, we do not care about their actual typethey all look and behave just like the Product class. When we call either of the get_number_in_stock or ship_product_units methods, we are assured that the correct one will be executed for the appropriate object.

Preventing Inheritance and Overriding

A need to prevent some methods or classes from being inherited might arise in our object hierarchy. For example, we might want a way to prevent people from overriding the get_ProductID method in our Product class.

In its long-running tradition of solving our problems, PHP also provides a solution for this. By prefixing a method with the final keyword, you can prevent inheriting classes from overriding it and implementing their own version. We can modify our Product class as follows:

 <?php   abstract class Product   {     // etc.     //     // nobody can override this method.     //     final public method get_ProductID()     {       return $this->id;     }     // etc.   } ?> 

You may be scratching your head and wondering how a class can be abstract but have final methods that nobody can override. The answer is that an abstract class can still contain some implementation, but somebody has to extend it and implement the unimplemented portion defined by the abstract methods. The implemented portion of the class can include methods that cannot be overridden, as indicated by the final keyword.

Inheriting classes can still access and call these methods; they just cannot include them in the set of things they extend, modify, or reimplement.

Classes can also be declared as final, which means that nobody is allowed to inherit from them. You might see this if you have a User object and an inheriting AdministratorUser object with special privilegesyou might choose to declare the AdministratorUser object as final to prevent others from inheriting it and any of its special privileges.

Exposing a Common Interface

We have now set up a convenient product hierarchy that represents our initial set of products, namely those we maintain locally, and those from the navigation equipment partner. However, if we wanted to add a new partner (such as a manufacturer of boats), and our developers were too busy to sit down and write a new class in our object hierarchy, it would be nice if we could let them write their own and tell them what methods we want them to have for products.

This is done in PHP by declaring an interface. Basically, this is a way of creating a set of methods that people must implement if they want to be viewed as a member of the class of objects with the same interface.

You declare an interface with the interface keyword. Classes indicate that they want to implement an interface by using the implements keyword:

 <?php   //    // prefixing the interface name with a capital I is   // strictly a naming convention that we use.   //   interface IProduct   {     public function get_ProductID();     public function get_Name();     public function get_Description();     public function get_PricePerUnit();     public function check_product_in_stock($in_num_desired);     public function ship_product_units($in_num_shipped);   }   abstract class Product implements IProduct   {     // etc.   } ?> 

By inheriting from IProduct, the Product class is committing either itself or one of its descendant classes to implementing all of the methods in IProduct. Thus, to force its two inheriting classes LocalProduct and NavigationPartnerProduct to implement the methods get_number_in_stock and ship_product_units, the Product class no longer needs to declare them as abstractit can simply omit them, which means that any subclass is obliged to implement them or receive an error. If we were to create a simple test class that inherits from Product, but doesn't implement them

 <?php   class TestProduct extends Product   {   } ?> 

we would see the following errors when we tried to load the script with this new class:

 Fatal error: Class TestProduct contains 2 abstract methods and   must therefore be declared abstract    (IProduct::check_product_in_stock,     IProduct::ship_product_units) in   /home/httpd/www/phpwebapps/src/chapter04/itfs.php on line 10 

The key in the previous error indicates that two methods from IProduct were not implemented.

This lets our boating manufacturer partner implement his own product-like class by implementing the IProduct interface, as follows:

 <?php   require('iproduct.inc');   class SuperBoatsProduct implements IProduct   {     protected $boatid;     protected $model_name;     protected $model_features;     protected $price;     //     // initialize a new instance of this class.     //     public function __construct     (       $in_boatid,       $in_modelname,       $in_modeldesc,     $in_price   )   {     $this->boatid = $in_boatid;     $this->model_name = $in_modelname;     $this->model_features = $in_modeldesc;     $this->price = $in_price;   }   public function __destruct()   {     // nothing to do   }   //   // this is final because we don't want people fiddling with it.   //   final public function get_ProductID()   {     return $this->boatid;   }   public function get_Name()   {     return $this->model_name;   }   public function get_Description()   {     return $this->model_features;   }   public function get_PricePerUnit()   {     return $this->price;   }   public function get_number_in_stock($in_num_desired)   {     // go to our local dbs and see how many we have     // left.  return -1 on a failure of some sort.   }     public function ship_product_units($in_num_shipped)     {       // go to our local dbs and mark $in_number units       // as no longer available.  TRUE == ok, FALSE == bad.     }   } ?> 

The boat manufacturer now has objects that appear like our own products and can easily integrate into our web application without our having to write any code for them (apart from designing our system well in the first place). We can even mix the products into collections of other products, and they would behave the same.




Core Web Application Development With PHP And MYSQL
Core Web Application Development with PHP and MySQL
ISBN: 0131867164
EAN: 2147483647
Year: 2005
Pages: 255

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net