Software Paradigms

Software Paradigms (Lesson 3)

Object-Oriented Paradigm (2)

Table of Contents

1 Reusing Classes 2

1.1 Composition 2

1.2 Inheritance 4

1.2.1 Extending Classes 5

1.2.2 Method Overriding 7

1.2.3 Initialization 8

1.3 Combining Composition and Inheritance 9

2 Abstract Classes 11

2.1 Creating Abstract Classes 11

2.2 Extending Abstract Classes 12

3 Interfaces 14

3.1 Creating Interfaces 14

3.2 Implementing an Interface 14

3.3 Multiple Inheritance 15

4 Polymorphism 18

4.1 Static Binding 18

4.2 Dynamic Binding 18

4.3 Interchangeable Objects & Upcasting 19

4.4 Extensibility 19

1  Reusing Classes

Once a class has been created and tested, it should (ideally) represent a useful unit of code. If it happens that such a class has a good design and is useful we might want to reuse this class. Code reuse is one of the greatest advantages that object-oriented programming languages provide.

There are two possibilities to reuse classes in object-oriented programs:

·  Reusing implementation

·  Reusing interface

If we want to reuse implementation we basically compose a new class from the already existing classes, thus creating a composite class. We say that the composite class reuses the implementation from its components.

On the other hand, if we want to reuse interface of a class we create a subclass of that class. Then we say that the subclass inherits functionality (methods) from the superclass. Thus, the subclass reuses the interface of the superclass.

1.1  Composition

The simplest way to reuse a class within a new class is to place an object of that class inside the new class. The new class can be made up of any number and type of other objects, in any combination that is required to achieve the functionality that is needed. Because we are composing a new class from existing classes, this concept is called composition (aggregation). Composition is often referred to as a “has-a” relationship, as in “a car has an engine.”

Let us look on the following example to see how composition works. Suppose we want to create the class definition for simple 2D shape objects. Each shape object has a color, a position where it is placed on the screen, a dimension (width and height), and so on. Now, suppose that we already have classes that represent color objects, point objects and dimension objects. For instance, the Color class might look as follows:

public class Point{

private int x_;

private int y_;

public Point(){

}

public Point(int x, int y){

x_ = x;

y_ = y;

}

public int getX(){

return x_;

}

public int getY(){

return y_;

}

public void setX(int x){

x_ = x;

}

public void setY(int y){

y_ = y;

}

}

Suppose that similar to that the Color class and the Dimension class have been defined and that they encapsulate the RGB values, and the values for width and height, respectively. Further, they provide a number of methods to manipulate the encapsulated data, as well as constructors for a parameterized initialization of objects.

Now we might compose the Shape class from the Color, Point and Dimension classes:

public class Shape{

private Color color_;

private Point position_;

private Dimension dim_;

public Shape(){

}

public Shape(Point position, Dimension dim){

this(position, dim, new Color(0, 0, 0));

}

public Shape(Point position, Dimension dim, Color color){

position_ = position;

dim_ = dim;

color_ = color

}

// here comes some code

// …

public Rectangle getBounds(){

return new Rectangle(position_.getX(),

position_.getY(),

dim_.getWidth(),

dim_.getHeight());

}

public void setBounds(Rectangle bounds){

position_.setX(bounds.getX());

position_.setY(bounds.getY());

dim_.setWidth(bounds.getWidth());

dim_.setHeight(bounds.getHeight());

}

}

In two methods, printed in bold in the above code, we see an example of providing the Shape class with a certain functionality (getting and setting the bounds) by reusing the functionality, which is already provided by the Point and Dimension classes.

Another interesting thing to notice in the above example is how we nested constructor calls. The second constructor, which takes a Point and a Dimension object calls the third constructor, which takes a Point, a Dimension and a Color object. This is achieved by using the self-reference of an object: this.

Summarizing, composition comes with a great deal of flexibility. The member objects of the new class are usually private, making them inaccessible to the client programmers who are using the class. This allows programmers to change those members without disturbing existing client code. They can also change the member objects at run-time, to dynamically change the behavior of the composed objects.

1.2  Inheritance

The second way to reuse a class code can be achieved through inheritance. Inheritance allows programmers to define a class as a subclass of an existing class. The subclass inherits in this way all instance variables and all methods from the basic class, as long as they declared as public or protected.

The level of inheritance can be arbitrarily deep. That means that we can create a subclass from a class that is already a subclass of another class. If we have more then one level of inheritance then a subclass inherits variables and methods from all of ancestors of its superclass.

The subclass can use the instance variables or methods as is, or it can hide the member variables or override the methods.

1.2.1  Extending Classes

The inheritance concept is called extending a class in Java. Thus, when we create a subclass of a class we say that we extend that class.

Let us look again on an example. We use again the same example from the previous section. Suppose we have the Shape class representing 2D shapes that we can draw on the screen. In the previous section we presented this class to demonstrate the composition principle. Here we give the complete code for the Shape class. Note, that the code now includes methods that reflect a typical behavior that we would expect from the Shape class. Thus, we have methods that we can call in order to draw a shape or erase a shape for example.

public class Shape{

protected Color color_;

protected Point position_;

protected Dimension dim_;

public Shape(){

}

public Shape(Point position, Dimension dim){

this(position, dim, new Color(0, 0, 0));

}

public Shape(Point position, Dimension dim, Color color){

position_ = position;

dim_ = dim;

color_ = color

}

public void draw(Graphics g){

// here comes implementation

}

public void erase(){

// here comes implementation

}

public Rectangle getBounds(){

return new Rectangle(position_.getX(),

position_.getY(),

dim_.getWidth(), dim_.getHeight());

}

public void setBounds(Rectangle bounds){

position_.setX(bounds.getX());

position_.setY(bounds.getY());

dim_.setWidth(bounds.getWidth());

dim_.setHeight(bounds.getHeight());

}

}

Suppose now that we want to represent different Shape objects, such as circles, rectangles, polygons, triangles, ellipses, etc. Obviously all these objects are shapes and share the same characteristics and behavior, such as they all have a color, a position, a dimension, or they can all be drawn, moved, erased, and so on. However, they all differ from each other in some way. For example, a triangle has three distinct points, whereas a polygon can have 6 points, etc.

Obviously, the Shape class that we introduced represents very well the things that all shapes have in common, but we need more specialized classes in order to represent different shapes. These subclasses would extend the Shape class and add required instance variables or implement different, more specialized behavior. Thus, the Circle class would draw a circle on a user screen and the Triangle class would draw a triangle.

Note also that we changed the access modifiers for instance variables of the Shape class from private to protected. We did so because we want subclasses of the Shape class to obtain access to its instance variables.

Let us now look on an example for the Circle class:

public class Circle extends Shape{

public void draw(Graphics g){

// here comes the Circle specific code

}

}

First, we notice the keyword extend, which declares a class to be a subclass of another class. Then we see that the Circle class just overrides (implements in another way) the draw method of the basic Shape class in order to implement a specific behavior for circle objects.

Let us look now on another example:

public class Polygon extends Shape{

private Point[] points_;

public Polygon(Point[] points){

points_ = points;

}

public void draw(Graphics g){

// here comes the Polygon specific code

}

}

Note that the Polygon class not only overrides the draw method, but also adds a new, specific instance variable to the Polygon class. It is an array of Points that represent points of that polygon.

1.2.2  Method Overriding

The ability of a subclass to override a method in its superclass allows a class to inherit from a superclass whose behavior is "close enough" and then override methods as needed. We saw this already on the examples of the Circle and the Polygon class that override the draw method of the basic Shape class.

Let us look more closely on these two overridden methods:

public class Polygon extends Shape{

public void draw(Graphics g){

g.setColor(color_);

for(int i = 0; i < (points_.length – 1); i++)

g.drawLine(points_[i].getX(), points[i].getY(),

points_[i+1].getX(), points_[i+1].getY());

}

// here is other code

}

public class Circle extends Shape{

public void draw(Graphics g){

g.setColor(color_);

g.drawOval(position_.getX(), position_.getY(),

dim_.getWidth(), dim_.getHeight());

}

}

Thus, we see that these two classes provide different implementations for the draw method and therefore a different behavior for the two classes.

Another important thing to notice is that the return type, method name, and number and type of the parameters for the overriding method must match those in the overridden method.

1.2.3  Initialization

Another important aspect of the inheritance mechanism is the initialization of objects of a subclass. Basically, an object of a subclass is an instance of both classes: subclass and superclass. Thus, a proper initialization of both “objects” has to be accomplished.

In Java, the default empty constructor of a class is automatically called when an object is created. If a class is a subclass of another class then the Java system automatically invokes first the constructor of the superclass.

In the case that programmer wants to define constructors that takes arguments, then he/she has to call the constructor of the superclass by him/herself.

Let us look on the following example to demonstrate this:

public class Polygon extends Shape{

private Point[] points_;

public Polygon(Point[] points, Point position,

Dimension dim){

super(position, dim);

points_ = points;

}

// here comes the rest

}

The code in bold calls the constructor of the superclass to initialize its instance variables properly. Note that this call has to be the first thing that a subclass constructor executes.

1.3  Combining Composition and Inheritance

Sometimes we want to create more complex classes that possible combine composition and inheritance mechanism.

The following example shows the creation of a more complex class, using both inheritance and composition. Suppose that we want to have a composite shape object, which groups together a number of other shapes. In that way we can treat all shapes object that are put into the grouped object as just one shape object.

Obviously, such object is a special shape object, i.e., it can be represented by a subclass of the Shape class and at the same time it is a composite object composed of say an array of other shape objects. The code for such Group class might look as follows:

public class Group extends Shape{

private Shape[] shapes_;

public Group(Shape[] shapes){

shapes_ = shapes;

}

public void draw(Graphics g){

for(int i = 0; i < shapes_.length; i++)

shapes_[i].draw(g);

}

}

The cold in bold from the example above represents inheritance and composition mechanisms.

2  Abstract Classes

Sometimes, a class that we define represents an abstract concept and, as such, should not be instantiated. Rather it should just serve as a base superclass for a number of more specialized classes. That means a basic abstract class defines just an interface that should be shared among its subclasses.

Let us revisit our Shape class example and try to think about it in terms of an abstract class. Basically, we used the Shape class just as a superclass for a number of different subclasses, such as the Circle, Polygon, Group class and so on. We didn’t use the Shape class to directly create instances of it.

If we think more about it we can conclude that it is not necessary to create instances of the Shape class, because an instance of a Shape class that is not also an instance of a special subclass is of no much use, because such Shape instance is not aware of how to draw itself, for example. It knows that it is a Shape but it doesn’t know which Shape it is.

Obviously, we could and should (to prevent creation of objects that don’t know how to draw themselves, for example) define the Shape class to be an abstract class. Technically, to define an abstract class means that we define a number of its methods to be abstract, i.e., we don’t provide the implementation for them but rather leave the subclasses to do so. Obviously, the draw method and for example the erase method of the Shape classes should be defined abstract, because special classes should implement this special behavior themselves.

2.1  Creating Abstract Classes

To create an abstract class we must declare it to be abstract and in addition to that we must declare a number of its methods to be abstract. Let us revisit the Shape class from above and define it to be an abstract class:

abstract public class Shape{

protected Color color_;

protected Point position_;

protected Dimension dim_;

public Shape(){

}

public Shape(Point position, Dimension dim){

this(position, dim, new Color(0, 0, 0));

}

public Shape(Point position, Dimension dim, Color color){

position_ = position;

dim_ = dim;

color_ = color

}

abstract public void draw(Graphics g);

abstract public void erase();

public Rectangle getBounds(){

return new Rectangle(position_.getX(),

position_.getY(),

dim_.getWidth(), dim_.getHeight());

}

public void setBounds(Rectangle bounds){

position_.setX(bounds.getX());

position_.setY(bounds.getY());

dim_.setWidth(bounds.getWidth());

dim_.setHeight(bounds.getHeight());