Showing posts with label inheritance. Show all posts
Showing posts with label inheritance. Show all posts

Saturday, June 11, 2011

Various relations in software design - Association, Aggregation, Composition, Abstraction, Generalization, Realization, Dependency

We will just discuss about some of the terms used in software engineering, regarding relationship among the objects, and how they are represented in class diagram.

Association

Association is a relationship between two objects. In other words, association defines the multiplicity between objects. You may be aware of one-to-one, one-to-many, many-to-one, many-to-many all these words define an association between objects. Aggregation is a special form of association. Composition is a special form of aggregation.

Example: A Student and a Faculty are having an association. The relationship between a car and a driver is representative of this relationship. The car will have a driver who will be associated with the car for a period of time.

Aggregation

Aggregation is a special case of association. A directional association between objects. When an object ‘has-a’ another object, then you have got an aggregation between them. Direction between them specified which object contains the other object. Aggregation is also called a “Has-a” relationship.

 
A FileReader object that has been created using a File object represents a mutual dependency where the two objects combine to create a useful mechanism for reading characters from a file. The UML symbols for expressing this relationship as shown in following figure which involve a line connecting the two classes with a diamond at the object that represents the greater whole.


Composition

Composition is a special case of aggregation. In a more specific manner, a restricted aggregation is called composition. When an object contains the other object, if the contained object cannot exist without the existence of container object, then it is called composition.

Example: A class contains students. A student cannot exist without a class. There exists composition between class and students. Other example of this is a customer object and its related address object; the address object cannot exist without a customer object. This relationship is shown with a darkened diamond at the object, which represents the greater whole, as shown in the following figure:

Difference between aggregation and composition

Composition is more restrictive. When there is a composition between two objects, the composed object cannot exist without the other object. This restriction is not there in aggregation. Though one object can contain the other object, there is no condition that the composed object must exist. The existence of the composed object is entirely optional. In both aggregation and composition, direction is must. The direction specifies, which object contains the other object.
Example: A Library contains students and books. Relationship between library and student is aggregation. Relationship between library and book is composition. A student can exist without a library and therefore it is aggregation. A book cannot exist without a library and therefore its a composition. For easy understanding I am picking this example. Don’t go deeper into example and justify relationships!

Abstraction

Abstraction is specifying the framework and hiding the implementation level information. Concreteness will be built on top of the abstraction. It gives you a blueprint to follow to while implementing the details. Abstraction reduces the complexity by hiding low level details.
Example: A wire frame model of a car.

Generalization

Generalization uses a “is-a” relationship from a specialization to the generalization class. Common structure and behaviour are used from the specializtion to the generalized class. At a very broader level you can understand this as inheritance. Why I take the term inheritance is, you can relate this term very well. Generalization is also called a “Is-a” relationship.

Example: Consider there exists a class named Person. A student is a person. A faculty is a person. Therefore here the relationship between student and person, similarly faculty and person is generalization.

Realization

Realization is a relationship between the blueprint class and the object containing its respective implementation level details. This object is said to realize the blueprint class. In other words, you can understand this as the relationship between the interface and the implementing class.

Example: A particular model of a car ‘GTB Fiorano’ that implements the blueprint of a car realizes the abstraction.

Dependency

Change in structure or behaviour of a class affects the other related class, then there is a dependency between those two classes. It need not be the same vice-versa. When one class contains the other class it this happens.

Example: Relationship between shape and circle is dependency. The same relationship would exist for a stock transfer object that needed to use a stock item object to get the information on the stock item being transferred. The stock transfer object would have a dependency relationship with the stock item object; the stock transfer object would read the transfer information and could then discard the stock item object and continue processing.

Tuesday, May 24, 2011

The Square and Rectangle problem OR Circle and Eclipse Problem

Most people would think that a Square is a special kind of Rectangle and therefor intuitively inherit their Square class from the Rectangle class to be able to reuse code. Similar way goes for Circle and eclipse, where circle is considered eclipse's extension and special case.


Notice that the Rectangle have a couple of traits that the Square does NOT and vice versa. First of all the Square has only *ONE* attribute; size, while the Rectangle have *TWO* attributes; width & height. And this fact may for some problems be quite tricky to discover sometimes. And if you do the capital sin of inheriting your Square class from your Rectangle class, then you might currently get around it by hiding the width property of the Rectangle somehow, maybe by convention, and then just let the size property of the Square forward the call to height or something similar, but this is always wrong!

So consider the rectangle class:
public class Rectangle   
{
private double width;
private double height;
public void SetWidth (double w) { width = w; }
public void SetHeight (double h) { height = h; }
public void GetWidth () { return w; }
public void GetHeight () { return h; }
public void GetArea () { return width * height; }
}

Consider the square class:
public class Square  
{
private Rectangle r;
public void SetWidth (double x) { r.SetWidth (x); r.SetHeight (x); }
public void SetHeight (double x) { r.SetWidth (x); r.SetHeight (x); }
public void GetWidth () { return r.GetWidth; }
public void GetHeight() { return r. GetHeight; }
public void GetArea () { return r. GetArea; }
}

But Square has to somehow guarantee that its width and height are same or in other words height is redundant. So if client changes height, it has to change width accordingly.
Why is it that the client code gets affected by the derived class object reference being given in place of a base class object reference? Because the derived class's post-condition is weaker than that of the base class. The base class promises that if you call the SetHeight function, it will change the value of the height, but will do nothing to the value of width. But nothing like that happens in derived class and therefore, a client who is relying on the above base class gets affected when it gets a derived class object reference instead.

Therefore, inheritance is not the right approach in this situation. Delegation is the right choice. See here for solution to this problem.

Thursday, May 19, 2011

Open Close Principle ( OCP )

The Open-Closed Principle lies at the heart of the object-oriented paradigm. In a sense, we can say that its the goal of our object-oriented designs. Examining a myriad of patterns reveals its application often. It states the following:
Software entities should be open for extension, but closed for modification.

So it has 2 attributes:
  1. Open For Extension - It is possible to extend the behavior of the module as the requirements of the application change (i.e. change the behavior of the module).So classes can be extended
  2. Closed For Modification - Extending the behavior of the module does not result in the changing of the source code or binary code of the module itself.

Example - Violation of OCP
Consider the classes hierarchy:

class Animal
{
protected String sound;
}

class Cat extends Animal
{
//getters and setters
}

class Dog extends Animal
{
//getters and setters
}

Now consider the class, which manages the animals and make them sound.
SoundManager.java
public class SoundManager{
private List<Animal> animalList;

public SoundManager(){
animalList = new ArrayList<Animal>();
}

public void add(Animal a){
animalList.add(a);
}
public void makeSound(){
for(Animal a : animalList)
if(a instanceof Cat)
makeMew(a);
else if(a instanceof Dog)
makeBark(a);
}
public void makeMew(Animal a){
print(a.getSound());
}
public void makeBark(Animal a){
print(a.getSound());
}

}
Note that this is not a good solution because suppose a new class Lion comes into picture, and it has a sound called roar, you have to edit the code and another if else or switch() to make that work. So it is violation of OCP.

Solution
So a good solution would be to make makeSound function part of class hierarchy itself.

class Animal
{
protected String sound;
public abstract void makeSound();
}

class Cat extends Animal
{
//getters and setters
@Override
public void makeSound(){
print("mew");
}
}

class Dog extends Animal
{
//getters and setters
@Override
public void makeSound(){
print("bark");
}
}

Now it is much robust to write the above code, no matter how many animals you add.
public class SoundManager{
private List<Animal> animalList;

public SoundManager(){
animalList = new ArrayList<Animal>();
}

public void add(Animal a){
animalList.add(a);
}
public void makeSound(){
for(Animal a : animalList)
a.makeSound();
}
//makeBark, makeMew() functionr removed

}

Note of caution
It sems from the 80s and 90s when OO frameworks were built on the principle that everything must inherit from something else and that everything should be subclassable. So for example take a class called Stack in java, it subclasses Vector. So why on earth will you need push and pop if you can directly access element's index?
Joshua Bloch in his famous book "Effective Java" gives the following advice: "Design and document for inheritance, or else prohibit it", and encourages programmers to use the "final" modifier to prohibit subclassing.
So keep in mind, use inheritance only when its compulsory, otherwise try to make your classes final if you feel in future you won't be extending them.

Summary
It should be clear that no significant program can be 100% closed. So we may need to change the code depending on the requirement, but we can try to make our code as robust as possible. Though it comes with experience.