Educational outcomes
Student:
- explains how to implement constructs such as: disjoint inheritance, abstract classes, overriding methods, abstract methods, overlapping, incomplete inheritance, multiinheritance in natural language (understanding);
- identifies and corrects errors in the source code, which is an implementation of the above structure;
- lists constructs implemented in the source code (understanding);
- implements the above structures using the selected programming language (application);
- indicates functional differences between two selected programs based on code snippets source (analysis);
- creates a program that implements specific business requirements using the constructs learned from the lectures (synthesis);
Justification
Modern programming languages are mostly based on the object paradigm. Therefore, its practical understanding is a necessary basis for working in this profession. Moreover, programmers often make systems based on analytical materials. And these largely use object-oriented graphical notations (such as UML). During this and the next lessons, you will deepen your theoretical bond, but first of all you will learn how to implement different concepts from object-orientness. This will allow you to design and implement modern business applications.
Disjoint inheritance
Inheritance is one of the most important concepts in object-orientness and implies a certain relationship between classes. This dependency allows you to create a new class (we say "subclass") based on "old" (superclass). The subclass has all the features of its superclass plus, possibly, its own. A subclass is a special case of a superclass, that is, you can inherit the class Employee from the Person class (because the employee is also a person unless we describe the robot factory). Thus, wrong examples: room from home (because room is not a special case of home) or a student from a university.
The simplest type of inheritance is called disjoint. The class instance (object) belongs to one of the classes. Let's look at the figure below:

Let's start by checking whether we were allowed to use inheritance at all: are all subclasses special cases of the superclass? Of course, yes: an employee is a person, just like a student and a retired person.
But is such a construction needed at all? After all, all these dedicated attributes can be placed in the superclass and it would also "work". Of course, it would, but:
- Depending on what kind of person we would describe, attributes that are specific to others would be "unused" (occuping memory),
- The diagram would be less descriptive of our business because it could not be stated that there is a pensioner, a student and an employee,
- We could not use the polymorphism of methods and overrides (see below).
Another doubt may be related to the fact that the Retirement class seems empty. It is not really "empty" - it contains important information that this person is a retired one. It is worth pointing out that in general (this is not quite true - we will go back to that), we inherit all elements in the class:
- attributes,
- methods,
- Associations - it is worth remembering especially because those who avoid the "empty" subclass (see above) sometimes forget it.
This kind of inheritance occurs directly in popular programming languages (eg Java) and can be implemented eg:
// For simplicity all attributes are public (no getters/setters)
Public class Person {
public String firstName;
public String lastName;
public LocalDate birthDate;
}
public class Employee extends Person {
public boolean medicalTest;
}
public class Student extends Person {
public int number;
}
public class Pensioner extends Person {
}
Inheritance and class extent
When discussing class extent, we mentioned that in conjunction with inheritance it raises some problems. Well, as we recall, objects in the subclass are indirectly also instances of the superclass (an Employee is also a Person). And that is exactly the problem. Since we are extending the Person class that contains all its objects, and the Employee class inherits it, should it be in the Person extent? Should Person objects be in the Employee extent (it seems to be the least appropriate)? The opinions of the scientists are divided. The most popular view is that Employee objects should be in the Person extent, but "cut" to a set of person properties (without additional attributes, methods, and associations). In practice, we can decide what are our business needs and choose the right solution.
Classes and abstract methods
Abstract class is a class that can not have direct instances (there can not be objects belonging to this class).
By reading the definition above you might wonder why we have a class that can not have objects? The reason is that it will be utilized to create a hierarchy of inheritance. The system described in the above example will not have a Person as such, but their specializations: Student, Employeeetc. The abstract class Personis our assurance that nobody (intentionally or wrongly) will create such objects. We do this by writing the name in italics (which is not very readable) or by using the {abstract} label value.

Abstract method is a method that only has a declaration but has no definition (body). It can only occur in an abstract class.
Abstract class can contain concrete methods (just like any other class), but also abstract ones. As in the case of the abstract class, you might wonder why we have a method that does not have a body? We will provide them in subclasses, in different forms, depending on the specific business requirements. For example, for the above case, we may want to ask our people for their income. The problem is that the person, as such, has no income - they have specializations such as Student has a scholarship, Employee salary, etc. As a result, our diagram could look like this:

public abstract class Person {
// [...]
private String firstName;
private String lastName;
private LocalDate birthDate;
public Person(String firstName String lastName LocalDate birthDate) {
this.firstName = firstName;
this.lastName = lastName;
this.birthDate = birthDate;
}
public abstract float getIncome();
}
public class Employee extends Person {
// [...]
private float salary;
public float getSalary() {
return salary;
}
public void setSalary(float salary) {
this.salary = salary;
}
@Override
public float getIncome() {
return getSalary();
}
}
Methods overriding
An important issue that is connected with the inheritance is the overriding and polymorphism of methods (there are several other types of polymorphism, but we will not deal with them). It allows for multiple versions of the same name (and the number and
type of parameters) to be placed in each class of the same inheritance hierarchy (similar to our getIncome()) method. Mechanism of the polymorphic call of the method, allows you to run the proper of them.
Let's look at the code:
public abstract class Person {
// [...]
private String firstName;
private String lastName;
private LocalDate birthDate;
public Person(String firstName String lastName LocalDate birthDate) {
this.firstName = firstName;
this.lastName = lastName;
this.birthDate = birthDate;
}
public abstract float getIncome();
}
public class Employee extends Person {
// [...]
private float salary;
public float getSalary() {
return salary;
}
public void setSalary(float salary) {
this.salary = salary;
}
@Override
public float getIncome() {
return getSalary();
}
}
public class Student extends Person {
private int number;
private float scholarship;
// [...]
@Override
public float getIncome() {
return getScholarship();
}
public float getScholarship() {
return scholarship;
}
public void setScholarship(float scholarship) {
this.scholarship = scholarship;
}
}
The easiest way to understand this mechanism is by looking at how it is used:
Person p1 = new Employee("John" "Smith" LocalDate.of(1990 12 20), true 4000.0f);
Person p2 = new Student("Adam" "Black" LocalDate.of(1995 10 18), 1212 2000.0f);
System.out.println(p1.getIncome());
System.out.println(p2.getIncome());
- (1). We create
employee. Notice that we use a reference of typePersonwhich is simply a person. - (2). We create a
student. Like the above, we use a reference of typePerson. - (4). We show the result of the call for a person's income method. But what happened? In person as such, there is only an abstract method that has no body. Now there is a polymorphic call to the method: before the virtual machine actually invokes the method, it checks what object it is dealing with (and not just what we show). If it does not find a method with the given signature in the same class, it is looking for superclasses ("going up"). This allows us to remain at a relatively high level of abstraction - we do not see who we are dealing with (student, employee or retired) - we simply ask a person for income.
- (5). The situation is similar to that in (4), but it concerns a student.
It is worth emphasizing that this mechanism is used sparsely in object languages, eg in a very popular method toString().
Overlapping inheritance - flattening the hierarchy
Let's look at the diagram below. It looks almost the same as the previous one. The only difference is overlappingmeaning that we can expect objects of several classes at once in the system: in this case the object may belong to the Employee class and/or Student and/or Pensioner. The object can be any combination of them, which means that they will have all their features. It is worth noting that this can lead to similar problems as in multiinheritance (see below).

Unfortunately, this type of inheritance does not occur directly in any of the popular programming languages. Therefore, we need to implement it manually. Actually, we have the following ways:
- Replace the entire hierarchy of inheritance with one class (flattening),
- Use of aggregation or composition,
The first way is very simple, both conceptually and implementably. It depends on the specific grouping of all classes from the overlapping hierarchy within a single class. It usually has a superclass name. In other words:
- All invariants (attributes, methods, associations) are placed in one superclass,
- We add a discriminator (special attribute) that informs us about the object type. In our particular case we want to be able to remember several values at once. Therefore, we will use a special collection offered by Java called
EnumSet.
enum), which is not only safer, but also more comfortable to use.
Relevant source code:
enum PersonType {Person Employee Student Pensioner};
public class Person {
private String firstName;
private String lastName;
private LocalDate birthDate;
private boolean medicalTest;
private int number;
// We need to use EnumSet rather then PersonType because we would like
// to have a possibility of storing combinations of the Person, e.g. Employee + Student
private EnumSet<PersonType> personKind = EnumSet.of(PersonType.Person);
}
- (1). We have introduced an enumeration type that allows us to remember what kind of person we are dealing with.
- (3). In one class we grouped the attributes of person (4), employee (8) and student (10).
- (14). A special kind of collection acting as a discriminator. We include the appropriate enumeration types, such as
PersonKind.EmployeePersonKind.Student. This configuration means that we are dealing with a working student.
How can we summarize such a solution? It certainly simpliy to implement and ease of use. At the same time, it also has some disadvantages. One of them is the inability to use inheritance constructs, such as method override, polymorphic methods calls, etc. Another one is not to use invariants belonging to another specialization - even though they occupy space. For example: if the object of our new class Person would describe a working student, then the other attributes that hold other attributes will not be used. And finally a considerable confusion of the class diagram. All attributes and methods will be in one class (it may become quite large), and associations that have so far come to the subclasses will be connected to that one.
Overlapping inheritance - Composition
Another way to apply overlapping inheritance in popular programming languages is to use aggregation or composition. We usually choose composition, but in some specific business cases, aggregation may be better. The following figure shows the transformation of our previous example into a form of inheritance overlapping.

As you can see, the matter is quite simple. Instead of inheritance we insert a composition where the superclass becomes a whole and subclasses are parts. As a result we got rid of inheritance fairly radical, because it no longer exists in any form. You also need to decide how we will treat the associations originally existing in the subclasses. The problem is that with "true" inheritance pointing to an instance of an Employee class gives us direct access to invariants (attributes, methods, associations) from the superclass. At the moment when the inheritance is replaced by the composition, such possibilities disappear and they have to be "simulated" somehow. We have two ways to do this after conversion. Subclass associates can show on:
- Whole. In this situation, we can add appropriate methods to make it easier for us to access the invariants in the subclasses. These will be methods that propagate the execution from class-whole to class-part. Thanks to this, the programmer who will use it will not have to navigate through the whole class.
- Part. In this case, object-parts can not be hidden. There must be direct access to them (not by object-whole), which involves a specific way of implementing the composition.
Of course, the composition can be implemented in one of the previously discussed ways. The following listing contains an example implementation of the overlapping inheritance with composition and ObjectPlusPlus class:
public class Person extends ObjectPlusPlus {
private String firstName;
private String lastName;
private LocalDate birthDate;
public Person(String firstName String lastName LocalDate birthDate) {
super(); // Required by the ObjectPlusPlus
this.firstName = firstName;
this.lastName = lastName;
this.birthDate = birthDate;
}
public Person(String firstName String lastName LocalDate birthDate boolean medicalTest) {
super(); // Required by the ObjectPlusPlus
this.firstName = firstName;
this.lastName = lastName;
this.birthDate = birthDate;
// "Changes" a person into an employee
addEmployee(medicalTest);
}
private void addEmployee(boolean medicalTest) {
// Creation of the employee part
Employee p = new Employee(medicalTest);
// Adding an employee as a link
// We use a method provided by the ObjectPlusPlus
this.addLink(roleNameEmployee roleNameGeneralization p);
}
private void addStudent(int number) throws Exception {
// Creation of the student part
Student s = new Student(number);
// Adding a student as a link
// We use a method provided by the ObjectPlusPlus
this.addLink(roleNameStudent roleNameGeneralization s);
}
private void addPensioner() throws Exception {
// Creation of the pensioner part
Pensioner e = new Pensioner();
// Adding a pensioner as a link
// We use a method provided by the ObjectPlusPlus
this.addLink(roleNamePensioner roleNameGeneralization e);
}
public boolean hasMedicalTest() throws Exception {
// get an employee object
try {
ObjectPlusPlus[] obj = this.getLinks(roleNameEmployee);
return ((Employee) obj[0]).isMedicalTest();
} catch (Exception e) {
// Probably this is an exception telling that this is not an employee
// (we should introduce different exception classes)
throw new Exception("The object is not an employee!");
}
}
public int getNumber() throws Exception {
// get a student object
try {
ObjectPlusPlus[] obj = this.getLinks(roleNameStudent);
return ((Student) obj[0]).getNumber();
} catch (Exception e) {
// Probably this is an exception telling that this is not a student
// (we should introduce different exception classes)
throw new Exception("The object is not a Student!");
}
}
private static String roleNameEmployee = "specializationEmployee";
private static String roleNamePensioner = "specializationPensioner";
private static String roleNameStudent = "specializationStudent";
private static String roleNameGeneralization = "generalization";
}
Important fragments:
- (2). Standard business characteristics specific to the person.
- (6). A constructor that creates an instance of a person class.
- (7). Since we use the
ObjectPlusPlusclass, we must call the constructor from the superclass. - (9). Storing business data.
- (14). Constructor creating an employee.
- (22). Calling the method (25) that creates the object-part describing the employee and the association within the composition.
- (27). Create a part-object describing the employee.
- (31). Add it as a part to an existing whole. Uses the method provided by
ObjectPlusPlus. - (43). A Method analogous to (25), but creating a pensioner.
- (52). A method to check if an employee has medical tests.
- (54). The main part of the method is executed within the exception catch block.
- (55). Based on the role name we are trying to recover related objects. There really should be only one.
- (57). In case of any problems (e.g. no link, the person is not an employee), we receive an exception. In the real implementation one should distinguish different types of errors, eg by introducing different classes of exceptions.
- (64). Method analogous to (52), but returning information specific to student.
- (76). Role names used to create associations within a composition.
The undoubted advantage of this method is that by breaking down functionality into different classes we use only those parts that we really need. Compared to the previous way, where in all classes were always all attributes or methods, this is definitely a useful feature. Depending on the implementation of auxiliary methods, you can also talk about the ease of use.
The main disadvantage of this method, which also occurred in the previous approach, is the lack of inheritance constructs, such as overriding methods or their polymorphic call. And of course, bigger disorder on the diagram. Someone who will look first and see compositions will not associate this with an inheritance.
Polymorphism in overlapping inheritance
Mechanism of overriding methods and their polymorphic call seems very useful. Unfortunately, for more complex hierarchies (eg, inheritance), there is a problem with choosing the right method. It consists in that, depending on the subclass, we have different definitions of the same operation. At the same time our object belongs to several classes at once, so theoretically also has access to several versions of methods - which one to choose? It is difficult to imagine an algorithm that does this automatically for all business cases.

Which version of the getIncome() method should be called to calculate the income of the employee and employee and student? Probably none of them... The solution is to create a new method which, depending on the
types of objects, will take into account the corresponding incomes. Sample code is pretty simple:
public float getIncome() throws Exception {
float income = 0.0f;
if(this.anyLink(roleNameEmployee)) {
// Employee
ObjectPlusPlus[] obj = this.getLinks(roleNameEmployee);
// add employee's income
income += ((Employee) obj[0]).getIncome();
}
if(this.anyLink(roleNameStudent)) {
// Student
ObjectPlusPlus[] obj = this.getLinks(roleNameStudent);
// add Student's income
income += ((Student) obj[0]).getIncome();
}
if(this.anyLink(roleNamePensioner)) {
// Pensioner
ObjectPlusPlus[] obj = this.getLinks(roleNamePensioner);
// add pensioner's income
income += ((Pensioner) obj[0]).getIncome();
}
return income;
}
Complete and incomplete inheritance
Incomplete inheritance means that we are aware that we will have more subclasses but we do not know them yet. A similar construction is an ellipse (placed as "..." instead of a subclass) that says we know additional subclasses, but for some reason (eg, readability) they are not presented.
Let's consider - what this kind of inheritance means for implementation? First of all, we need to ensure that we can inherit, for example, we should not use the word finalwhich does not allow Java to inherit from a class
declared with that expression.
Let's keep in mind that we usually have several ways to meet a business requirements. Knowing that in the future we can have more subclasses, it is worth to try a solution, which may now make us a little more trouble, but later will significantly facilitate implementation.

final keyword to implement the {Complete} constraint in a Java program. It blocks the creation of subclasses, e.g.
final class Person { ... }→ no subclasses for thePersonclass,final class Employee { ... }→ no subclasses for theEmployeeclass.
Person subclasses (beyond the existing ones), so using final in our code won't help us.
Multi-inheritance - flattening or composition
Multi-inheritance (multiple inheritance) occurs when a subclass has more than one superclass.
Let's look at the above definition. This one, seemingly minor change, causes a lot of trouble. Let's analyze the following example:

The main change is to add a class inheriting from Employee and Student (Working Student). First let's see if we can do that. Is the subclass a special case of both superclasses? Naturally, yes: a working student is
both an employee and a student. Since we have already tested this, let's go back to the problem we mentioned earlier. In accordance with the rules of superclass inheritance we get all invariants (constants such as attributes, methods,
associations). Both classes have the same attributes (inherited from class Person): name, lastName, etc. In this case, class Employed student inherits two sets of attributes. We do not want to have two last names in the class,
two first names, etc. The problem with attributes can be solved somehow, thinking this way: what does it mean that we inherit the attribute? This means the ability to store a certain value, that is, if the class already has that ability
(eg attribute name), then placing the same attribute one more time could be ignored(because they are all the same). More serious problems occur with methods, because they can vary (and most likely they are). In our example, there are two
versions of getIncome() (derived from student and employee) - which one to choose? In business terms, none is appropriate. We should create another version that sums up the income from the "employee" part and
the "student" part.
What is the solution to the above problem with duplication of invariants? Unfortunately, there is no one good way - we usually have to determine exactly which element we are dealing with, or manually eliminate the conflict.
How does all this have to do with programming languages? Multi-inheritance
- occurs directly in C++. In the case of a name conflict that we wrote before, we use the scope operator. This allows the compiler to know which invariant to use;
- does not exist in Java or MS C#.
How can we implement them? Traditionally, there are several ways to deal with this problem:
- We can use the approaches we used to overlapping inheritance: we leave one hierarchy of inheritance, and the rest are grouped in superclasses or implemented by composition or aggregation. The basic question that arises is about the selection criteria: which hierarchy of inheritance to leave and which to replace? The answer is quite intuitive: the branch, where we use more inheritance-related constructions (overriding, polymorphic calls, etc.) leave intact, but remaining branches should be "converted".
- The new way we have not used so far is to utilize interfaces available in both Java and MS C# - see below.
Multi-inheritance, interfaces
Let's start by reminding what interfaces are and what is they purpose. The interface contains a list of method headers (without their bodies or definitions). Facilitate the use of objects (method calls) without being interested in their "class membership". Implementing an interface by a class simply means delivering bodies to all the methods it defines. A class can implement any number of interfaces. By contrast, because of their limitations, we have no way of including attributes in them, so we use only methods. Part of this problem can be solved using getters and setters, that is, methods with the prefix get/set, which are used to retrieve attribute values and changing its value.
public and final. So they really serve as constants, eg to diversify the kind of an element.
Unfortunately, a serious drawback of using interfaces is the need to implement the same methods repeatedly and in the same way. This is the case when in different classes we have, for example, the getName() method, which simply
returns its value. Part of this problem is solved by modern IDEs that can automatically generate the appropriate sets of setters and getters for the specified attributes. After all, this is only a partial solution because:
- it relates to specific types of methods and does not help in "normal" methods,
- in case of modifications, we need to manually update each implementation
Another approach to solving the problem of multiple implementation methods can be a solution that use design patterns, such as delegations or commands. In short, by implementing the same method in different classes, instead of directly duplicating the code in its body, we delegate its execution (from within it in many classes) to some (the same) object.
Let's take a look at the following figure illustrating the conversion of interface (in UML interfaces are similar to inheritance, but with dashed lines).

After the conversion, the Employed Student class inherits from the Employee class and implements the IStudent interface. This interface is also implemented by the Student class. There is also a situation
we have described previously. The getScholarship() and getNumber() methods will probably have identical bodies in both implementations.
getIncome() is no longer available - for a student it will simply return the scholarship value, but the employee will still have his paycheck.
The appropriate code implementing the WorkingStudent class is shown below
public interface IStudent {
public abstract float getIncome();
public abstract float getScholarship();
public abstract void setScholarship(float scholarship);
public abstract int getNumber();
}
public class WorkingStudent extends Employee implements IStudent {
private int number;
private float scholarship;
public WorkingStudent(String firstName String lastName LocalDate birthDate int number float scholarship boolean medicalTest float salary) {
super(firstName lastName birthDate medicalTest salary);
this.number = number;
this.scholarship = scholarship;
}
@Override
public float getScholarship() {
return scholarship;
}
@Override
public void setScholarship(float scholarship) {
this.scholarship = scholarship;
}
@Override
public float getIncome() {
return super.getIncome() + getScholarship();
}
@Override
public int getNumber() {
return number;
}
@Override
public String toString() {
return String.format("Working student: %s %s, scholarship: %s, salary: %s" getFirstName(), getLastName(), getScholarship(), getSalary());
}
}
IStudent. This allows you to see at a glance what we are dealing with and there is no name conflict problem (if we
wanted to have a class and interface named Student). Java is a bit different: interfaces are called as the ability to perform certain operations, eg Serializable.
Notes for implementation:
- (1). The
IStudentinterface declaration. As we can see, it only contains method headers. Their bodies will be added in the classes that implement it. - (8). The
WorkingStudentclass inherits fromEmployeeand implementsIStudent(implements). - (12). Class constructor retrieving the relevant business parameters.
- (13). Call the superclass constructor.
- (19). Implementation of the interface method. Probably identical to that in the
Studentclass. - (23). Implementing a method from an interface, but in a different version than the
Studentclass.
We mentioned the problem of multiple functionality duplication in classes implementing the same interface. Can it be fixed? Of course - we already wrote about it. Partial solution to the problem of multiple implementation of the same methods may look like the following (diagram, code and comments):

public class WorkingStudent extends Employee implements IStudent {
Student student;
public WorkingStudent(String firstName String lastName LocalDate birthDate int number float scholarship boolean medicalTest float salary) {
super(firstName lastName birthDate medicalTest salary);
student = new Student(null null null number scholarship); // pass nulls to prevent data redundancy (firstName, etc.)
}
@Override
public float getIncome() {
return super.getIncome() + getScholarship();
}
@Override
public int getNumber() {
return student.getNumber();
}
@Override
public float getScholarship() {
return student.getScholarship();
}
@Override
public void setScholarship(float scholarship) {
student.setScholarship(scholarship);
}
}
- (2). A reference to the object to which we will redirect the student function calls.
- (4). Constructor.
- (5). Call the superclass constructor.
- (7). Create a student object that will be used to provide "student" functionality.
- (10). An interface method. As you can see, we call it from the student object and return the result.
- (18). An interface method that returns an income. First, we call the superclass method (to give us the employee's income), then we add the scholarship.
Quite a concern may be remembering some of the attributes twice (eg first name, last name): once in class WorkingStudent and the second time in the attached Student class. How can we remedy this? The answers are actually
two: modifying the Student class (and/or inheritance hierarchy), or passing some special value to the Student object, eg null.
Source codes
