CSC/ECE 517 Fall 2011/ch6 6b ra

From Expertiza_Wiki
Jump to navigation Jump to search

Subclassing


Introduction

Subclassing is one of the significant features of OO programming, which greatly increases the reusability of classes and also minimizes duplication of code. A subclass usually inherits some properties from a super class. Inheritance is a design principle in object oriented languages like Java. The reason behind inheritance is to reuse the code of existing objects or establish a subtype from an object. This greatly improves the efficiency and makes the code more readable as methods which are written only once can be used by a lot of subclasses. A superclass consists of common features that can be extended over subclasses. Superclass and subclass are commonly called base and derived class. The relationship between a subclass and superclass can be represented by a simple UML diagram as shown below.

Advantages of Subclassing

Method Overriding

This feature mainly depends on the object oriented language but it’s advantage can be exploited by using subclasses. It permits a class to replace the implementation of a method that has been inherited. This process is termed as overriding. Overriding requires the compiler to perform some sort of optimization so that the right method is invoked. C++ uses virtual pointer along with vtables to remember which function has overridden it’s definition inherited from the base class. In C#, overriding of a method should be specified by the program itself. The following example illustrates method overriding in C++.

#include <iostream.h>

class Base {
public :
virtual void show() {
	cout << "In Base";
}
};

class Derived:public Base {
public :
void show() {
	cout << "In Derived";
}
};

void main() {
	Base b;
	Derived d;
	Base *b2 = new Derived();
	b.show();
	d.show();
	b2->show();
}

Reusability

By creating subclass, the code of the base class can be reused in many situations. This gives subclass the freedom to create more specialized functions. It can use the base class methods to create these special functions. Apart from avoiding code duplication, this helps in decreasing the file size thereby saving some memory space.
For instance, we maybe programming animal characteristics but there is a special class of animals called mammals. Mammals display some specialized functions which can be defined in the subclass. As per Liskov Substitution principle which is discussed later, mammals obey the constraints enforced by the animal class and hence this kind of implementation avoids code duplication.

Liskov Substitution Principle

The Liskov Substitution principle provides the principle for substitution in object oriented programming. This principle was formally introduced by Barbara Liskov in a 1987 conference keynote address entitled Data abstraction and hierarchy. The principle states that "If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T".

This precisely means that functions that use references to base (Super) classes must be able to use objects of derived (Sub) classes without knowing it[9].

LSP is an extension of the Open-Closed principle[10]. As we can tell by the name, what this principle seems to mean is that classes should be open for extension but closed for modification. The Open Closed Principle (OCP) is one of the most fundamental class category principle. So much so that, most of class principles have been derived from OCP.

OCP allows the programmers to add new features to the system without resorting to modifications of preexisting classes. So, a new data structure can be added to the system without having to modify the existing system's legacy code base. By doing so, it provides a solid foundation for building code that could be maintainable as well as reusable. Achievement of OCP can be done by adhering to the basic principle of OCP i.e. reducing coupling between classes to the abstract level. This can be done by formulating relationships between a concrete class and an abstract class (For Java, an interface), instead of creating relationships between two concrete classes.

[8] To better understand how the OCP works, we need to know what mechanisms it is based on. These are: abstraction and polymorphism. With the use of inheritance we can create derived classes that conform to the abstract polymorphic interfaces defined by pure virtual functions in abstract base classes.

Looking back at the Liskov principle, it also states that 'Subclasses should be substitutable for their base classes'[10]. In order to take advantage of LSP, we must adhere to OCP because violations of LSP also are violations of OCP, but not vice versa. LSP and OCP almost seem to be the same albeit a subtle difference which is difficult to spot. OCP is centered around the concept of abstract coupling. LSP, while also heavily depending on abstract coupling, also draws heavily from the concept of preconditions and postconditions. This is LSP's relation to Design by Contract, where the concept of preconditions and postconditions were formalized.[10]

To further simplify the concept, we present the following definitions:

Precondition  : This is a condition that must be satisfied before a method can be invoked.

Postcondition  : This condition must be true upon method completion.

Thus, the agreement between the client that it will satisfy the precondition and the supplier will satisfy the post-condition is called as ‘Design by Contract’.


Implicilty, this means that if the precondition is not met, the method shouldn't be invoked, and if the postcondition is not met, the method shouldn't return. These conditions are sometimes difficult to be checked categorically as most of them are always based on some manual assertions or nonexecutable comments. Because of this, violations of LSP can be difficult to find. Next we would like to consider the design rules that govern this particular use of inheritance. Also, it would be worth considering the traps that cause hierarchies to be created that do not conform to the Open-Closed principle. This could be done to better understand the principle and the situations where it could prove to be very useful.

LSP Violation

Following is another definition of the LSP, with whose reference we explain the example given below [7] “Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.”

Suppose we have an Interface defined as follows

public interface IMessageBox
{
	void showOption();
	void saveChange();
}

This interface represents a message sending entity that displays and can also make changes to the messages setting options. Following are the mock implementations of the interface.

public class PhoneSettings : IMessageBox
{
	public void showOption()
{	//show some Message setting options for the phone
	}
public void saveChange()
{	// save the changes  to the Message settings options for the phone
	}
}

public class UserSettings : IMessageBox
{
	public void showOption()
{	//show some Message settings for the phone
	}
public void saveChange()
{	// save  the changes to the Message settings for the phone
	}
}

Suppose in our example we have a method to retrieve the list of all instances of the implementation of the interface.

static  IEnumerable<IMessageBox> ShowAll()
{
	var allOptions = new List<IMessageBox>
				{
					new PhoneSettings();
					new  UserSettings();
				} ;
	allOptions.ForEach(r => r.Show());
	return allOptions;
}

Suppose at some other place in the code, we have a method which uses the list all all these objects created above and call the change method on them:

static void SaveAll(IEnumerable <IMessageBox> messages)
{
	options
		.ForEach(r => r.saveChange());
}

And let these methods be used at another place:

IEnumerable<IMessageBox> messages = ShowAll();
Console.WriteLine(“ Setting options:”);
//makes changes to the message settings
SaveAll(messages);
Console.WriteLine(“Message setting changes have been saved “ );

Until now, everything was working fine. But, let’s twist the situation a bit. We now add a new class to the system called as NewSettings. This new class is also Showed in other class that implement the same interface:

public class NewSettings : IMessageBox
{
	public void showOption()
{	//show some Message setting options for the phone
	}
public void saveChange()
{	throw new NotImplementedException();
	}
}
static  IEnumerable<IMessageBox> ShowAll()
{
	var allOptions = new List<IMessageBox>
				{
					new PhoneSettings();
					new  UserSettings();
					new NewSettings();
				} ;
	allOptions.ForEach(r => r.Show());
	return allOptions;
}

After we run this code, the subtlety in the violation of the LSP is caught at this point. i.e. when we expect the code to run fine, we get an exception “NotImplementedException”. The following method can be used to fix it but it's not ethical.

static void SaveAll(IEnumerable <IMessageBox> messages)
{
	options
		.ForEach(r => 
if(r is NewSettings)
return;
r.saveChange());
}

Let’s review the Liskov Substitution Principle here. Put in simple words, it comes out to be: “An object should be substitutable by its base class (or interface).”

By relating this with our example, we realize that the SaveAll method clearly shows that “NewSettings” is NOT substitutable by its “IMessageBox” interface. Becaue if we call saveChanges() on it it, the system gives an exception if we call Persist on it. So we might want to change the method to take that one problem into consideration. The first solution one might think of would be to change the saveChanges() method on the NewSettings class so that it won’t throw an exception. This would be a bad idea, because now the method wouldn’t do what its name suggests i.e. save the changes essentially because the options are read-only and no changes can be made to it. The fix here is to adjust the interface to suit the clients’ needs. This is stated by the Interface Segregation Principle or ISP . This is another principle that could be used in place of LSP. So, the ShowAll method is only concerned with showing all the options, whereas the SaveAll method is concerned with the saving of the changes capabitlity. So why not have separate interfaces for these two?

public interface IShow
{
	void showOption();
}
public interface ISave
{
	void saveChange();
}

By doing this we achieve the same functionality that we had before. Just that now we have two separate interfacse which try to suit the clients’ needs. Both the UserSettings and PhoneSettings classes can implement these two interfaces, whereas the NewSettings class will only implement IShow and not ISave as well. By doing this, every class implements only those interfaces it can handle. To sum up what went wrong in our prior implementation : a single interface was trying to implement much more than what it could handle. For the kinds of methods that were to be implemented for a single interface, suiting the clients’ needs would be difficult with the kind of constraints that it had. In many such cases, one of the other principles for subclassing comes into the picture, which proves to be more helpful than the LSP. This principle is called as Single Responsibility Principle SRP . This principle suggests subclassing such that we have each interface with a single responsibility to take care of. Many such other principles (from the class of SOLID priciples) have been introduced in the section further in this article.

Other Principles of Object Oriented Design

A suite of eleven principles was conceived by people such as RobertCecilMartin, BertrandMeyer, BarbaraLiskov and 5 of these are class design principles. The 5 class design principles called SOLID. Since OCP and LSP have been covered before, this section gives a brief introduction about the other 3 class design principles.

Single Responsibility Principle (SRP)

The principle states that
1. Each responsibility carried out should be made into a class because each responsibility represents an axis of change.
2. A class should have one and only reason to change.
3. If a change to the business rules causes a class to change, then a change to the database schema, GUI, report format, or any other segment of the system should not force that class to change. [11]

Interface Segregation Principle (ISP)

ISP is a software development principle that can used for legacy code development that makes the software impossible to change. It is used for clean development. This will help a system stay decoupled and makes refactoring the code easier. The ISP says that once an interface has become too 'fat' it needs to be split into smaller and more specific interfaces so that any clients of the interface will only know about the methods that pertain to them. In a nutshell, no client should be forced to depend on methods it does not use.[11]

Dependency Inversion Principle (DIP)

DIP can be summarized as
1. High level modules should not depend upon low level modules. Both should depend upon abstractions.
2. Abstractions should not depend upon details. Details should depend upon abstractions. [11]

DIP wishes to avoid designs which are hard to change because there is heavy dependency among the modules. Also, the code should not be immobile i.e, the code should be easy to reuse.

References

[1] Object Oriented programming
[2] Inheritance
[3] Vtables
[4] Open Closed Principle (OCP)
[5] Abstraction
[6] Polymorphism
[7] LSP Example
[8] Robert C Martin - Liskov Substitution Principle
[9] Object Oriented Design Principles
[10] Application Design Concepts and Principles
[11] SOLID