Class::Accessor
Struppi
- perl
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.
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
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.
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).
term_id
user_id
term_begin_date
term_end_date
term_alert_date
term_expiry_date
term_warn_once
und
user_id
user_name
user_family_name
user_email
Dann folgende drei Klassen:
new( user_id )
create( %user_properties )
delete()
#inherited getData(@fields)
#inherited setData(%props)
addTermin(%props)
selectTermin(%conditions)
deleteTermin(%conditions)
copyAllEntriesToUser( user_obj )
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:
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
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.