Inheritance and
abstraction are also very important features of object-oriented
languages. They provide a way to make polymorphic representations of
objects and object relationships that can be managed at run time or
compile time.
Inheritance is the ability of one object to be derived
by creating a new class instance from a parent or base class and
overloading the constructor(s), methods, and attributes of that
parent object and implementing them in the instance. In Java this is
known as subclassing. Inheritance is important because many times
an object contains some base functionality that another object also
needs and, instead of maintaining the same logic in two objects, they
can share and even override or change this functionality by using a
base or parent class. If this occurs, then the base or parent object
should be defined in such a way that several common derived objects
can use the same common functionality from the parent. The parent
should only contain functionality common to all its children.
Abstraction is the
actual method in which we use inheritance. Abstraction is the ability
to abstract into a base class some common functionality or design
that is common to several implementation or instanced classes. The
difference between implementation and abstract classes is that
abstractions of classes cannot be instanced, while implementations
can. Abstraction and inheritance are both aspects of polymorphism,
and the reverse is true as well.
Another important aspect
of object-oriented languages is how they deal with collections of
objects. The equals implementation for objects is an important aspect
of dealing with objects inside a collection. Languages like C#,
VB.NET, and Java all use this method to help index and compare
objects in collections. Let’s talk about this briefly. When a hash
table or other collection object indexes and compares an object, it
uses the GetHashCode() method to help in this indexing and
comparison. This method can be overridden to capture a more accurate
sampling of the intrinsic properties or state of the object. In other
words, the GetHashCode() method can return an integer representation
of the concatenated state of the properties within an object. If not
overridden, then this relationship is less exact. This is important
when making comparisons between objects in collection classes like
iterators or generic collection objects like hash tables. You need to
make accurate representations of the internal state of objects so the
correct object can be compared or indexed in a collection.
There are general
rules to guarantee that each object gets a
unique hashing algorithm:
Objects that compare as equal must return the same hashed value.
GetHashCode() must return the same value every time, unless the
internal value or state is modified. The hashed value is not like
a GUID (global unique identifier) in that it is not globally unique,
but only unique if the hashed algorithm and the object’s value are
not the same as any other object in the scope of the executing code.
The default
implementation of GetHashCode() in objects that contain state
variables or values is not guaranteed to be unique. That is why if
uniqueness is desired, then a proper algorithm needs to be
implemented in the overridden method on a particular class. To
provide a complete representation of state, the values of each
variable that represents the object’s state need to be part of
the hashing algorithm. To illustrate the proper way to implement the
GetHashCode() method, take a look at this example:
public override int
GetHashCode()
{
return
_name.GetHashCode() ^ _address.GetHashCode();
}
We see that the method has
been overridden, and two instance variables have been concatenated
with the ^ symbol and returned as their sum. Another way to do this
is with the + sign, which returns the same result:
public override int
GetHashCode()
{
return
_name.GetHashCode() + _address.GetHashCode();
}
Taking all the variables
that may change the state of the object and returning their
concatenated hashed values guarantees that each object will have
unique values based on state. This allows objects used as keys in
collections like hash tables to act in the proper manner. The
Equals(object obj) method is the required method for bitwise
comparisons of value objects. It is especially useful in sorting
collections or when a comparison operation is desired in a
collection. Not all primitive or object types can have bitwise
equality, and so those are compared by value, as in the case of
decimal 2.2000 and 2.2, which have the same value but different
binary equality. The proper operational sequence usually starts with
a null check, then a class type comparison, and then a comparison of
all the value types (or reference types) that influence the state of
the class:
public override bool
Equals(object obj)
{
if(obj != null &&
obj is Component)
return
_name.Equals(((Component)obj).Name) &&
_address.Equals(((Component)obj).Address);
else
return false;
}
There are some basic rules
when testing the equals implementation for proper return values:
obj1.Equals(obj1) = true
— an object always
equals itself.
obj1.Equals(obj2) =
obj2.Equals(obj1) —
equals implementations
across different class instances
always return true on
both classes if equal.
obj1.Equals(obj2) &&
obj2.Equals(obj3) && obj3.Equals(obj1) = true — if object 1
is
equal to object 2 and
object 2 is equal to object 3, then
object 3 must be equal to
object 1.
All calls to Equals()
return the same value unless the class’s state or internal value is
modified.
Equals(null) always
returns false.