OOP: Sichtbarkeit in Unterklassen - Verständnisfrage

Einklappen
X
 
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • OOP: Sichtbarkeit in Unterklassen - Verständnisfrage

    Hallo,

    soweit ich weiß ist es bei OOP möglich jede Eigenschaft/Methode der Basisklasse in einer abgeleiteten Unterklasse zu überschreiben. Einzige Voraussetzung ist, daß die Sichtbarkeit dadurch nicht eingeschränkt wird.

    Z.B.:

    Code:
      Basisklasse::test() // [B]public[/B]
       
            Unterklasse::test() // public (OK)
            Unterklasse::test() // protected (Access level Error)
            Unterklasse::test() // private (Access level Error)
       
      Basisklasse::test() // [B]protected[/B]
       
            Unterklasse::test() // public (OK)
            Unterklasse::test() // protected (OK)
            Unterklasse::test() // private (Access level Error)
       
      Basisklasse::test() // [B]private[/B]
       
            Unterklasse::test() // public (OK)
            Unterklasse::test() // protected (OK)
            Unterklasse::test() // private (OK)
    Analog zu den Methoden verhält es sich auch mit den Eigenschaften.

    Nun möchte ich von einer MySQL-Klasse eine Singleton-Klasse wie folgt ableiten:


    PHP-Code:
    class MySql {
          protected function 
    __construct() {
              
    // Verbindung zur Datenbank aufbauen
          
    }
                  
      }
       
      class 
    MySqlSingleton extends MySql{
          
          private static 
    $instance null;
          protected function 
    __construct() {
             
    parent::__construct();
          }
          
          public static function 
    getInstance() {
              if(
    self::$instance === null) {
                  
    self::$instance = new MySqlSingleton();
              }
              return 
    self::$instance;
          }
          
          private final function 
    __clone() {}
      }
       
      
    // $DB = new MySqlSingleton(); // Singleton, daher keine Objekte mit new möglich!
      // $DB = new MySql(); // geht nicht wegen protected function __construct()
       
      
    $DB MySqlSingleton::getInstance();
      
    $DB1 MySqlSingleton::getInstance();
       
      echo (
    $DB === $DB1)? "Ein Objekt":"Zwei Objekte"// liefert Ein Objekt 
    Soweit so gut. Jetzt gibt es nur noch ein Problem. Wenn ich die Basisklasse auch ohne die Unterklasse nutzen möchte, dann brauche ich doch einen „public“ Kontruktor. Wenn ich jedoch den Konstruktor in der Basisklasse (MySql) auf public setze, dann muss ich zwangsläufig auch den Konstruktor in der Unterklasse (MySqlSingleton) auf public ändern (ansonsten gibt es einen Access level error) und dann ist mein Singleton-Pattern strenggenommen kein Singleton mehr.

    Nun bin ich im Zusammenhang mit dem genannten Problem auf den folgenden Code gestoßen:


    PHP-Code:
    class MySqlSingleton extends MySQLi {
          
          private static 
    $instance null;
          protected function 
    __construct($server,$user,$pass,$db) {
             
    parent::__construct($server,$user,$pass,$db);
          }
          
          public static function 
    getInstance($server,$user,$pass,$db) {
              if(
    self::$instance === null) {
                  
    self::$instance = new MySqlSingleton($server,$user,$pass,$db);
              }
              return 
    self::$instance;
          }
          
          private final function 
    __clone() {}
          
          
      }
       
      
    // Singelton-Instanz
      
    $DB_Single MySqlSingleton::getInstance("localhost","user","passwort","datenbank");
       
      
    // MySQLi-Instanz
      
    $DB = new MySQLi ("localhost","user","passwort","datenbank"); 
    Hier scheint es komischerweise zu funktionieren. Der Konstruktor in der abgeleiteten Singelton-Klasse ist mit protected geschützt und der Konstruktor in der Basisklasse MySQLi ist offensichtlich public?! Nun verstehe ich ehrlich gesagt nicht, wie das möglich ist?



    Vielen Dank im Voraus für jede Hilfe.
    jack

  • #2
    Warum das Verhalten bei MySqli jetzt so ist, wie es ist, kann ich dir nicht sagen.

    Wenn du in der abgeleiteten Klasse das Singleton implementieren möchtest, dann füge der Basisklasse ebenfalls eine getInstance()-Methode hinzu, die aber - anders als in der abgeleiteten Klasse - eben nicht prüft, ob schon eine Instanz existiert und holst dir eine neue Instanz eben wieder über diesen Weg, anstelle von new.
    Zuletzt geändert von Quetschi; 26.03.2012, 22:10.
    Ihr habt ein Torturial durchgearbeitet, das auf den mysql_-Funktionen aufbaut?
    Schön - etwas Geschichte kann ja nicht schaden.
    Aber jetzt seht euch bitte php.net/pdo oder php.net/mysqli bevor ihr beginnt!

    Kommentar


    • #3
      Vielen Dank erst mal für die schnelle Antwort.

      Die Idee mit getIstnace() in der Basisklasse ist nicht schlecht, allerdings finde ich es schon eleganter, wenn es auch ohne Änderungen an der Basisklasse ginge.

      Deshalb finde ich den theoretischen Hintergrund bei dem MySQLi -Singelton-Code so interessieren. Letztendlich gelten für alle Klassen doch die gleichen Regeln, also müsste es für das „MySQLi-Singelton-Pattern“ auch eine plausible Erklärung geben?

      Kommentar


      • #4
        Wenn du an der Basisklasse nichts mehr ändern willst, bliebe noch der Weg über eine weitere Unterklasse, mit der du eine neue Instanz erzeugst.

        Warum die Sichtbarkeiten bei den BuiltIn-Klassen von PHP nun geändert werden können, weiß ich ehrlich gesagt nicht. Es betrifft jedenfalls nicht nur die mysqli sondern auch (vermutlich alle) andere Klassen wie exception, soap usw.
        PHP hat hier mit der OOP die Realität wohl besonders gut abgebildet - in unserer Welt sind ja auch manche "gleicher als gleich" :-D
        Ihr habt ein Torturial durchgearbeitet, das auf den mysql_-Funktionen aufbaut?
        Schön - etwas Geschichte kann ja nicht schaden.
        Aber jetzt seht euch bitte php.net/pdo oder php.net/mysqli bevor ihr beginnt!

        Kommentar


        • #5
          Zitat von jack88 Beitrag anzeigen
          (...)
          Nun möchte ich von einer MySQL-Klasse eine Singleton-Klasse ... ableiten ...
          Darf man fragen warum?
          Klingon function calls do not have “parameters”‒they have “arguments”‒and they always win them!

          Kommentar


          • #6
            Klar, man darf ;-)

            Es geht wie gesagt nur ums Verständnis. Bin gerade dabei ein Buch über Design-Patterns zu lesen und wollte die einzelnen Patterns nun anhand eigener Beispiele ausprobieren. (Ich weiß auch das vor allem das Singleton-Pattern sehr umstritten ist - aber ich möchte es trotzdem ausprobieren dürfen)

            Es wird ja oft geschrieben, daß das Singleton-Pattern z.B. in DB-KLassen Verwendung findet, also habe ich es einfach mit einer eigenen Leeren-DB-Klasse probiert und dann mit der MySQLi. Dabei bin ich auf das komische Verhalten mit der Sichtbarkeit des Konstruktors aufmerksam geworden und das lässt mir jetzt keine Ruhe mehr, weil ich das einfach gerne verstehen würde.

            Kommentar


            • #7
              Es wird ja oft geschrieben, daß das Singleton-Pattern z.B. in DB-KLassen Verwendung findet,
              Mit Gewehren werden oft Leute erschossen.
              Sind Gewehre deshalb gut?
              Wir werden alle sterben

              Kommentar


              • #8
                In Autos sterben oft Menschen.
                Sind Autos deshalb schlecht?
                Ihr habt ein Torturial durchgearbeitet, das auf den mysql_-Funktionen aufbaut?
                Schön - etwas Geschichte kann ja nicht schaden.
                Aber jetzt seht euch bitte php.net/pdo oder php.net/mysqli bevor ihr beginnt!

                Kommentar


                • #9
                  Wie gesagt, es geht mir in erster Line um das Thema "Sichtbarkeit in Unterklassen" und speziell um die die Frage wieso die Sichtbarkeit des Konstruktors bei einer von MySQLi abgeleiteten Klasse eingeschränkt werden kann?
                  Zuletzt geändert von jack88; 28.03.2012, 08:30.

                  Kommentar


                  • #10
                    Die Antwort ist einfach: Weil PHP das erlaubt!
                    Bzw. Weil die Entwickler das so vorgesehen haben.

                    Oder anders rum: Die MySQLi Klasse ist eine eingebaute, in C geschriebene Klasse, und diese verhalten sich nicht in allen Beziehungen wie in PHP geschriebene Klassen.

                    Wenn du meinst, dass das Verhalten falsch ist, dann wäre das evtl. ein Fall für einen Bugreport.
                    Es könnte allerdings schon einen Bugreport zu dem Thema existieren.
                    Dort würdest du Aufklärung finden, ob das geändert wird, oder auch warum nicht.
                    Zuletzt geändert von combie; 28.03.2012, 12:50.
                    Wir werden alle sterben

                    Kommentar


                    • #11
                      Wie schon festgestellt wirst du das auf diesen Ansatz nicht gelöst bekommen. Wäre in deinem Fall vielleicht das Singleton Factory Pattern etwas ?

                      Habe auf die schnelle nur eine Java Resource gefunden, sollte aber verständlich sein ( Singleton Factory patterns example - WikiJava, singleton, factory, unique instance )

                      Sprich du hältst in der Singleton Klasse eine Instanz von der "Basisklasse". Die Singleton Klasse erbt nicht mehr von der Basisklasse.
                      hostbar - Full Service Internet Agentur

                      Kommentar


                      • #12
                        Laut dem Buch „Objektorientierte Programmierung“ sollten sich Unterklassen wie folgt verhalten:

                        Eine Unterklasse kann die Vorbedingungen für eine Operation, die durch die Oberklasse definiert wird, einhalten oder abschwächen. Sie darf die Vorbedingungen aber nicht verschärfen.
                        Das bedeutet, daß man in der Unterklasse einen Kontruktor nicht mit protected oder private überschreiben darf, wenn dieser in der Basisklasse als public deklariert wurde – so sind offensichtlich die OOP Regeln. Wie man anhand der Beispiele ganz am Anfang sehen kann, ist dieses Verhalten auch in PHP implementiert.

                        @combie
                        Dieses Verhalten kann von den MySQLi-Entwicklern nicht gewollt sein, es wäre schlicht unlogisch in OOP zu programmieren und gleichzeitig bewusst die OOP-Regeln zu verletzen – dann könnte man genauso gut gleich prozedural programmieren.

                        Offensichtlich handelt es sich hierbei tatsächlich um einen kleinen Bug.

                        Nun denke ich, daß ich eine plausible Erklärung für das „komische“ Verhalten der MySQLi-Klasse gefunden habe.
                        Vor der Einführung der magischen Methode __construct() musste der Konstruktor bekanntlich immer den gleichen Namen wie die Klasse haben. Aus Gründen der Rückwertskompatibilität wurde dieses Verhalten bis jetzt so beibehalten. D.h. PHP unterstütz aktuell parallel zwei verschiedene Konstruktor-Arten.

                        Ein Beispiel:

                        PHP-Code:
                        class Alt {
                              public function 
                        Alt() {
                                  echo 
                        "ich bin ein alter Konstruktor!<br>";
                              }
                        }
                           
                          class 
                        Neu {
                              public function 
                        __construct() {
                                  echo 
                        "ich bin ein neuer Kostruktor!<br>";
                              }
                          }
                           
                          
                        $alt = new Alt();
                          
                        $neu = new Neu(); 
                        Jetzt wird es interessant, denn genau das passiert auch in der MySQLi-Klasse:

                        PHP-Code:
                        class TestAlt extends Alt {
                               protected static 
                        $instance null;
                              
                              protected function 
                        __construct() {
                                  echo 
                        "darf eigentlich nicht sein, aber ES GEHT TROTZDEM!<br>";
                              }
                              
                              public static function 
                        getInstance()
                              {
                                if(
                        self::$instance == null)
                                {
                                   
                        self::$instance = new TestAlt();
                                }
                                return 
                        self::$instance;
                              }
                          }
                           
                          
                        $test_alt TestAlt::getInstance(); 
                        Wie man an dem Beispiel schön erkennen kann habe ich es geschafft die Basisklasse „auszutricksen“ und in der Unterklasse TestAlt den public-Konstruktor der Basisklasse mit protected zu überschreiben. Das war nur möglich, weil die Basisklasse eine andere Kostruktor-Methode (Alt()) benutzt als die Unterklasse(__construct()) - es geht übrigens auch umgekehrt! Wenn wir diese „Erkenntnis“ nun auf die MySQLi Klasse übertragen, dann bedeutet das, daß in MySQLi der Konstruktor einfach in der veralteten Form implementiert ist:

                        // MySQLi Konstruktor
                        public function MySQLi(….)

                        implementiert ist und das läßt sich auch mit dem folgenden Beispiel leicht belegen:

                        PHP-Code:
                        class MysqlSingle extends MySQLi
                            
                        {
                              private static 
                        $instance null;
                           
                              protected function 
                        MySQLi($server$user$password$database)
                              {
                                
                        parent::__construct($server$user$password$database);
                           
                              }
                           
                               public static function 
                        getInstance($server$user$password$database)
                              {
                                if(
                        self::$instance == null)
                                {
                                   
                        self::$instance = new MysqlSingle($server$user$password$database);
                                }
                                return 
                        self::$instance;
                              }
                           
                              private final function 
                        __clone()
                              {
                              }
                            }
                            
                            
                            
                        $db MySqlSingle::getInstance('localhost''user''pass''db'); 
                        Wie man sieht führt der Code diesmal zu einem Access level error.

                        Aufgrund der Zwei-Konstruktoren-Implementiereung in PHP ist es somit offensichtlich möglich mit diesem kleinen Trick die Sichtbarkeit eines Konstruktors in Unterklasse entgegen den OOP-Regeln auch einzuschränken. Die einzige Möglichkeit um dieses Verhalten zu unterbinden ist offenbar immer ZWEI Konstruktoren in der Basisklasse zu verwenden – schön kurios wenn Ihr mich fragt!

                        PHP-Code:
                          class SuperKonstruktor {
                              public function 
                        SuperKonstruktor() {
                                  echo 
                        "ich bin der alte  Konstruktor und werde nur gebraucht ";
                                  echo 
                        "damit man die Sichtbarkeit des Konstruktors in einer Unterklasse nicht einschränken kann!<br>";
                              }
                           
                              public function 
                        __construct() {
                                  echo 
                        "ich bin ein neuer Konstruktor und bleibe auch in Unterklassen immer PUBLIC!<br>";
                              }
                          }
                           
                          
                        $super = new SuperKonstruktor(); 
                        Welche negativen Folgen dieses Verhalten haben kann, weiß ich nicht einzuschätzen, aber eine saubere Implementierung sieht für mich anders aus.

                        jack
                        Zuletzt geändert von jack88; 28.03.2012, 16:06.

                        Kommentar


                        • #13
                          Zitat von jack88 Beitrag anzeigen
                          Welche negativen Folgen dieses Verhalten haben kann, weiß ich nicht einzuschätzen, aber eine saubere Implementierung sieht für mich anders aus.
                          Es ist PHP. Du stehst gerade im McDonalds und philosophierst über 5-Sterne-Küche.

                          Kommentar


                          • #14
                            Zitat von h3ll Beitrag anzeigen
                            Es ist PHP. Du stehst gerade im McDonalds und philosophierst über 5-Sterne-Küche.
                            Nein - PHP ist noch viel cooler - wenn du was trinken willst, kannst du einfach einen Hamburger nehmen - in dem Moment an dem du davon trinken willst, wird der Hamburger sich in eine Cola verwandeln!
                            Ihr habt ein Torturial durchgearbeitet, das auf den mysql_-Funktionen aufbaut?
                            Schön - etwas Geschichte kann ja nicht schaden.
                            Aber jetzt seht euch bitte php.net/pdo oder php.net/mysqli bevor ihr beginnt!

                            Kommentar


                            • #15
                              Die Idee mit getIstnace() in der Basisklasse ist nicht schlecht, allerdings finde ich es schon eleganter, wenn es auch ohne Änderungen an der Basisklasse ginge.
                              Dann leite doch deine zwei Klassen von einer gemeinsamen (notfalls abstrakten) Basisklasse ab:

                              PHP-Code:

                              class mom {
                                  protected function 
                              __construct() {
                                      
                              printf('mom here');
                                  }
                              }

                              // this could be your mysql class
                              class child extends mom {
                                  function 
                              __construct() {
                                      
                              printf('(new child)');
                                  }
                              }

                              // this could be your mysql singleton class
                              class kid extends mom {
                                  protected function 
                              __construct() {
                                      
                              printf('(new kid)');
                                  }

                                  static function 
                              make_baby() {
                                      return new 
                              self();
                                  }
                              }

                              $child = new child(); // works

                              $kid kid::make_baby(); // works

                              $error = new kid(); // raises an error 

                              Zitat von jack88 Beitrag anzeigen
                              Wie gesagt, es geht mir in erster Line um das Thema "Sichtbarkeit in Unterklassen" ...
                              Ich hatte keineswegs vor, deinen Thread zu torpedieren. Ich wollte nur einen Denkanstoß geben. Eine Datenbank-Verbindung als Singleton zu bauen, ergibt in meinen Augen wenig Sinn. Ein Singleton regelt den Zugriff auf eine exklusive Ressource so, dass immer nur ein Client darauf zugreifen kann. Bei Datenbanken ist dafür schon der Datenbank-Server-Daemon zuständig. Von den Verbindungsobjekten im Script sollte man dagegen mehrere erzeugen und auch nutzen können.

                              Und ohne deine Singleton-Implementierung hättest du das Problem mit der Vererbungshierarchie erst gar nicht.

                              Statt "Hier ist mein Pattern, jetzt wende ich das auf das erstbeste Problem an, dass sich findet.", ist es besser zu versuchen, konkret auftretende Probleme so zu verallgemeinern, bis sich ein passendes (Software-)Pattern zur Lösung findet.

                              ... und speziell um die die Frage wieso die Sichtbarkeit des Konstruktors bei einer von MySQLi abgeleiteten Klasse eingeschränkt werden kann?
                              ...? (Antwort: Weil er's kann.)

                              Was in der Zend-Engine intern vorgeht, muss nichts mit Buchweisheiten über OOP zu tun haben. Schlimmstenfalls ist das ein Fehler in der Dokumentation (wenn es dort nirgendwo erwähnt wurde.). Das muss man akzeptieren oder sich eine andere Spielwiese suchen. Es gibt schließlich genügend Alternativen mit vernünftig(er)en OOP-Implementierungen.

                              Zitat von jack88 Beitrag anzeigen
                              Laut dem Buch „Objektorientierte Programmierung“ ...
                              Ich halte, ehrlich gesagt, nicht sooo viel von Galileo-Books, obwohl ich welche (gedruckt) im Regal stehen habe. Man findet (früher oder später) immer wieder handwerkliche Schwächen, wenn man sich mit ihnen beschäftigt.

                              ... sollten sich Unterklassen wie folgt verhalten:
                              Eine Unterklasse kann die Vorbedingungen für eine Operation, die durch die Oberklasse definiert wird, einhalten oder abschwächen. Sie darf die Vorbedingungen aber nicht verschärfen.
                              Diese Bedingung soll sicherstellen, dass ein Objekt einer abgeleiteten Klasse sich nicht restriktiver verhält als das der Basisklasse. Damit wird gewährleistet, dass ein Objekt einer abgeleiteten Klasse mindestens das gleiche kann, wie das der Basisklasse.

                              Konstruktoren (in PHP) gehören aber nicht direkt zur Schnittstelle eines Objektes, sondern zur Klasse. Sie werden (normalerweise) nur über den Operator new aufgerufen.

                              Wenn du jetzt eine praktisch vorkommende Situation beschreiben kannst, in der Objekte sowohl von MySQLi als auch von einer davon abgeleiteten Klasse (mit zugriffsgeschütztem Konstruktor) erzeugt werden müssen und die Klassen dieser Objekte beliebig austauschbar sein sollen, dann sehe ich das auch als Bug an.

                              ... Das bedeutet, daß man in der Unterklasse einen Konstruktor nicht mit protected oder private überschreiben darf, wenn dieser in der Basisklasse als public deklariert wurde – so sind offensichtlich die OOP Regeln.
                              Wie PHP OOP-Regeln interpretiert, bestimmt Rasmus Lerdorf (oder irgendwelche anderen derzeit aktuellen PHP-Core-Developer).

                              Wie man anhand der Beispiele ganz am Anfang sehen kann, ist dieses Verhalten auch in PHP implementiert.
                              Und wie dir Quetschi und combie schon erklärt haben, gelten für in PHP eingebaute Klassen eben manchmal andere Regeln als für gewöhnliche, vom Anwender deklarierte, Klassen.

                              @combie
                              Dieses Verhalten kann von den MySQLi-Entwicklern nicht gewollt sein, es wäre schlicht unlogisch in OOP zu programmieren und gleichzeitig bewusst die OOP-Regeln zu verletzen – dann könnte man genauso gut gleich prozedural programmieren.
                              Diese Schlussfolgerung ist unlogisch, was hat imperative ("prozedurale") Programmierung hiermit zu tun? Nichts, bis auf die Tatsache, dass deine Kenntnisse darin anscheinend ausbaufähig sind und du sie deswegen geringschätzt ...

                              ...
                              Vor der Einführung der magischen Methode __construct() musste der Konstruktor bekanntlich immer den gleichen Namen wie die Klasse haben. Aus Gründen der Rückwertskompatibilität wurde dieses Verhalten bis jetzt so beibehalten. D.h. PHP unterstütz aktuell parallel zwei verschiedene Konstruktor-Arten.
                              Schon seit Juli 2010 nicht mehr.
                              Zuletzt geändert von fireweasel; 28.03.2012, 22:56. Grund: changelog 5.3.3 verlinkt
                              Klingon function calls do not have “parameters”‒they have “arguments”‒and they always win them!

                              Kommentar

                              Lädt...
                              X