Muster - die einsame [KB]ruecke
hkl
- perl
0 Hamstar0 Sven Rautenberg0 Hamstar0 hkl0 Sven Rautenberg0 hkl
( singleton bridge )
Hallo !
Unter Perl gibt es ja die Moeglichkeit auf Funktoinen in einem Modul mittels <PAKETNAME>::<Fumktionsname> zuzugreifen. Wenn man das Paket aber objektorientiert nutzen will, ist das ja nicht ganz unproblmatisch, da es sein koennte dass der Konstruktor noch gar nicht gerufen wurde.
Das kann wie hier gezeigt mittels einer Praeambel
sub tmpdir {
my $self = shift;
$self = __PACKAGE__->instance
unless ref $self eq __PACKAGE__;
#...
}
im Funktionskoerper abgefangen werden, aber es laesst sich vielleicht auch semantisch unterdruecken* :
Hab mal ein Singleton entworfen das das Problem loesen soll:
use strict;
package Log::LogImpl;
if ( defined __PACKAGE__ and __PACKAGE__ eq 'Log' )
{
sub log {
my $class = shift;
my $msg = shift;
print scalar(localtime(time)) . " " . "$msg\n";
}
}
package Log;
my $_self = undef;
my %attrs = ( foo => 'bar'
);
sub _getInstance {
$_self ||= do {
# Dies vermeidet einen Aufruf
# wie Log::log
@Log::ISA = 'Log::LogImpl';
bless { %attrs }, 'Log';
};
}
1;
Wenn jetzt ein Client versucht die Methode mittels "Log::log" aufzurufen, wuerde das nicht klappen :
#! /usr/bin/perl -w -I ./modules
use strict;
use Log;
my $log = Log::_getInstance()
or die 'Could not get instance for log' . "\n" ;
eval ("Log::log('Logged in class scope...')")
and die '...by accident ! Terminating' . "\n"
or print 'Method "log" not in class scope...' . "\n";
$log->log('Logged correctly')
and print '...but in object scope, as intended !' . "\n" ;
Das Ganze ist eigentlich ein degeneriertes Brueckenmuster; degeneriert, da hier eben grade nicht die Implementierung aggregiert sondern inkludiert wird, was dem Konzept loser Koppelung, das die "GoF" thematisiert, wiederspricht.
Ausserdem handelt es sich um keine Implemetierungs-"Klasse" (kein bless) sondern um ein einfaches Paket.
Trotzdem find ich das recht praktisch.
Habe noch nicht soviel Erfahrung mit Perl - hier einige Fragen an Euch Wizards:
Ist das Eurer Meinung nach
?
Ueber Feedback wuerde ich mich sehr freuen
Danke fuer's Lesen
Gruss && schones Wochenende
Holger
*) Ich weiss dass meine Loesung einen Compiler-Fehler provoziert, die Praeambel-Loesung nicht. Genau damit will ich aber eine Aufrufkonverntion erzwingen.
Unter Perl gibt es ja die Moeglichkeit auf Funktoinen in einem Modul mittels <PAKETNAME>::<Fumktionsname> zuzugreifen. Wenn man das Paket aber objektorientiert nutzen will, ist das ja nicht ganz unproblmatisch, da es sein koennte dass der Konstruktor noch gar nicht gerufen wurde.
Kannst Du da noch mal ein paar Sachen zu schreiben? Liest sich für mich ganz merkwürdig...
Hallo !
Unter Perl gibt es ja die Moeglichkeit auf Funktoinen in einem Modul mittels <PAKETNAME>::<Fumktionsname> zuzugreifen. Wenn man das Paket aber objektorientiert nutzen will, ist das ja nicht ganz unproblmatisch, da es sein koennte dass der Konstruktor noch gar nicht gerufen wurde.
Kannst Du da noch mal ein paar Sachen zu schreiben? Liest sich für mich ganz merkwürdig...
So in etwa :
package Naive_Log;
use strict;
my $resource = undef;
sub new() {
my $class = shift;
$resource = { foo => 'bar'};
my $self = {};
bless $self, $class;
}
sub do_it() {
$resource->{foo};
}
1;
#! /usr/bin/perl -w -I ./modules
use strict;
use Naive_Log;
print Naive_Log::do_it() . "\n";
Gruss
Holger
1;
EOF Naive_Log.pm
#! /usr/bin/perl -w -I ./modules
Naive_LogClient.pl
use strict;
use Naive_Log;Wenn das hier noch nicht passiert ist...
my $naive_log = new Naive_Log;
...schlaegt das hier fehl
print Naive_Log::do_it() . "\n";
EOF Naive_LogClient.pl
Nun, Du hast hier einen Objektverweis, aber keine Objektinstanz. - Nun bin ich ja nicht gerade der OO-Bulle und meine auch nur gelesen zu haben, dass Perl auch nicht gerade OO mit beiden Löffeln gefressen hat, aber das scheint mir doch alles ganz OK...
Moin!
Unter Perl gibt es ja die Moeglichkeit auf Funktoinen in einem Modul mittels <PAKETNAME>::<Fumktionsname> zuzugreifen.
Statischer Aufruf einer Klassenmethode. Hat gewisse Vorteile (man braucht kein Objekt instanziieren), und gewisse Nachteile (eine Klasse hat keine Objektvariablen und keinen Konstruktor, weil es keine Instanziierung gab).
Wenn man das Paket aber objektorientiert nutzen will, ist das ja nicht ganz unproblmatisch, da es sein koennte dass der Konstruktor noch gar nicht gerufen wurde.
Richtig, und die einzige Chance dagegen ist, dass man das Paket einfach "vernünftig" nutzt. Wenn die Klasse zwingend mindestens eine Instanz benötigt, und ihre Methoden nicht statisch sinnvoll aufgerufen werden können, ist es in der Verantwortung des Programmierers, das zu tun. Andernfalls muß er mit den Konsequenzen leben.
Beispielsweise:
my $log = Log::_getInstance()
or die 'Could not get instance for log' . "\n" ;
eval ("Log::log('Logged in class scope...')")
Wenn die Methode log der Klasse Log statisch aufgerufen wird und das nicht verträgt (weil die Verbindung zu einem offenen Logfile, die durch den Konstruktur hergestellt wird, fehlt), wird sie den Fehler in der Regel selbst bemerken und entsprechende Fehlerwerte zurückgeben. Sollte sie zumindest.
$log->log('Logged correctly')
Die Methode log des Objekts $log hingegen findet ja alles so vor, wie geplant: Eine Instanz, einen aufgerufenen Konstruktor etc. Wenn jetzt noch irgendwas fehlschlagen kann, sind es unerwartete Fehler wie "Festplatte voll". Auch das wäre zu berücksichtigen, aber außerhalb des Themenbereiches hier.
Ist das Eurer Meinung nach
- eval('undef'); # ;-)
- (void*) NULL; // Denn Perl ist nun mal kein C++
- Voelliger Mist
- Verwirrend fuer den Nutzer
- Kalter Kaffee # "Das mach ich doch seit Beginn der Epoche besser..."
- vielleicht doch irgendwie brauchbar
- ...
Um was geht's dir denn konkret? Das Singleton-Pattern kann man auf diverse Arten umsetzen. Man kann sowohl eine separate Klasse zur Instanziierung haben, als auch eine statische getInstance-Methode, oder "nur" eine Funktion mit lokaler statischer Variable...
Aber egal ob Singleton oder normale Instanziierung: Du hast immer das Problem, dass statische Aufrufe, die der Hersteller der Klasse nicht vorgesehen hat, böse sind.
- Sven Rautenberg
Wenn man das Paket aber objektorientiert nutzen will, ist das ja nicht ganz unproblmatisch, da es sein koennte dass der Konstruktor noch gar nicht gerufen wurde.
Richtig, und die einzige Chance dagegen ist, dass man das Paket einfach "vernünftig" nutzt. Wenn die Klasse zwingend mindestens eine Instanz benötigt, und ihre Methoden nicht statisch sinnvoll aufgerufen werden können, ist es in der Verantwortung des Programmierers, das zu tun. Andernfalls muß er mit den Konsequenzen leben.
Würde da eher eine Fehlermeldung erwarten.
Wenn die Methode log der Klasse Log statisch aufgerufen wird und das nicht verträgt (weil die Verbindung zu einem offenen Logfile, die durch den Konstruktur hergestellt wird, fehlt), wird sie den Fehler in der Regel selbst bemerken und entsprechende Fehlerwerte zurückgeben. Sollte sie zumindest.
Wenn aber jede Methode dieses Bemerken selbst durchzuführen hat, wie Du vorgibst, dann fände ich das schon etwas uncool.
Hallo Sven !
Richtig, und die einzige Chance dagegen ist, dass man das Paket einfach "vernünftig" nutzt.
LOL ! :-)
Dann versuch ich mal eine "Kritik der reinen Vernunft" :
Wenn ich ein Paket schreibe dass ich haeufig wiederverwenden und ggf. auch anderen Programmierern zur Verfuegung stellen werde, moechte ich, dass es moeglichst robust ausgelegt ist und Nebeneffekte moeglichst minimiert.
Wenn die Klasse zwingend mindestens eine Instanz benötigt, und ihre Methoden nicht statisch sinnvoll aufgerufen werden können, ist es in der Verantwortung des Programmierers, das zu tun. Andernfalls muß er mit den Konsequenzen leben.
Beispielsweise:
my $log = Log::_getInstance()
or die 'Could not get instance for log' . "\n" ;
eval ("Log::log('Logged in class scope...')")Wenn die Methode log der Klasse Log statisch aufgerufen wird und das nicht verträgt (weil die Verbindung zu einem offenen Logfile, die durch den Konstruktur hergestellt wird, fehlt), wird sie den Fehler in der Regel
Da moechte ich aus der Regel eine Norm machen.
Sowas:
"Undefined subroutine &Log::log called at LogClient.pl line XY"
finde ich als Hinweis auf einen Fehler recht ausagekraeftig.
Besser als wenn's spaeter an ganz anderer Stelle knallt.
Oder ich sowas auch noch im Clientcode abfangen muss - siehe das 'eval'.
und entsprechende Fehlerwerte zurückgeben. Sollte sie zumindest.
Das gesamte Objekt und alle seine Variablen jeweils auf Konsistenz zu pruefen hielte ich fuer sehr aufwendig und ggf. fehleranfaellig.
Sind ja nicht immer nur Handles, es kann sich ja auch um regulaere Variablen und Attribute handeln. Das ist ja gerade ein der Zusicherungen die andere OO-Sprachen geben; naemlich dass wenn ich auf einer validen Objektreferenz bin, EIN Initialisierer garantiert aufgerufen wurde und der Satz von Variablen existiert.
Vor sowas
(Death*) pMyAssassin = (Death*) NULL;
pMyAssassin->relief(); // ..and get outta here - hopefully HERE !
kann mich auch C++ nicht bewahren.
Aber bei jeder Methode eine Abpruefung zu muessen ob sie auch als Methode gerufen wurde oder nicht fuehrt OO doch ein wenig ad absurdum , oder ?
Um was geht's dir denn konkret? Das Singleton-Pattern kann man auf diverse Arten umsetzen. Man kann sowohl eine separate Klasse zur Instanziierung haben, als auch eine statische getInstance-Methode, oder "nur" eine Funktion mit lokaler statischer Variable...
Aber egal ob Singleton oder normale Instanziierung: Du hast immer das Problem, dass statische Aufrufe, die der Hersteller der Klasse nicht vorgesehen hat, böse sind.
Deshalb lass ich ja auch den "Compiler" ggf. drueber schimpfen.
Gruss
Holger
P.S.: Ein Singleton habe ich deshalb als Beispiel gewaehlt, da man hier die Moeglichkeit hat, die Schnittstelle aus Klassen- und Instanzmethoden zu kombinieren; bei Klassen mit hoeherer Multiplizitaet ist "das" Set von Instanzvariablen ja undefiniert.
Man koennte auf diese Weise einem Singleton-Paket zusaetzlich eine prozedurale Schnittstelle geben und diese geeignet skalieren.
z.B. "log()" waere eine Funktion, bei der man sowas in Betracht ziehen koennte - wenn es noch kein Log gibt, macht sie - transparent - halt eins auf; bei einem "addSink(@)" hingegen moecht eich dass der Nutzer merkt, dass er ein Objekt konfiguriert.
Imo kann ich mit meinem Entwurf genau diese Semantik festlegen.
Moin!
Richtig, und die einzige Chance dagegen ist, dass man das Paket einfach "vernünftig" nutzt.
LOL ! :-)
Dann versuch ich mal eine "Kritik der reinen Vernunft" :Wenn ich ein Paket schreibe dass ich haeufig wiederverwenden und ggf. auch anderen Programmierern zur Verfuegung stellen werde, moechte ich, dass es moeglichst robust ausgelegt ist und Nebeneffekte moeglichst minimiert.
Wenn's dir wirklich um handfeste Objektorientiertheit gehen würde, dann würdest du sicherlich etwas anderes als Perl benutzen. Dessen Objektorientiertheit läßt ja, genau wie PHP 4, viele Dinge vermissen. Interfaces und private bzw. geschützte Eigenschaften und Methoden beispielsweise. Von außen _kann_ man, muß aber nicht, das gesamte Innenleben eines Objektes sehen.
und entsprechende Fehlerwerte zurückgeben. Sollte sie zumindest.
Das gesamte Objekt und alle seine Variablen jeweils auf Konsistenz zu pruefen hielte ich fuer sehr aufwendig und ggf. fehleranfaellig.
Nicht das ganze Objekt. Aber die Dinge, die die Methode braucht, und die ggf. fehlschlagen können. Wie eine fehlende DB-Verbindung oder eine ungeöffnete Datei, weil der Konstruktor nicht aufgerufen wurde.
Gerne kann man daraus natürlich auch eine Grundvereinbarung machen: Der Konstruktor setzt im Objekt immer ein Flag - und wenn die Methode das Flag nicht findet, ist sie wohl statisch aufgerufen worden.
P.S.: Ein Singleton habe ich deshalb als Beispiel gewaehlt, da man hier die Moeglichkeit hat, die Schnittstelle aus Klassen- und Instanzmethoden zu kombinieren; bei Klassen mit hoeherer Multiplizitaet ist "das" Set von Instanzvariablen ja undefiniert.
Man koennte auf diese Weise einem Singleton-Paket zusaetzlich eine prozedurale Schnittstelle geben und diese geeignet skalieren.
Würde ich einer Log-Klasse noch eine prozedurale Schnittstelle geben wollen, würden die Prozeduren ganz schlicht die Methoden der Klasse aufrufen, und fertig.
Es ist nun mal so, dass man mit Code ziemlich viel erreichen kann - aber je mehr man erreichen muß, desto komplizierter wird's.
Und eine Log-Klasse ist nun beileibe kein einfaches Ding. Jedenfalls dann nicht, wenn die Klasse auch I/O implementiert. Dann wird's nämlich schwierig mit den Unit-Tests.
- Sven Rautenberg
Hallo !
[...]
Wenn's dir wirklich um handfeste Objektorientiertheit gehen würde, dann würdest du sicherlich etwas anderes als Perl benutzen. Dessen Objektorientiertheit läßt ja, genau wie PHP 4, viele Dinge vermissen.
Interfaces
Perl-Klassen haben Interfaces.
Oder meinst Du damit "Interfaces wie in Java" ? Oder C++ Klassen, die ( ggf. ausschliesslich ) rein virtuelle Methoden beinhalten (virtual f() = 0;) ?
Wie man's nimmt. Es ist schon moeglich, Funktionen ohne Implementierung in fuer Perl-Klasse zu deklarieren. Das bewahrt mich allerdings nicht davor, diese abstrakte Funktion dann auch aufzurufen.
Das ist uebrigens auch mit DER imo objektorientierten Sprache schlechthin, naemlich C++, moeglich - man muss sich nur "Muehe" geben:
/*
* pure-virtual.cpp
* Calls an abtract method at a VERY incovenient moment..
*/
/* abstract */ class A {
protected:
virtual void foo() = 0;
void bar() { foo(); };
public:
A() {
// ...namely here, where the VMT is not yet readied !
bar();
}
};
class B : public A {
protected:
virtual void foo(){};
public:
B() : A(){};
};
int main ( int argc, char* argv[]) {
B b; // ..and get outta here !
return 0;
}
// EOF pure-virtual.cpp
g++-3.3 pure-virtual.cpp
./a.out
pure virtual method called
Aborted
Das "klappt" mit dem g++; mit den MS Compilern( >5.0 ?) wenn "Construction displacement" abgeschaltet hat. Was ich immer tue um sowas von vornherein zu unterdruecken.
Der Code vestoesst natuerlich sowieso gegen die Faustregel, in Konstruktoren moeglichst nur nicht-virtuellen Code einzusetzen.
und private bzw. geschützte Eigenschaften und Methoden beispielsweise.
my
Nicht das ganze Objekt. Aber die Dinge, die die Methode braucht, und die ggf. fehlschlagen können. Wie eine fehlende DB-Verbindung oder eine ungeöffnete Datei, weil der Konstruktor nicht aufgerufen wurde.
Das waere ja nicht so schlimm; ob die DB oder eine Datei noch 'da' ist wenn ich auf darauf zugreife, muss ich ja sowieso pruefen.
Schlimmer waere doch wenn ich jede Instanz-Variable auf Gueltigkeit und ggf. auf Vorhandensein pruefen muesste.
Gerne kann man daraus natürlich auch eine Grundvereinbarung machen: Der Konstruktor setzt im Objekt immer ein Flag - und wenn die Methode das Flag nicht findet, ist sie wohl statisch aufgerufen worden.
Von Flags halte ich gar nichts; wenn das irgendwie anders geht wuerde ich immer eine andere Loesung waehlen.
Durch Flags und "last if" und "if (...) return" wird Code unuebersichtlich.
Würde ich einer Log-Klasse noch eine prozedurale Schnittstelle geben wollen, würden die Prozeduren ganz schlicht die Methoden der Klasse aufrufen, und fertig.
Wie soll ich die Methofden dann nennen um sie von den Klassenmethoden zu unterscheiden ?
_log() ; real_log() ? ; eigentliches_log() ? ;-)
Da waere mir die skizzierte Praeambel-Loesung doch noch lieber.
Es ist nun mal so, dass man mit Code ziemlich viel erreichen kann - aber je mehr man erreichen muß, desto komplizierter wird's.
Und eine Log-Klasse ist nun beileibe kein einfaches Ding.
Hab mich fuer ein "fettes" Modell entschieden, das von einem Moderator - naemlich einer (Singleton-)Application-Klasse komponiert wird.
Dort werden durch addSink() sinks ( STDOUT/STDERR, SYSLOG, FILE, DB) samt Formatierern ( PLAIN, XML ) eingebunden und die Verbosity ( NOTE, TRACE ) soll von aggregierten Strategie-Objekten gesteuert werden
Mi dem Log zieh ich sozusagen eine ganze Familie von Klassen gross, die spaeter ueberall eingestzt werden koennen.
Mal gucken wie das so mit Perl laeuft.
Gruss
Holger