Sichtbarkeit Klassenvariablen

cwriter

Erfahrenes Mitglied
Ok, das ist aber auch protected und nicht private :)

PHP:
class Flugzeug {
    private $plaetze = 200;
    function getPlaetze() {
        return $this->plaetze;
    }
}

class Kleinflugzeug extends Flugzeug {
   
    public $plaetze = 5;
   
    function getPlaetze() {
        return "Kindklasse";
    }
    function getParentPlaetze(){
        return parent::getPlaetze();
    }  
}

$kl = new Kleinflugzeug();
echo $kl->getParentPlaetze();
Ergibt 200 ;)

(Das ist nicht weiter verwunderlich: PHP: Objekt-Vererbung - Manual )
Wenn man zum Beispiel von einer Klasse erbt, so erbt die Unterklasse alle Methoden der Sichtbarkeiten public und protected von der Vaterklasse. Wenn eine Klasse diese Methoden nicht überschreibt, wird die ursprüngliche Funktionalität beibehalten.
Nur ganz verschmolzen wird ja in diesem Fall nicht - zumindest nicht intern auf den privaten Variablen.

Gruss
cwriter
 

ComFreek

Mod | @comfreek
Moderator
Hm, aber private Variablen werden ja nicht überschrieben.
Aha! Ich ging davon aus, dass die (öffentliche?) Instanzvariable im Codeschnipsel meines Beitrags überschrieben wird.

Vielleicht als Erklärung für @schiese: Es gibt zwei Konzepte: Überschreiben ("overloading") und Überdecken ("name shadowing"). Gleich vorweg: Shadowing ist fast immer schlechter Stil und sollte vermieden werden.
Überschreiben:
PHP:
class Test {
  function foo() { return 5; }
}
class TestChild extends Test {
  function foo() { return 10; }
}

$obj = new TestChild
echo $obj->foo(); // 10

function bar(Test $obj2) {
  echo $obj2->foo();
}
bar($obj); // immer noch 10! "Bindung zur Laufzeit" ist das Stichwort
Überdecken: Siehe Code von @cwriter ein Beitrag über mir: Die Variablen "$plätze" wird überdeckt. Das heißt einfach nur, dass es zur Laufzeit pro Kleinflugzeug zwei Instanzvariablen gibt! Einmal das $plätze von Kleinflugzeug und einmal das $plätze von Flugzeug. Es ist nur so, dass du innerhalb des Codes von Kleinflugzeug das $plätze von Flugzeug nicht mehr referenzieren kannst. Du hast nämlich das $plätze der Elternklasse überdeckt ("shadowed") und kannst du auf das "innerste" $plätze im Scope zugreifen und das ist nun mal das $plätze von Kleinflugzeug.
Hingegen referenziert das $plätze im Code von Flugzeug immer das $plätze von Flugzeug! Deswegen wird auch 200 ausgegeben.
 

schiese

Erfahrenes Mitglied
Danke für eure Antworten. Ich komme leider jetzt erst zum Antworten.
Wie @Yaslaw in Beitrag 10 geschrieben hat, wird ja die Klassenvariable aus der Elternklasse auch überschrieben/überdeckt, wenn sie nicht den Zugriffsmodifizierer private besitzt. Dann greift auch selbst die Methode getPlaetze() aus der Elternklasse auf die überdeckende Variable der Kindklasse zu. Das finde ich verwirrend. Weiter darf der Zugriffsmodifizierer der überdeckenden Variable der Kindklasse nur schwächer oder gleich dem Zugriffsmodifizierer der Elternklasse sein.

Ein weiteres Beispiel:

PHP:
class Flugzeug {

    protected $plaetze = 200;

    public function getPlaetze() {
        return $this->plaetze;
    }

    public function getPlaetzeVater() {
        return $this->plaetze;
    }

}

class Kleinflugzeug extends Flugzeug {

    protected $plaetze = 5;

    public function getPlaetze() {
        return parent::getPlaetze();
    }

}

$kleinflugzeug = new Kleinflugzeug();

echo $kleinflugzeug->getPlaetzeFlugzeug();

Hätte ich es in Java geschrieben, würde er 200 ausgeben, gibt allerdings 5 aus.

Schöne Grüße

schiese
 

ComFreek

Mod | @comfreek
Moderator
Wie @Yaslaw in Beitrag 10 geschrieben hat, wird ja die Klassenvariable aus der Elternklasse auch überschrieben/überdeckt, wenn sie nicht den Zugriffsmodifizierer private besitzt.
In dem Fall wird sie, wie dein Beispiel zeigt, tatsächlich überschrieben und eben nicht überdeckt. Würde $plätze überdeckt werden, so würde 200 ausgegeben werden.

Wenn in Java 200 ausgegeben wird, so kann man in Java anscheinend keine Membervariablen überschreiben. Tatsache: If you overwrite a field in a subclass of a class, the subclass has two fields with the same name(and different type)?

Am besten verwendet man niemals gleichnamige Membervariablen, egal ob es die Programmiersprache hergibt oder nicht. Um das sicherzustellen, verwendet man am besten auch ein statisches Lintingtool :)
 

cwriter

Erfahrenes Mitglied
Da die Diskussion nun doch etwas länger wurde, sollten wir das ein bisschen theoretischer betrachten.

Es gibt 2 Herangehensweisen für Klassenvererbungen:
1) Überschreiben (override)
2) Überdecken (~hide)

Mir ist nur eine Sprache bekannt, die beides zulässt: C++.
Gehen wir man ein paar Schritte zurück und schauen uns an, was die Idee hinter den Klassen ist.

C++:
class Tier {
public:
    void ton() {
        std::cout << "Unbekanntes Tier!" << std::endl;
    }
}

class Hund : public Tier {
public:
    void ton() {
        std::cout << "Wuff!" << std::endl;
    }
}

class Katze : public Tier {
public:
    void ton() {
        std::cout << "Miau!" << std::endl;
    }
}
//....

auto h = new Hund();
h->ton(); // "Wuff!"
Hier macht es nicht viel Sinn, Vererbung zu benutzen - ich will ja nur, dass ein Hund laut gibt. Meist will man aber, dass mehrere Tiere in einer Sammlung etwas tun sollen. Z.B. will ich, dass alle Tiere im Haus einen Laut von sich geben. Ich habe also mehrere Tiere:
C++:
std::vector<Tier*> tiere = { new Hund(), new Katze()};
for(auto t : tiere) {
    t->ton();
}
Hier greifen wir auf den statischen Typ "Tier" zu, hinter dem Pointer ist aber der dynamische Typ "Hund" bzw. "Katze".
Aber interessanterweise wird bei dem Beispiel nicht "Wuff! Miau!" ausgegeben, sondern 2x "Unbekanntes Tier!".
Hier überdeckt im statischen Typ die Definition von ton() in Hund und Katze die statische Definition von ton() in Tier. Wenn wir also auf den statischen Typ zugreifen, wird auch die Funktion des jeweiligen statischen Typs aufgerufen.

Das ist seltsam für alle Java-Programmierer. In C++ gibt es das "Java-Verhalten" aber auch: Man nennt es "virtual inheritance":
C++:
class Tier {
public:
    virtual void ton() {
        std::cout << "Unbekanntes Tier!" << std::endl;
    }
}

class Hund : public Tier {
public:
    virtual void ton() override {
        std::cout << "Wuff!" << std::endl;
    }
}

class Katze : public Tier {
public:
    virtual void ton() override {
        std::cout << "Miau!" << std::endl;
    }
}
//....

auto h = new Hund();
h->ton(); // "Wuff!"

std::vector<Tier*> tiere = { new Hund(), new Katze()};
for(auto t : tiere) {
    t->ton(); //"Wuff! Miau!"
}
Der Grund dahinter ist relativ einfach: C++ unterstützt mehrfache Vererbung, Java nicht. Diese Mehrfachvererbung führt zu einem Overhead beim Cast, weshalb es unsinnig wäre, alles per default virtual zu halten. Java hat dieses Problem nicht.

Dann gibt es noch ein Problem: Wir haben eine Klasse Tier, die keinen ton() von sich geben kann, es aber trotzdem tut. Insbesondere kann man ein Tier erstellen, das kein konkretes Tier ist:
C++:
auto t = new Tier();
t->ton();
Das ist schlecht. In Java gibt es dazu Interfaces, in C++ gibt es "pure virtual": Abstrakte Funktionen:
C++:
class Tier {
public:
    virtual void ton() = 0;
}

auto t = new Tier(); // Compiler Error: Abstract Class instantiated

Nun haben wir die "virtual inheritance" auch geklärt.

In Java sind die Memberfunktionen immer virtual (werden überschrieben) und die Felder (Membervariablen) immer non-virtual (werden verdeckt). Die Verdeckung findet aber natürlich nur in der Unterklasse statt, da die Basisklasse nichts von einer Überdeckung weiss. (Per super() kann auch eine Kindklasse auf die Elternvariablen zugreifen, aber soweit ich weiss geht das nicht von ausserhalb).

In PHP und meines Wissens auch JS (ES6) ist alles virtual (Ausnahme: Private Felder). Exportierte Symbole der Kindklassen teilen sich den Namespace mit allen Elternklassen.
Analog kennt PHP auch keinen Statischen Typ*: Alles ist ein Objekt, und der Typ ist (nur) dynamisch feststellbar.
Übrigens ist das bei den Skriptsprachen allgemein verbreitete Duck Typing ein Vorteil, der aus den fehlenden Statischen Typen hervorgeht: Du kannst damit selbst die Funktionen überschreiben (zumindest in JS, weiss gerade nicht, ob das auch in PHP so leicht geht).
(Ja, ich weiss, dass auch Skriptsprachen einige Andeutungen an statische Typen haben).

Am besten verwendet man niemals gleichnamige Membervariablen, egal ob es die Programmiersprache hergibt oder nicht.
Das lässt sich selten vermeiden - die gängige Art, dagegen vorzugehen, ist, getter und setter zu verwenden und die Membervariablen privat zu halten.
Aber es kann ja auch sein, dass man die Funktionalität einer Klasse mit einer eigenen Klasse erweitern will - oft werden die exportierten Variablen nicht dokumentiert, und man möchte ja auch nicht alle Klassen durchlesen.
Das Problem hier ist halt: Bei PHP gibt es das "Sorglospaket" nicht.


Um zusammenzufassen: PHP verhält sich anders als Java/C++. Ein Vergleich:
Code:
Feature                        C++        Java        PHP
Function Inheritance     static/virtual   virtual    virtual
Variable Inheritance     static           static     virtual (except private)

Oder anders: Die Objektstruktur von Java ist
Code:
class Base {
    vf_table
    base_fields
}
class Derived extends Base {
    vf_table
    class Base {
        base_fields
    }
    derived_fields
}
Die Basisklasse wird also in die Unterklasse "reinkopiert".
Die vf_table (virtual function table) beinhaltet die Function pointers, vereinfacht gesagt wird da jeweils die Adresse der überschreibenden Funktion an den Funktionsnamen gebunden.
Bei PHP ist das Klassenlayout so:
Code:
class Base {
    map_base_private
    map_members
}

class Derived extends Base {
    map_base_private
    map_derived_private
    map_members = assign map_derived_members to map_base_members
}

Als Schlusswort: Klassen verhalten sich in unterschiedlichen Sprachen leider anders. In deinem Fall kannst du Flugzeug als "abstract class" (= Interface) definieren, damit es gar nie Variablen geben kann, die sich überschreiben.
Und bei allem anderen: Private Variablen mit Gettern und Settern verwenden, das geht auch wie in Java.

Gruss
cwriter

*: Nicht ganz wahr - PHP hat Elemente von Statischen Typen, wie abstract classes, aber vieles andere (wie typed variables) fehlt halt.