Science Telecommunications    
   
Table of contents
(Prev) IngotInkscape (Next)

Inheritance (object-oriented programming)

In object-oriented programming (OOP), inheritance is a way to establish Is-a relationship between objects. It is often confused as a way to reuse the existing code which is not a good practice because inheritance for implementation reuse leads to Tight Coupling (Coupling (computer programming)). Re-usability of code is achieved through composition (Composition over inheritance). In classical inheritance where objects are defined by classes, classes can inherit attributes and behavior from pre-existing classes called base classes, superclasses, or parent classes. The resulting classes are known as derived classes, subclasses, or child classes. The relationships of classes through inheritance gives rise to a hierarchy. In prototype-based programming, objects can be defined directly from other objects without the need to define any classes, in which case this feature is called differential inheritance.

The inheritance concept was invented in 1968 for Simula.

Contents

Subclasses and superclasses

A subclass, heir class, or child class is a modular, derivative class that inherits one or more language entities from one or more other classes (called superclasses, base classes, or parent classes). The semantics of class inheritance vary from language to language, but commonly the subclass automatically inherits the instance variables and member functions of its superclasses. Some languages support the inheritance of other construct as well. For example, in Eiffel, contracts which define the specification of a class are also inherited by heirs. The superclass establishes a common interface and foundational functionality, which specialized subclasses can inherit, modify, and supplement. The software inherited by a subclass is considered reused in the subclass. A reference to an instance of a class may actually be referring one of its subclasses. The actual class of the object being referenced is impossible to predict at compile-time. A uniform interface is used to invoke the member functions of objects of a number of different classes. Subclass may replace superclass functions with entirely new functions that must share the same method signature.

Uninheritable classes

In some languages a class may be declared as uninheritable by adding certain class modifiers to the class declaration. Examples include the "final" keyword in Java or the "sealed" keyword in C#. Such modifiers are added to the class declaration before the "class" keyword and the class identifier declaration. Such sealed classes restrict reusability, particularly when developers only have access to precompiled binaries and not source code.

The sealed class has no subclasses, so it can be easily deduced at compile time that references or pointers to objects of that class are actually referencing instances of that class and not instances of subclasses (they don't exist) or instances of superclasses (upcasting a reference type violates the type system). subtype polymorphism. Because the exact type of the object being referenced is known before execution, early binding (or "static dispatch") can be used instead of late binding (also called "dynamic dispatch" or "dynamic binding").. which requires one or more virtual method table lookups depending on whether multiple inheritance or only single inheritance are supported in the programming language that is being used.

Methods that cannot be overridden

Just as classes may be sealed/finalized method declarations may contain method modifiers that prevent the method from being overridden (i.e. replaced with a new function with the same name and type signature in a subclass). A private method is unoverridable simply because it is not accessible by classes other than the class it is a member function of. A "final" method in Java or a "sealed" method in C#) cannot be overridden.

Virtual methods

If the superclass method is a virtual method, then invocations of the superclass method will be dynamically dispatched. Some languages require methods to be specifically declared as virtual (e.g. C++) and in others all methods are virtual (e.g. Java). An invocation of a non-virtual method will always be statically dispatched (i.e. the address of the function call is determined at compile-time). Static dispatch is faster than dynamic dispatch and allows optimisations such as inline expansion.

Applications

Inheritance is used to co-relate two or more classes to each other.

Overriding

Many object-oriented programming languages permit a class or object to replace the implementation of an aspect—typically a behavior—that it has inherited. This process is usually called overriding. Overriding introduces a complication: which version of the behavior does an instance of the inherited class use—the one that is part of its own class, or the one from the parent (base) class? The answer varies between programming languages, and some languages provide the ability to indicate that a particular behavior is not to be overridden and should behave as defined by the base class. For instance, in C#, the overriding of a method should be specified by the program.[citation needed] An alternative to overriding is hiding the inherited code.

Code reuse

Implementation inheritance is the mechanism whereby a subclass re-uses code in a base class. By default the subclass retains all of the operations of the base class, but the subclass may override some or all operations, replacing the base-class implementation with its own.

In the following Python example, the subclass CubeSumComputer overrides the transform() method of the base class SquareSumComputer. The base class comprises operations to compute the sum of the squares between two integers. The subclass re-uses all of the functionality of the base class with the exception of the operation that transforms a number into its square, replacing it with an operation that transforms a number into its cube. The subclass therefore computes the sum of the cubes between two integers.

class SquareSumComputer: def __init__(self, a, b): self.a = a self.b = b def transform(self, x): return x * x def inputs(self): return range(self.a, self.b) def compute(self): return sum(self.transform(value) for value in self.inputs()) class CubeSumComputer(SquareSumComputer): def transform(self, x): return x * x * x

In most quarters, class inheritance for the sole purpose of code reuse has fallen out of favor.[citation needed] The primary concern is that implementation inheritance does not provide any assurance of polymorphic substitutability—an instance of the reusing class cannot necessarily be substituted for an instance of the inherited class. An alternative technique, delegation, requires more programming effort, but avoids the substitutability issue.[citation needed] In C++ private inheritance can be used as form of implementation inheritance without substitutability. Whereas public inheritance represents an "is-a" relationship and delegation represents a "has-a" relationship, private (and protected) inheritance can be thought of as an "is implemented in terms of" relationship.[1]

Inheritance vs subtyping

Subtyping enables a given type to be substituted for another type or abstraction. Subtyping is said to establish an is-a relationship between the subtype and some existing abstraction, either implicitly or explicitly, depending on language support. The relationship can be expressed explicitly via inheritance in languages that support inheritance as a subtyping mechanism. For example, the following C++ code establishes an explicit inheritance relationship between classes B and A, where B is both a subclass and a subtype of A, and can be used as an A wherever a B is specified (via a reference, a pointer or the object itself).

class A { public:   void DoSomethingALike() const {}}; class B : public A { public:   void DoSomethingBLike() const {}}; void UseAnA(A const& some_A){   some_A.DoSomethingALike();} void SomeFunc(){   B b;   UseAnA(b); // b can be substituted for an A.}

In programming languages that do not support inheritance as a subtyping mechanism, the relationship between a base class and a derived class is only a relationship between implementations (a mechanism for code reuse), as compared to a relationship between types. Inheritance, even in programming languages that support inheritance as a subtyping mechanism, does not necessarily entail behavioral subtyping. It is entirely possible to derive a class whose object will behave incorrectly when used in a context where the parent class is expected; see the Liskov substitution principle. [2] (Compare connotation/denotation.) In some OOP languages, the notions of code reuse and subtyping coincide because the only way to declare a subtype is to define a new class that inherits the implementation of another.

Limitations and alternatives

Using inheritance extensively in designing a program imposes certain constraints.

For example, consider a class Person that contains a person's name, address, phone number, age, gender, and race. We can define a subclass of Person called Student that contains the person's grade point average and classes taken, and another subclass of Person called Employee that contains the person's job-title, employer, and salary.

In defining this inheritance hierarchy we have already defined certain restrictions, not all of which are desirable.

Design constraints

  • Singleness: using single inheritance, a subclass can inherit from only one superclass. Continuing the example given above, Person can be either a Student or an Employee, but not both. Using multiple inheritance partially solves this problem, as one can then define a StudentEmployee class that inherits from both Student and Employee. However, in most implementations, it can still inherit from each superclass only once, and thus, does not support cases in which a student has two jobs or attends two institutions. The inheritance model available in Eiffel makes this possible through support for repeated inheritance.
  • Static: the inheritance hierarchy of an object is fixed at instantiation when the object's type is selected and does not change with time. For example, the inheritance graph does not allow a Student object to become a Employee object while retaining the state of its Person superclass. (This kind of behavior, however, can be achieved with the decorator pattern.) Some have criticized inheritance, contending that it locks developers into their original design standards.[3]
  • Visibility: whenever client code has access to an object, it generally has access to all the object's superclass data. Even if the superclass has not been declared public, the client can still cast the object to its superclass type. For example, there is no way to give a function a pointer to a Student's grade point average and transcript without also giving that function access to all of the personal data stored in the student's Person superclass. Many modern languages, including C++ and Java, provide a "protected" access modifier that allows subclasses to access the data, without allowing any code outside the chain of inheritance to access it. This largely mitigates this issue.[citation needed]

The composite reuse principle is an alternative to inheritance. This technique supports polymorphism and code reuse by separating behaviors from the primary class hierarchy and including specific behavior classes as required in any business domain class. This approach avoids the static nature of a class hierarchy by allowing behavior modifications at run time and allows a single class to implement behaviors buffet-style, instead of being restricted to the behaviors of its ancestor classes.

Roles and inheritance

Sometimes inheritance-based design is used instead of roles. A role, such as a Student role of a Person describes a characteristic associated with the object that exists because the object happens to participate in some relationship with another object; for example, the person in the student role has an "enrolled" relationship with a Course object. Some object-oriented design methods do not distinguish this use of roles from more stable aspects of objects. Thus there is a tendency to use inheritance to model roles; a Student role of a Person would be modeled as a subclass of a Person. However, neither the inheritance hierarchy nor the types of the objects can change with time.

Therefore, modeling roles as subclasses can cause roles to be fixed on creation; a Person cannot easily change his role from Student to Employee when circumstances change. From a modeling point of view, such restrictions are often not desirable, because this causes artificial restrictions on future extensibility of the object system, making future changes harder to implement because existing design needs to be updated. This critique motivated the introduction of Role-oriented programming where a new relation, played-by, is introduced combining several properties of inheritance with the desired dynamism.

In languages without explicit support for roles, inheritance is often better used with a generalization mindset, such that common aspects of instantiable classes are factored to superclasses, for example, having a common superclass 'LegalEntity' for both Person and Company classes for all the common aspects of both. The distinction between role based design and inheritance based design can be made based on the stability of the aspect. Role based design should be used when it is conceivable that the same object participates in different roles at different times, and inheritance based design should be used when the common aspects of multiple classes are factored as superclasses, and do not change with time.

One consequence of separation of roles and superclasses is that this cleanly separates compile-time and run-time aspects of the object system. Inheritance is then clearly a compile-time construct. It does influence the structure of many objects at run-time, but the different kinds of structure that can be used are already fixed at compile-time.

To model the example of Person as an employee with this method, the modeling ensures that a Person class can only contain operations or data that are common to every Person instance regardless of where they are used. This would prevent use of a Job member in a Person class, because every person does not have a job, or at least it is not known that the Person class is only used to model Person instances that have a job. Instead, object-oriented design would consider some subset of all person objects to be in an "employee" role. The job information would be associated only with objects that have the employee role. Object-oriented design would also model the "job" as a role, since a job can be restricted in time, and therefore is not a stable basis for modeling a class. The corresponding stable concept is either "WorkPlace" or just "Work" depending on which concept is meant. Thus, from an object-oriented design point of view, there would be a "Person" class and a "WorkPlace" class, which are related by a many-to-many associatation "works-in", such that an instance of a Person is in employee role, when he works-in a job, where a job is a role of his work place in the situation when the employee works in it.

Note that in this approach, all of the classes that are produced by this design process form part of the same domain, that is, they describe things clearly using just one terminology. This is often not true for other approaches.

The difference between roles and classes is unclear if one assumes referential transparency because roles are types of references and classes are types of the referred-to objects.

Issues

Complex inheritance, or inheritance used within an insufficiently mature design, may lead to the yo-yo problem.

See also

Further reading

  • Object-Oriented Software Construction, Second Edition, by Bertrand Meyer, Prentice Hall, 1997, ISBN 0-13-629155-4, Chapter 24: Using Inheritance Well.

References

  1. ^ "GotW #60: Exception-Safe Class Design, Part 2: Inheritance". Gotw.ca. http://www.gotw.ca/gotw/060.htm. Retrieved 2012-08-15.
  2. ^ Mitchell, John (2002). "10 "Concepts in object-oriented languages"". Concepts in programming language. Cambridge, UK: Cambridge University Press. p. 287. ISBN 0-521-78098-5.
  3. ^ Why extends is evil - JavaWorld

(Prev) IngotInkscape (Next)