Skip to content

X Chromosomal Recessive Vererbung Beispiel Essay

Britannica Academic from Encyclopædia Britannica
  • Login to Britannica Academic
  • Combines Encyclopedia Britannica plus Merriam-Webster’s Collegiate Dictionary, magazines and periodicals, and many other research tools providing the variety of reliable sources that college students need to consult when conducting thorough collegiate research—all from one resource. Written by Nobel laureates, historians, curators, professors, and other notable experts.
  • Video Tutorial: Getting started with Britannica Academic
Britannica School from Encyclopedia Britannica
  • Login to Britannica School – High
  • Offers thousands of up-to-date, curated, and curriculum-relevant articles, images, videos, audio clips, primary sources, maps, research tools, recommended Web sites.
Testing & Education Reference Center from Gale
Opposing Viewpoints in Context
Diversity Studies Collection
  • Login to the Diversity Studies Collection
  • For social science, history and liberal arts coursework, this collection of more than 150 journals explores cultural differences, contributions and influences in our global community.

Additional Encyclopedias

 

Britannica School from Encyclopedia Britannica
  • Login to Britannica School – Middle
  • Offers thousands of up-to-date, curated, and curriculum-relevant articles, images, videos, audio clips, primary sources, maps, research tools, recommended Web sites.
Research in Context

Additional Encyclopedias

Britannica School from Encyclopedia Britannica
  • Login to Britannica School – Elementary
  • Offers thousands of up-to-date, curated, and curriculum-relevant articles, images, videos, audio clips, primary sources, maps, research tools, recommended Web sites.
Kids InfoBits

Objektorientierung ist das in der Praxis vorherrschende Programmiersprachenparadigma. Vererbung gilt als einer ihrer wichtigsten Grundpfeiler – vielfach sogar als das Merkmal der Objektorientierung schlechthin. Sie verspricht Wiederverwendbarkeit, indem sie die Klassen eines objektorientierten Systems hierarchisch strukturiert, vom Allgemeinen zum Speziellen: Button erbt von Widget, Fahrzeug von Fortbewegungsmittel, Motorrad von Fahrzeug et cetera.

Problembeispiel: Kreis-Ellipsen-Paradoxon

Es gibt Situationen, in denen der Einsatz von Vererbung scheinbar unlösbare Schwierigkeiten nach sich zieht. Eine davon ist das sogenannte Kreis-Ellipsen-Dilemma (beschrieben u.a. von Robert C. Martin [1], Kazimir Majorinc [2] und Kevlin Henney [3]): Einerseits ist ein Kreis eine spezielle Ellipse, was dafür spricht, eine Klasse Circle von einer Klasse Ellipse erben zu lassen (s. Listing 1).

Listing 1: Circle erbt von Ellipse

Anzeigepublic class Ellipse {

private float minorAxis;
private float majorAxis;

public Ellipse(float minorAxis, float majorAxis) {
this.minorAxis = minorAxis;
this.majorAxis = majorAxis;
}

public float getMajorAxis() {
return majorAxis;

}
public float getMinorAxis() {
return minorAxis;
}

public void stretchMajorAxis(float factor) {
this.majorAxis *= factor;
}

// ... more methods ...
}


public class Circle extends Ellipse {

public Circle(float radius) {
super(radius, radius);
}

public float getRadius() {
return getMajorAxis();
}

@Override
public void stretchMajorAxis(float factor) {
this.majorAxis *= factor;
this.minorAxis *= factor;
}

// ... more methods ...
}

Andererseits benötigt eine Ellipse mehr Attribute als ein Kreis, nämlich eine Haupt- und eine Nebenachse statt nur einen Radius. Das wiederum deutet darauf hin, dass Ellipse von Circle erben sollte (s. Listing 2).

Listing 2: Ellipse erbt von Circle

public class Circle {

private float radius;

public Circle(float radius) {
this.radius = radius;
}

public float getRadius() {
return radius;
}

public void setRadius(float radius) {
this.radius = radius;
}

// ... more methods ...
}


public class Ellipse extends Circle {

private float minorAxis;

public Ellipse(float majorAxis, float minorAxis) {
super(majorAxis);
this.minorAxis = minorAxis;
}

public float getMajorAxis() {
return this.radius;
}

public void setMajor(float majorAxis) {
this.radius = majorAxis;
}

public float getMinorAxis() {
return minorAxis;
}

public void setMinorAxis(float minorAxis) {
this.minorAxis = minorAxis;
}

// ... more methods ...
}

Diese Mehrdeutigkeit ist nicht das einzige Problem. Im ersten Fall erbt die Klasse Circle von der Klasse Ellipse nicht benötigte Member-Variablen und Methoden. Zum Beispiel sind die beiden unabhängig voneinander änderbaren Member-Variablen majorAxis und minorAxis für Circle nicht sinnvoll, er benötigt nur einen Radius.

Eine Operation, die eine Achse der Ellipse streckt, würde in der Subklasse Circle dazu führen, dass der Kreis nach Anwendung dieser Methode kein Kreis mehr ist. Überschreibt man sie in der Subklasse so, dass der Kreis seine Form behält, werden Invarianten der Klasse Ellipse verletzt: Bei einer Ellipse ändert die Methode stretchMajorAxis die Fläche proportional zu factor, bei einem Kreis dagegen quadratisch mit factor. Davon abgesehen wäre der Methodenname stretchMajorAxis in der Subklasse Circle missverständlich, wenn hier beide Achsen gestreckt werden.

Auch im zweiten Fall erbt die Subklasse Methoden, die sie nicht benötigt: getRadius wirft bei einer Ellipse Probleme auf, da für Benutzer nicht klar ist, ob damit die Haupt- oder die Nebenachse gemeint ist. Ellipse ist als Subklasse von Circle semantisch unsinnig, da im Allgemeinen eine Ellipse kein Kreis ist, vielmehr ist umgekehrt ein Kreis eine spezielle Ellipse. Egal, wie man es dreht: An irgendeiner Stelle "passt" es nicht – darum die Bezeichnung Dilemma.

Ein ähnlich gelagerter Fall ist die Programmierung einer Klasse Complex für die Abbildung der komplexen Zahlen als Subklasse einer Klasse Float (s. Listing 3).

Listing 3: Complex als Subklasse einer Klasse Float

public class Float {

private float value;

public Float(float value) {
this.value = value;
}

public float getValue() {
return value;
}
// ... more methods ...
}


public class Complex extends Float {

private float img;

public Complex(float real, float img) {
super(real);
this.img = img;
}

public float getReal() {
return value;
}

public float getImg() {
return img;
}

public Complex add(Complex other) {
return
new Complex(this.getReal() + other.getReal(),
this.getImg() + other.getImg());
}
// ... more methods ...
}

Complex erbt hier den Realteil von der Oberklasse, und für den Imaginärteil fügt sie eine weitere Member-Variable, ebenfalls vom Typ float, hinzu.

Auch hier ist die Klasse Float in der Vererbungshierarchie über Complex angesiedelt. Float-Zahlen lassen sich aber als spezielle komplexe Zahlen ansehen, nämlich solche mit Imaginärteil 0. Von daher wäre Float in der Typhierarchie unterhalb von Complex zu erwarten. Der umgekehrte Ansatz, die Programmierung von Float als Subklasse von Complex, würde allerdings ähnliche Schwierigkeiten aufwerfen wie im vorhergehenden Beispiel die Programmierung von Circle als Subklasse von Ellipse.

Gleichheit

Problembeispiel: Gleichheit

Ein anderer Fall ist die Implementierung von Gleichheit. Eine Vielzahl von Fachbüchern, Artikeln und Blogs widmet sich dieser überraschend schwierigen Aufgabe (um nur einige herauszugreifen: Joshua Bloch & Bill Venners, Martin Odersky, Cay Horstmann sowie Joshua Bloch [4], Tal Cohen [5], Chandan R. Rupakheti und Daqing Hou [6]). Die korrekte Implementierung der Methode equals ist so komplex und fehlerträchtig, dass sich auch in professionellem Code und in vielen
Lehrbuchbeispielen fehlerhafte Implementierungen für sie finden lassen (mehrere Beispiele dafür sind bei Angelika Langer und Klaus Kreft aufgeführt).

Ein Codebeispiel mit zwei- und dreidimensionalen Punkten gibt einen Eindruck von der Komplexität dieser Aufgabe. Listing 4 zeigt die beiden Klassen Point2D und Point3D mit der zugehörigen Vererbungsbeziehung.

Listing 4: Point3D erbt von Point2D

public class Point2D {

private float x;
private float y;

public Point2D(float x, float y) {
this.x = x;
this.y = y;
}

@Override
public boolean equals(Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (!(obj instanceof Point2D)) { return false; }
Point2D other = (Point2D) obj;
if (this.x != other.x) { return false; }
if (this.y != other.y) { return false; }
return true;
}

// ... more methods ...
}


public class Point3D extends Point2D {

private float z;

public Point3D(float x, float y, float z) {
super(x,y);
this.z = z;
}

// ... more methods ...
}

Die beiden folgenden Codebeispiele in Listing 5 und Listing 6 zeigen jeweils naheliegende, aber falsche Implementierungen der Methode equals für die Klasse Point3D.

Listing 5: equals-Methode in Point3D – Problem: nicht symmetrisch

@Override
public boolean equals(Object obj) {
if (!(obj instanceof Point3D)) { return false; }
Point3D other = (Point3D) obj;
if (!(super.equals(other))) { return false; }
if (this.z != other.z) { return false; }
return true;
}

// Aufruf von equals
Point2D p2 = new Point2D(1, 2);
Point3D p3 = new Point3D(1, 2, 3);
if (p2.equals(p3)) { ... } // true
if (p3.equals(p2)) { ... } // false

Listing 6: Andere equals-Methode in Point3D – Problem: nicht transitiv

@Override
public boolean equals(Object obj) {
if (!(obj instanceof Point2D)) { return false; }
if (!(obj instanceof Point3D)) { return obj.equals(this); }
Point3D other = (Point3D) obj;
if (!(super.equals(other))) { return false; }
if (this.z != other.z) { return false; }
return true;
}

// Aufruf von equals
Point3D p1 = new Point3D(1, 2, 3);
Point2D p2 = new Point2D(1, 2);
Point3D p3 = new Point3D(1, 2, 4);
if (p1.equals(p2)) { ... } // true
if (p2.equals(p3)) { ... } // true
if (p1.equals(p3)) { ... } // false

Auch hier liegt bei genauer Betrachtung das Problem darin, dass sich die zugehörigen Klassen in einer Vererbungshierarchie befinden. Wie Joshua Bloch in [4; Item 8] ausgeführt hat, ist es praktisch nicht möglich, Gleichheit so zu implementieren, dass sie sich korrekt, das heißt regelkonform (Stichwort: Equality Contract) verhält und dass gleichzeitig alle Vergleiche erwartungsgemäß funktionieren:

  • Vergleiche von Exemplaren der Superklasse untereinander,
  • Vergleiche von Exemplaren der Subklasse untereinander und
  • Vergleiche von Exemplaren der Superklasse mit Exemplaren der Subklasse sowie umgekehrt.

Gleichheit ist in dieser Hinsicht kein Einzelfall: Alle binären Methoden – das heißt solche, bei denen Receiver und Parameter vom gleichen Typ sind, wie Addition, Multiplikation, Kleiner-Beziehung et cetera – werfen in einer Vererbungshierarchie Probleme auf [7].

Bundling

Vererbung = "Bundling" zweier Sprachbausteine

Zwar unterscheiden sich objektorientierte Sprachen darin, wie Vererbung im Detail funktioniert (Einfach- versus Mehrfachvererbung, unterschiedliche Zugriffs-Modifier, dynamisches Binden etc.), allen verbreiteten objektorientierten Sprachen – Java, C#, Scala, Kotlin, Ceylon, Swift, Eiffel, Smalltalk et cetera – ist aber gemeinsam, dass ihre Vererbung zwei grundlegend verschiedene Sprachbausteine miteinander kombiniert: Subclassing und Subtyping.

Subclassing ist die Übernahme der Implementierung und wird darum als "Implementierungsvererbung" bezeichnet: Alle Member – Datenfelder und Methoden – der Superklasse stehen automatisch auch in der Subklasse zur Verfügung. Letztere kann zusätzliche Member definieren und innerhalb vorgegebener Regeln die geerbten Methoden überschreiben, das heißt mit einer eigenen Implementierung versehen. Definiert etwa eine Klasse Fahrzeug die Member-Variablen Hersteller, Höchstgeschwindigkeit und Hubraum, werden sie auf eine Subklasse wie Motorrad vererbt. Zusätzlich kann die Subklasse eigene, Motorrad-spezifische Member hinzufügen.

Subtyping, auch als "Schnittstellenvererbung" bekannt, beschreibt den Umstand, dass sich der eine Typ als Subtyp des anderen auffassen lässt: Alle dessen Exemplare zusammengenommen bilden eine Teilmenge aller Exemplare des Supertyps, und darum lässt sich, wo ein Element des Supertyps erwartet wird, ein Element des Subtyps verwenden. So ist die Menge aller Motorräder eine Teilmenge aller Fahrzeuge, und Code, der Fahrzeuge bearbeitet, kann auch Motorräder handhaben, ohne dass er speziell für sie anzupassen ist. Der benutzende Code muss nicht einmal wissen, dass er es mit Motorrädern zu tun hat.

Subclassing und Subtyping bewirken unterschiedliche Arten von Wiederverwendung. Beim Subclassing wird der Code der Superklasse wiederverwendet: Die Member-Variablen und Methoden der Superklasse sind in der Subklasse nicht nochmals zu programmieren. Beim Subtyping dagegen wird der Code wiederverwendet, der die Superklasse benutzt, also seine Klienten. Code, der Fahrzeuge anspricht, lässt sich ohne weiteres auch für Motorräder, PKWs, LKWs et cetera benutzen.

Im Fahrzeugbeispiel fallen Subclassing und Subtyping zusammen. Die Klasse Motorrad erbt die Implementierung der Klasse Fahrzeug, und der Typ Motorrad bildet einen Subtyp des Typs Fahrzeug. Dass das aber nicht immer so ist, zeigen die eingangs beschriebenen Problemfälle Kreis/Ellipse, Float/Complex und Point2D/Point3D.

Grundsätzlich ist es möglich, Subclassing sprachlich von Subtyping zu trennen, und in einigen Programmiersprachen gibt es Ansätze dafür. So unterstützt C++ private Vererbung. Auf diese Weise erbt die Subklasse die Implementierung, sie bildet aber keinen Subtyp. Mit Duck Typing, das zum Beispiel Python zugrunde liegt, lässt sich umgekehrt eine Klasse immer dann anstelle einer anderen verwenden, wenn sie über deren Methoden verfügt. Sie fungiert dann als ihr Subtyp, unabhängig davon, ob es zwischen den beiden eine Vererbungsbeziehung gibt oder nicht.

Die meisten Programmiersprachen aber betreiben "Bundling": Mit einem einzigen Symbol (wie ":") oder Schlüsselwort (wie "extends") erhalten Programmierer immer beides, Subclassing und Subtyping. Sie sind untrennbar aneinander gekoppelt, das eine ist nicht ohne das andere zu haben. Diese Kopplung verringert die Lesbarkeit des Codes. Stößt man in bestehendem Code auf eine Anweisung wie A extends B, bleibt die Intention der Programmierer unklar. Wollten sie hier die Implementierung von A in B wiederverwenden? Oder B als Subtyp von A definieren? Oder beides? Vielen Programmierern ist der Unterschied zwischen Subclassing und Subtyping gar nicht bewusst, sie betrachten Vererbung als ein einziges, unteilbares Konzept.

Wie das Beispiel Fahrzeug/Motorrad zeigt, funktioniert die Kombination von Subclassing und Subtyping in manchen Fällen recht gut. Das Subclassing ist hier sogar ursächlich für das Subtyping: Weil die Klasse Motorrad alle Member-Variablen und Methoden von der Basisklasse Fahrzeug erbt, ist sie konform zu deren Schnittstelle, und deshalb lässt sich ein Motorrad-Exemplar überall dort verwenden, wo per Definition ein Fahrzeug akzeptiert wird.

Vererbung passt nicht zu Werten

In den eingangs beschriebenen Beispielen dagegen funktioniert die Kombination von Subclassing und Subtyping nicht. Dort führt Subclassing nicht zu Subtyping. Bereits anschaulich ist klar, dass die Menge der dreidimensionalen Punkte keine Teilmenge der zweidimensionalen Punkte bildet und die komplexen Zahlen nicht in den Gleitkommazahlen enthalten sind. Wer Ellipse von Kreis erben lässt, um die Implementierung von Kreis in Ellipse wiederzuverwenden, erhält in Hinblick auf Subtyping genau die "falsche" Richtung: Ellipsen sind keine speziellen Kreise, sondern umgekehrt sind Kreise spezielle Ellipsen.

In all diesen Beispielen handelt es sich um Werte: unveränderliche Abstraktionen mit fester Menge. Zahlen und Punkte sind Werte, auch Formen wie Kreis und Ellipse lassen sich als wertartige, mathematische Abstraktionen auffassen. Das ist der entscheidende Unterschied: Was bei Objekten hinlänglich gut funktioniert – Motorräder sind spezielle Fahrzeuge, Buttons sind spezielle Widgets –, klappt nicht bei Werten.

Bereits mehrfach wurde beobachtet, dass Vererbung in Zusammenhang mit Werten kritisch zu sehen ist. Kevlin Henney konstatierte bereits 2002, dass Vererbung auf Werte keine Anwendung findet: "Values do not typically find themselves in class hierarchies". Michael Kölling und John Rosenberg, deren Sprache Blue Werte in Form von "Manifest Classes" unterstützt, drücken es noch drastischer aus: "manifest classes and inheritance simply do not go together". Auch Programmiersprachen halten sich bei Werten mit Vererbung zurück. Beispielsweise gibt es in C# Vererbung nur bei Klassen, nicht aber bei den für die Unterstützung von Wertsemantik gedachten structs.

Fazit

Spezialfall Aufzählungen

Aufzählungen – Typen wie Wochentag, Himmelsrichtung oder Währung – lassen sich in vielen Sprachen, etwa Java, C#, Kotlin oder Swift, mit einem eigenen Schlüsselwort (enum) definieren. Andernfalls werden sie durch geeignete Muster wie das Typesafe Enum Pattern emuliert [8].

Auch bei Aufzählungen ergibt sich manchmal der Bedarf, Subtypen zu bilden. Zum Beispiel lässt sich der Typ Workday als Subtyp von Weekday ansehen: Die Menge aller Werktage (Montag bis Samstag) ist eine Teilmenge aller Wochentage (Montag bis Sonntag). Es ist sinnvoll, einen Workday immer dort zu verwenden, wo ein Weekday erwartet wird.

Um das zu bewerkstelligen, würden viele Entwickler am liebsten eine Vererbungsbeziehung zwischen Workday und Weekday programmieren. Doch wenn sie, wie in Java, als enum programmiert werden, ist das nicht möglich. Oracle gibt dafür technische Gründe an: "All enums implicitly extend java.lang.Enum. Because a class can only extend one parent (...), the Java language does not support multiple inheritance of state (see Multiple Inheritance of State, Implementation, and Type), and therefore an enum cannot extend anything else."

Doch Vererbung würde hier gar nicht zum Ziel führen. Würde man – im hypothetischen Fall, dass Vererbung für Enums möglich wäre – Workday als Subklasse von Weekday programmieren, könnte Workday allenfalls Member hinzufügen, aber keine weglassen.Workday wäre damit eine Obermenge, keine Teilmenge von Weekday. Wie in den obigen Beispielen führt Subclassing auch bei Aufzählungen nicht zu Subtyping.

Der Grund ist, dass auch Aufzählungen wie Workday und Weekday Werte beschreiben: Sowohl ihre Exemplare sind unveränderbar als auch ihre Wertemenge; bei Aufzählungen ist die Wertemenge sogar explizit in der Definition des Typs angegeben. Es sprechen also auch fachliche Gründe dafür, Vererbung bei Enums sprachlich nicht zu unterstützen.

Fazit und Ausblick

Rückblickend zeigt sich, dass die eingangs beschriebenen, problematischen Beispiele eine gemeinsame Ursache haben: Es wurde Subclassing angewendet, um Subtyping zu erreichen. Das konnte nicht funktionieren, weil es sich in allen Fällen um Werte handelte. Die meisten objektorientierten Programmiersprachen "verleiten" Entwickler zu diesem Fehler, weil sie Subclassing und Subtyping – unter dem Namen "Vererbung" – nur in Kombination anbieten.

Subtypbeziehungen können im Prinzip auch bei Werten vorkommen. Zum Beispiel lassen sich Ganzzahlen als Subtyp der Gleitkommazahlen betrachten, diese wiederum als Subtyp der komplexen Zahlen. Auch zwischen den im vorigen Abschnitt beschriebenen Aufzählungstypen Workday und Weekday liegt eine Subtypbeziehung vor. Wie eine objektorientierte Programmiersprache auch bei Werten Subtypbeziehungen unterstützen kann und wie das die Ausdruckskraft und Sicherheit der Sprache erhöht, soll in einem nachfolgenden Beitrag untersucht werden. (ane)

Beate Ritterbach
arbeitet seit 25 Jahren freiberuflich als Softwareentwicklerin. Daneben befasst sie sich mit Programmiersprachenkonzepten, schwerpunktmäßig mit der Verbindung von wertorientierter (= funktionaler/zustandsloser) und objektorientierter (= imperativer/zustandsbehafteter) Programmierung.

Literatur
  1. Robert C. Martin; The Liskov Substitution Principle; Engineering Notebook, The C++ Report (1996)
  2. Kazimir Majorinc; Ellipse-Circle Dilemma and Inverse Inheritance; in: ITI 98, Proceedings of the 20th International Conference of Information Technology Interfaces, 627-632 (1998)
  3. Kevlin Henney; From Mechanism to Method: Total Ellipse; in: Dr. Dobb's C/C++ Users Journal (2001)
  4. Joshua Bloch; Effective Java 2nd edition; Addison-Wesley, 2008
  5. Tal Cohen; How Do I Correctly Implement the equals() Method?; in: Dr. Dobb's Journal (2002)
  6. Chandan R. Rupakheti, Daqing Hou; An empirical study of the design and implementation of object equality in Java; in: CASCON '08, ACM 111-125 (2008)
  7. Kim Bruce, LucaCardelli, Giuseppe Castagna, Gary T. Leavens, Benjamin Pierce; On Binary Methods, Theory and Practice of Object Systems 1: 221-242 (1995)
  8. Joshua Bloch; Effective Java programming language guide; Sun Microsystems Inc., 2001