Struppi: Class::Accessor

Hallo alle

ich versuch's auch mal hier. Ich hab ein etwas kniffliges Perl Problem. Und zwar nutze ich das Modul Class::Accessor um ein Objekt zu erzeugen auf das ich mit Funktionen auf die Werte zugreife.

eigentlich klappt das auch, aber im Prinzip nur einmal, ich will aber zur Laufzeit Objekte mit unterschiedlichen Feldern erzeugen, doch das geht so nicht. Hier ein beispiel:

#!/usr/perl/bin -w
use strict;

package Foo;
use base qw(Class::Accessor);

use Carp;
sub new
{
    my $proto = shift;
    my $fields = shift || croak("Kein parameter");

my $class = ref($proto) || $proto;
    my $new = bless {
    }, $class;
    Foo->mk_accessors($new, @$fields);

return $new;
}
package main;

my $t = new Foo([qw/a b c/]);
my $t2 = new Foo( [qw/d e f/] );

$t2->b(22);

Eingentlich soll hier ein Fehler erzeugt werden, da in $t2 das feld b nihct existieren soll.

Soweit ich jetzt dahinter steige ist das Problem, diese Zeile:
    Foo->mk_accessors($new, @$fields);

Hier muss statt 'Foo' eigentlich $new stehen, was aber nicht geht, da $new natürlich nicht diese funktion 'mk_accessors()' kennt.

Jetzt weiß ich nicht mehr weiter. Ich denke mal, das ich noch eine Klasse definieren muss, aber mir ist nicht klar wie.

Struppi.

  1. Halihallo Struppi

    Eingentlich soll hier ein Fehler erzeugt werden, da in $t2 das feld b nihct existieren soll.

    Naja, naja... :-)

    Kurz zum Modul:

    Variablen von Instanzen (Objekte) lassen sich nicht wie in anderen
    Programmiersprachen über $a->c, a.c oder a->c ansprechen, deswegen
    gibt es ja überhaupt dieses Modul. Das einzige was dieser Notation
    am nächsten kommt ist ein Methodenaufruf: $a->c oder $a->c().

    Das Class::Accessor Modul arbeitet nun wie folgt:
    Eine Methode ist stets Modulbezogen. Modulbezogene Eigenschaften wie
    Modulvariablen (use vars qw() oder our) oder Methoden werden in der
    sogenannten Modulsymboltabelle referenziert. Was Class::Accessor nun
    macht, ist für jede mk_accessor übergebenen Variablennamen eine
    entsprechende Methode anzulegen (und dies eben auf das Modul oder
    das *package* bezogen, nicht auf Instanz-/Objektebene).

    Das sieht dann etwa wie folgt aus:

    my $method = sub { return $_[0]->{variablenname} }
    *{$class."::$field"} = $method
       unless defined &{$class."::$field"};

    Nun wird über $class->$method $self->{variablenname} zurückgegeben.

    Soweit ich jetzt dahinter steige ist das Problem, diese Zeile:
        Foo->mk_accessors($new, @$fields);
    Hier muss statt 'Foo' eigentlich $new stehen, was aber nicht geht, da $new natürlich nicht diese funktion 'mk_accessors()' kennt.

    Das Problem sitzt noch tiefer ;)
    Wenn du dein Vorhaben weiter verfolgen möchtest, gibt es nur folgende
    Möglichkeit:

    Du speicherst alle *Instanzen/Objekt-Variablen*, die die Instanz
    (z.B. $t2) kennt in einem Array/Hash und definierst eigene set/get
    Methoden (s. Doku), die testen, ob auf das Feld zugegriffen werden
    darf. Wenn nicht, ein croak() oder die() ggf. ein warn() wie immer du
    wünschst.
    Natürlich könntest du dies auch über AUTOLOAD erreichen, aber das
    ändert nichts am Sachverhalt (ist eben auch eine Methode und somit
    in der Symboltabelle definiert, welche eben auf Packetebene
    arbeitet). Oder du müsstest leider einfach alle benötigten Variablen
    als Methoden exportieren, ob sie nun existieren oder nicht...

    Oder aber, du musst für jeden Objekttyp eine eigene Klasse schreiben,
    was ja normalerweise auch die richtige und bei vielen
    Programmiersprachen die einzige Vorgehensweise ist.

    Viele Grüsse

    Philipp

    1. Hi,

      Kurz zum Modul:

      ich hatte mir das schon sehr genau angeschaut, da ich mit Problem schon länger kämpfe. Es ist ja glücklicherweise pures Perl.

      my $method = sub { return $_[0]->{variablenname} }
      *{$class."::$field"} = $method
         unless defined &{$class."::$field"};

      Nun wird über $class->$method $self->{variablenname} zurückgegeben.

      Das hätte ich halt gern Variabler gehabt.

      Soweit ich jetzt dahinter steige ist das Problem, diese Zeile:
          Foo->mk_accessors($new, @$fields);
      Hier muss statt 'Foo' eigentlich $new stehen, was aber nicht geht, da $new natürlich nicht diese funktion 'mk_accessors()' kennt.

      Das Problem sitzt noch tiefer ;)
      Wenn du dein Vorhaben weiter verfolgen möchtest, gibt es nur folgende
      Möglichkeit:

      Du speicherst alle *Instanzen/Objekt-Variablen*, die die Instanz
      (z.B. $t2) kennt in einem Array/Hash und definierst eigene set/get
      Methoden (s. Doku), die testen, ob auf das Feld zugegriffen werden
      darf. Wenn nicht, ein croak() oder die() ggf. ein warn() wie immer du
      wünschst.

      wie hesagt ich möchte zur Laufzeit mit variablen Feldern arbeiten.
      Das heißt ich weiss vorher nicht welche Felder existieren.

      Natürlich könntest du dies auch über AUTOLOAD erreichen, aber das
      ändert nichts am Sachverhalt (ist eben auch eine Methode und somit
      in der Symboltabelle definiert, welche eben auf Packetebene
      arbeitet). Oder du müsstest leider einfach alle benötigten Variablen
      als Methoden exportieren, ob sie nun existieren oder nicht...

      Das ist genauso wie ich es bisher mache und es funktioniert auch einwandfrei, aber AUTLOAD ist extrem langsam.

      Oder aber, du musst für jeden Objekttyp eine eigene Klasse schreiben,
      was ja normalerweise auch die richtige und bei vielen
      Programmiersprachen die einzige Vorgehensweise ist.

      Wahrscheinlich hast du recht. Ich fand es nur extrem lästig, dass ich für jedes (Daten)Objekt alles nochmal schreiben muss.

      Es ist ja so, dass man ein Objekt einerseits nutzt um die Daten zu verwalten und anderseits brauche ich es meistens zum speichern in einer DB.

      Also hatte ich mir überlegt (und wie gesagt auch mit AUTLOAD umgesetzt) ich schreib ein Objekt was einerseits alle Datenfelder als Methoden zu Verfügung stellt, das hat den Vorteil, dass ich nicht versuche auf Felder zuzugreifen, die nicht existieren und ich habe die Möglichkeit diese im Bedarfsfalle mit eigenen Methoden zu überschreiben. Zusätzlich habe ich jeweils eine Funktion eingebaut die mir die Daten als HASH zurückgibt und eine die die geänderten Felder zurückgibt, so dass ich z.b. beim schreiben in die Datenbank nur die geänderten Felder speichern kann.

      Mein Ziel mit Class::Accessor war halt das langsame AUTLOAD zu vermeiden und ich hatte die wage Hoffnung, dass es irgendwie gehen muss die Methode, die Class::Accessor benutzt  evtl. dafür zu benutzen aber bisher waren alle meine Versuche gescheitert, da ich dieses Foo in der Zeile "Foo->mk_accessors($new, @$fields);" nicht so hingebogen bekomme, dass es ein zur Laufzeit entstandenes Objekt beinhaltet.

      Struppi.

      1. Halihallo Struppi

        Natürlich könntest du dies auch über AUTOLOAD erreichen, aber das
        ändert nichts am Sachverhalt (ist eben auch eine Methode und somit
        in der Symboltabelle definiert, welche eben auf Packetebene
        arbeitet). Oder du müsstest leider einfach alle benötigten Variablen
        als Methoden exportieren, ob sie nun existieren oder nicht...

        Das ist genauso wie ich es bisher mache und es funktioniert auch einwandfrei, aber AUTLOAD ist extrem langsam.

        Ich glaube, dass dir die Lösung über AUTOLOAD einfach nicht so gut
        gefällt :-)  und ich verstehe das (AUTOLOAD ist böse :-)).

        Oder aber, du musst für jeden Objekttyp eine eigene Klasse schreiben,
        was ja normalerweise auch die richtige und bei vielen
        Programmiersprachen die einzige Vorgehensweise ist.

        Wahrscheinlich hast du recht. Ich fand es nur extrem lästig, dass ich für jedes (Daten)Objekt alles nochmal schreiben muss.

        Mir kommt da grad ein guter Satz in den Sinn:
        <cite>
           Wo kein Bedürfnis ist, kann man eines schaffen
        </cite>

        Mit anderen Worten: Wenn es zu lästig ist, für jeden Datentyp (z.B.
        Tabelle in einer Datenbank) ein eigenes Modul anzufertigen, dann
        musst du dir einfach die Vorteile einreden und dich des Resultats
        ergötzen :-)

        Wäre z.B. folgendes nicht sehr schön (zwar aufwendig, aber schön):?

        Terminkalenderanwendung (Mehrere Benutzer haben mehrere Termine).

        Table termin

        term_id
        user_id
        term_begin_date
        term_end_date
        term_alert_date
        term_expiry_date
        term_warn_once

        und

        Table user

        user_id
        user_name
        user_family_name
        user_email

        Dann folgende drei Klassen:

        Klasse User.pm  (extends DataAbstraction.pm)

        new(  user_id  )
        create( %user_properties )
        delete()
        #inherited getData(@fields)
        #inherited setData(%props)

        addTermin(%props)
        selectTermin(%conditions)
        deleteTermin(%conditions)
        copyAllEntriesToUser( user_obj )

        Klasse Termin.pm  (extends DataAbstraction.pm)

        new(  user_obj, term_id  )
        create( user_obj, %termin_properties )
        delete()
        #inherited getData(@fields)
        #inherited setData(%props)

        getUser()
        copyToOtherUser( user_obj )
        moveToOtherUser( user_obj )

        und zum Schluss:

        Klasse DataAbstraction.pm

        new( table, primary_key )
        create(%props)
        delete()
        setData(%props)
        getData(@fields)

        -------

        DataAbstraction.pm übernimmt die ganze Datenhaltung, erstellt, löscht
        und liest/schreibt alle Daten aus der Datenbank. "Darübergestülpt"
        sind alle spezifischen Datentypen, wie z.B. Termin und User. Diese
        haben nun fixe Felder und diese können bereits zur Compile-Time ganz
        am Anfang festgelegt werden.
        Was ich vorher mit "wo kein Bedüfnis ist, kann man sich eines
        schaffen" meinte ist, dass jetzt nicht nur auf die Daten über
        Class::Accessor zugegriffen werden kann, sondern auch die ganze
        Funktionalität (neuer User zufügen, neuen Termin anhängen, Termine
        selektieren etc.) in diesen spezifischen Datentypklassen abgelegt
        ist. _Das_ halte ich für sehr ästhetisch und dann nehme ich auch
        gerne den Mehraufwand in Kauf, für alle Datentypen (User, Termin)
        die Felder explizit anzugeben und für jeden Datentypen eine Klasse
        anzulegen.

        Na, keine Ahnung, vielleicht kannst du dich genauso gut selber
        täuschen wie ich mich. Vielleicht lässt sich dies jedoch auch nicht
        auf deine Aufgabenstellung anwenden... Egal :-)

        Also hatte ich mir überlegt (und wie gesagt auch mit AUTLOAD umgesetzt) ich schreib ein Objekt was einerseits alle Datenfelder als Methoden zu Verfügung stellt, das hat den Vorteil, dass ich nicht versuche auf Felder zuzugreifen, die nicht existieren und ich habe die Möglichkeit diese im Bedarfsfalle mit eigenen Methoden zu überschreiben. Zusätzlich habe ich jeweils eine Funktion eingebaut die mir die Daten als HASH zurückgibt und eine die die geänderten Felder zurückgibt, so dass ich z.b. beim schreiben in die Datenbank nur die geänderten Felder speichern kann.

        Naja, also entweder du verfolgst etwas ähnliches wie ich oben
        beschrieben habe, oder du verwendest AUTOLOAD (oder beides zusammen,
        dann liessen sich alle Felder über DataAbstraction.pm auslesen und
        ggf. über die Datentypklassen (Termin/User) erweitern). Das sind für
        mich die einzigen (guten) Möglichkeiten, die ich sehe.

        Mein Ziel mit Class::Accessor war halt das langsame AUTLOAD zu vermeiden und ich hatte die wage Hoffnung, dass es irgendwie gehen muss die Methode, die Class::Accessor benutzt  evtl. dafür zu benutzen aber bisher waren alle meine Versuche gescheitert, da ich dieses Foo in der Zeile "Foo->mk_accessors($new, @$fields);" nicht so hingebogen bekomme, dass es ein zur Laufzeit entstandenes Objekt beinhaltet.

        Ich versuche es nochmals:
        Class::Accessor - auch wenn es nicht so aussieht - arbeitet immer
        auf Klassen-/Packetebene, das hat perlinterne Gründe und diese kannst
        du nicht einfach so in den Wind schlagen; da Class::Accessor mit der
        Methoden in der Symboltabelle arbeitet und damit im Kontext
        des Packetes arbeitet und nicht im Kontext einer Instanz einer
        Klasse (Objekt).

        Es gibt nur eine Möglichkeit, eine Methode im "Objekt-Kontext" zu
        definieren und diese wird dich wohl kaum glücklich machen...:

        $a = new Whatever;
        $a->{field} = 15;
        $a->{_field} = sub {
           return $a->{field};
        };

        und dann:

        print $a->{_field}->();

        $a->{_field}->() ist nun eine Methode, die nur und wirklich nur in
        dieser *einen* Instanz von Whatever existiert (naja, es ist keine
        Methode mehr, aber das, was einer "Instanz-Methode" bei Perl am
        nächsten kommt).

        Hm, schade wa? - Finde ich eigentlich auch... Obwohl, naja, es ist
        sauberer so wie es ist. Instanzmethoden gibt es nicht und somit auch
        die Möglichkeit nicht, in Perl $a->field variabel zu gestalten (es
        sei denn mit den bereits genannten "Tricks" [AUTOLOAD])

        Viele Grüsse

        Philipp

        1. Erstmal danke, Phillip für die langen Erläuterungen. Bei (für einen selber) schwierigen Problemen hilft's doch oft wenn man mal darüber geredet hat ;-)

          Ich glaube, dass dir die Lösung über AUTOLOAD einfach nicht so gut
          gefällt :-)  und ich verstehe das (AUTOLOAD ist böse :-)).

          Als Resumee, scheint's aber letztlich die Lösung für mich zu sein.

          Und je länger ich jetzt damit arbeite umso besser gefällt es mir.
          So in etwa sieht es momentan aus:
          http://www.uni-mainz.de/~struebig/test/myObj.txt

          Es hilft mir beim meiner Schludrigkeit Fehler zu vermeiden, ich krieg immer schön eine Fehlermeldung, wenn ich versuche ein nicht definiertes Feld zu schreiben oder lesen.

          Mit anderen Worten: Wenn es zu lästig ist, für jeden Datentyp (z.B.
          Tabelle in einer Datenbank) ein eigenes Modul anzufertigen, dann
          musst du dir einfach die Vorteile einreden und dich des Resultats
          ergötzen :-)

          so sieht's aus ;-)

          Wäre z.B. folgendes nicht sehr schön (zwar aufwendig, aber schön):?

          wie gesagt ich bin relativ schludrig, d.h. ich baue gerne in der Entwicklungsphase DB Objekte um, füge Felder ein und so weiter. Insofern wäre schön zwar schön, ist aber leider nicht mein Stil.

          DataAbstraction.pm übernimmt die ganze Datenhaltung, erstellt, löscht
          und liest/schreibt alle Daten aus der Datenbank. "Darübergestülpt"
          sind alle spezifischen Datentypen, wie z.B. Termin und User. Diese
          haben nun fixe Felder und diese können bereits zur Compile-Time ganz
          am Anfang festgelegt werden.
          Was ich vorher mit "wo kein Bedüfnis ist, kann man sich eines
          schaffen" meinte ist, dass jetzt nicht nur auf die Daten über
          Class::Accessor zugegriffen werden kann, sondern auch die ganze
          Funktionalität (neuer User zufügen, neuen Termin anhängen, Termine
          selektieren etc.) in diesen spezifischen Datentypklassen abgelegt
          ist. _Das_ halte ich für sehr ästhetisch und dann nehme ich auch
          gerne den Mehraufwand in Kauf, für alle Datentypen (User, Termin)
          die Felder explizit anzugeben und für jeden Datentypen eine Klasse
          anzulegen.

          Im grossen und ganzen mach ich es auch so, bis auf die von mir gewünschte Flexibilität von DataAbstraction und den immer wieder benötigten Funktionen.

          Ich versuche es nochmals:
          Class::Accessor - auch wenn es nicht so aussieht - arbeitet immer
          auf Klassen-/Packetebene, das hat perlinterne Gründe und diese kannst
          du nicht einfach so in den Wind schlagen; da Class::Accessor mit der
          Methoden in der Symboltabelle arbeitet und damit im Kontext
          des Packetes arbeitet und nicht im Kontext einer Instanz einer
          Klasse (Objekt).

          Es gibt nur eine Möglichkeit, eine Methode im "Objekt-Kontext" zu
          definieren und diese wird dich wohl kaum glücklich machen...:

          also kein Wunder dass ich es nicht geschafft hatte, aber so hab wenigstens einen kleinen Einblick in die tiefen von Perl bekommen. Vor allem habe ich dadurch - zumindest ein bisschen - closures verstanden.

          Hm, schade wa? - Finde ich eigentlich auch... Obwohl, naja, es ist
          sauberer so wie es ist. Instanzmethoden gibt es nicht und somit auch
          die Möglichkeit nicht, in Perl $a->field variabel zu gestalten (es
          sei denn mit den bereits genannten "Tricks" [AUTOLOAD])

          Im Prinzip ist AUTLOAD ja auch ok, ich muss halt nur aufpassen, dass ich das Objekt nicht für Daten benutzte die in grosser Zahl verwendet werden.
          Aber selbst unter ungünstigen Umstände wo z.b. ein Objekt 100 mal vorhanden ist und jeweils 10 Eigenschaften abgefragt sollte der Zeitverlust vertretbar sein.

          Struppi.