Übergabe/Vererbung von fertigem Objekt
Jeena Paradies
- javascript
0 Jeena Paradies0 hotti0 CPAN
Hallo,
Folgendes Problem, ich habe ein Objekt notificationCenter das ich einem neu zu erstellenden Objekt mitgeben möchte, es soll aber auch schon im Konstruktor verfügbar sein. Ist das irgendwie möglich? Ich dachte da an Interitance oder so. Mein Ansatz hier funktioniert so weit ich verstehe schon mal:
function ExtensibleObject(properties) {
for(var propertyName in properties) {
this[propertyName] = properties[propertyName];
}
}
function Player(name) {
this.name = name;
this.notificationCenter.log("Player " + this.name + " created");
}
function NotificationCenter() {}
NotificationCenter.prototype.log = function(something) {
console.log(something);
}
function Factory() {
this.notificationCenter = new NotificationCenter()
}
Factory.prototype.create = function() { // (module, [arg1, arg2, ...])
if (arguments.length < 1)
throw 'Too fiew arguments';
if (typeof arguments[0] != 'function')
throw arguments[0] + ' is not a function';
var module = arguments[0];
module.prototype = new ExtensibleObject({
notificationCenter: notificationCenter
});
return new (module.bind.apply(module, arguments))();
}
// this is like you use it:
var myFactory = new Factory();
var aPlayer = myFactory.create(Player, "jeena");
// >> "Player jeena created"
aPlayer.notificationCenter.log("logging something");
// >> "logging something"
console.log(aPlayer.name)
// >> "jeena"
Das ist hier etwas einfacher dargestellt, in wirklichkeit gibt es mehr als eine Factory und das NotificationCenter hat komplett andere Aufgaben.
Irgendwie gefällt mir das ganze aber nicht so ganz richtig, weil mir das ExtensibleObject unnötig erscheint, ich habe aber nach alternativen geschaut und leider keine gefunden. Ich wollte mit Object.create() arbeiten aber da kann ich dann nichts an den Konstruktor übergeben was mir doch sehr komisch erscheint, vielleicht habe ich die ganzen Object und Function Methoden bisher einfach nur noch nicht wirklich verstanden. Mein Ansatz war in der Factory.create() so was reinzumachen:
Factory.prototype.create = function() { // (module, [arg1, arg2, ...])
var module = arguments[0];
return Object.create(module.prototype, {
notificationCenter: {value: this.notificationCenter}
});
}
Was natürlich nicht geht weil da die argumente nicht an Player übergeben werden können.
Vielleicht hat jemand von euch eine Idee wie ich sonst so ein NotificationCenter implementieren könnte worauf eine gewisse Gruppe von Playern zugriff hätte ohne dass ich es jedes mal von Hand übergeben/setzen müsste?
Jeena
Hallo,
module.prototype = new ExtensibleObject({
notificationCenter: notificationCenter
});
Kleiner copy&paste fehler hier sollte es natürlich heißen:
module.prototype = new ExtensibleObject({
notificationCenter: this.notificationCenter
});
hi Jeena,
Folgendes Problem, ich habe ein Objekt notificationCenter das ich einem neu zu erstellenden Objekt mitgeben möchte,
Tipp aus meiner Praxis: Tu es nicht. Das ergibt Abhängigkeiten, die schwer oder nicht mehr zu überschauen sind. Es bleiben im Grunde genau zwei Möglichkeiten, Beziehungen zwischen Klassen herzustellen:
1 Vererbung, wenn es 'in Etwa' dieselbe Aufgabenstellung ist
2 Delegation, wenn andere Aufgabenstellungen hinzukommen
zu 2: Wenn meine eigene Klasse nur wenige Methoden braucht, die eine andere (fremde) Klasse kann, wird das Objekt der fremden Klasse nicht außerhalb meiner Klasse erstellt ('und mitgegeben'), sondern es wird in meiner Klasse selbst erstellt; als Attribut meiner Instanz (zweckmäßig gleich im Konstruktor).
zu 1: Wenn ich so gut wie alle Methoden einer fremden Klasse brauche, jedoch nur ein paar eigene Methoden dazu, ja, dann wird meine Klasse einfach das Erbe der fremden Klasse antreten. Die eigenen Methoden können denselben Namen haben wie die Methoden der Basisklasse, das wäre Overload. Eigene Methoden mit anderen Namen können jederzeit hinzugefpgt werden. So oder so, benutzt werden stets die Methoden der abgleiteten Klasse, sofern eine Instanz der abgleiteten Klasse erstellt wurde.
Hotti
Hallo,
Du verstehst die Fragestellung etwas falsch, ich möchte ein (oder mehrere) Objekt/e (keine Klasse) mit reinmachen, so dass es auch schon im Konstruktor zur Verfügung steht, aber ohne es an den Konstruktor übergeben zu müssen.
Jeena
moin,
Du verstehst die Fragestellung etwas falsch,
Dependency Injection ist heimtückisch: Das Objekt und dessen Herkunft, was dem Konstruktor übergeben wird, kennt nur die main, also der Code, der das macht, nicht jedoch die Klasse selbst, die das Objekt bekommt. Das erschwert die Fehlersuche, insbesondere in Hinblick auf Teamarbeit.
Viele Grüße!
Hallo,
Dependency Injection ist heimtückisch: Das Objekt und dessen Herkunft, was dem Konstruktor übergeben wird, kennt nur die main, also der Code, der das macht, nicht jedoch die Klasse selbst, die das Objekt bekommt. Das erschwert die Fehlersuche, insbesondere in Hinblick auf Teamarbeit.
Trotzdem, das Problem bleibt und muss gelöst werden.
Jeena
Hi,
Dependency Injection ist heimtückisch: Das Objekt und dessen Herkunft, was dem Konstruktor übergeben wird, kennt nur die main, also der Code, der das macht, nicht jedoch die Klasse selbst, die das Objekt bekommt. Das erschwert die Fehlersuche, insbesondere in Hinblick auf Teamarbeit.
ganz im Gegenteil: es erleichtert die Teamarbeit, weil die Klasse durch Unit-Tests so besser getestet werden kann.
Wenn die Klasse A (mit Abhängigkeit zu B) B in sich instantiiert, dann kannst du A nur noch als Codeklumpen A+B testen, d.h. deine Unit-Tests von müssen immer die Funktionalität von B mittesten (*). Auch sorgt es für mehr Sicherheit beim Entwickeln, wenn man beginnt, gegen die Interfaces von anderen Entwicklern zu entwickeln und nicht gegen deren echten Programmcode. Dies bedeutet immer, dass man die Klasse A nutzen kann, ohne die echte Implementierung von B nutzen zu müssen (ggf. existiert diese noch gar nicht).
Warum es die Fehlersuche erschweren soll, musst du erklären.
*: außen vorgelassen sei nun das Instantiieren einer Default-Implementierung von B mit gleichzeitiger Existenz eines Setters, um B zu überschreiben.
Bis die Tage,
Matti
hi,
Warum es die Fehlersuche erschweren soll, musst du erklären.
Beispiel:
package MyClass;
use strict;
use warnings;
use Carp;
sub new{
my $class = shift;
my $fh = shift;
my $self = bless{}, $class;
return eval{
$self->{FH} = $fh or croak "Missing IO-File-Instance";
$self;
};
}
sub getFileContent{
my $self = shift;
my $filename = shift;
return eval{
$self->{FH}->open($filename, "r") or croak "IO-Err: $!";
read($self->{FH}, my $buffer, -s $self->{FH});
$self->{FH}->close;
$buffer;
};
}
package main;
use strict;
use warnings;
use IO::File;
use CGI;
my $fh = IO::File->new;
my $cgi = CGI->new;
my $obj = MyClass->new($cgi) or die $@;
# Übergebe das falsche Object in Zeile 38
my $fileContent = $obj->getFileContent("d:/tmp/cookiefile") or die $@;;
print $fileContent;
Und jetzt schauen wir uns mal die Fehlermeldung an:
Undefined subroutine CGI::open <
Was sagt das dem Anwender? Gar nichts. Ok, ich könnte das Modul, den Konstruktor verbessern:
sub new{
my $class = shift;
my $fh = shift;
my $self = bless{}, $class;
return eval{
croak "The argument is not a 'IO::File'" if ref $fh ne 'IO::File';
$self->{FH} = $fh or croak "Missing IO-File-Instance";
$self;
};
}
Und bekäme dann folgende FM:
The argument is not a 'IO::File' at pack.pl line 38 <
Da kann ich aber auch gleich selbst die Klasse IO::File in MyClass einbinden, das spart mir die Prüfung und dem Anwender der Klasse die extra notwendige Objekterstellung (mit einer eigenen Prüfung, ob die erfolgreich war).
Fertig:
package MyClass;
use strict;
use warnings;
use IO::File;
use Carp;
sub new{
my $class = shift;
my $self = bless{ FH => undef}, $class;
return eval{
$self->{FH} = IO::File->new or croak "Can' create FileHandle";
$self;
};
}
sub getFileContent{
my $self = shift;
my $filename = shift;
return eval{
$self->{FH}->open($filename, "r") or croak "IO-Err: $!";
read($self->{FH}, my $buffer, -s $self->{FH});
$self->{FH}->close;
$buffer;
};
}
package main; # AnwenderScript
use strict;
use warnings;
my $obj = MyClass->new() or die $@;;
my $fileContent = $obj->getFileContent("d:/tmp/cookiefile") or die $@;;
print $fileContent;
Hotti
hi,
Warum es die Fehlersuche erschweren soll, musst du erklären.
Noch ein Beispiel, wenn im Konstruktor nicht geprüft wird, sondern erst in der Methode, wo die Instanz einer foreign class gebraucht wird:
package MyClass;
use strict;
use warnings;
use Carp;
sub new{
my $class = shift;
my $fh = shift;
return eval{
my $self = bless{}, $class;
$self->{FH} = $fh; # hier Keine Prüfung
$self;
};
}
sub getFileContent{
my $self = shift;
my $filename = shift;
return eval{
# hier die Prüfung, ob das fremde Objekt passt
# Alle Meldungen, die hier eine Exception werfen, zeigen auf die
# Zeile im Anwendungsscript, in welcher die Methode
# 'getFileContent' aufgerufen wird
croak "Wrong object, expect IO::File" if ref $self->{FH} ne 'IO::File';
$self->{FH}->open($filename, "r") or croak "IO-Err: $!";
read($self->{FH}, my $buffer, -s $self->{FH});
$self->{FH}->close;
$buffer;
};
}
package main;
use strict;
use warnings;
use IO::File;
my $obj = MyClass->new() or die $@; # Objekt nicht übergeben, der Fehler ist hier in Zeile 39!
my $fileContent = $obj->getFileContent("d:/tmp/cookiefile") or die $@; # Zeile 40
# Wrong object, expect IO::File at pack.pl line 40
print $fileContent;
Mit solch einer Fehlermeldung wird sich der Anwender der Klasse verwundert fragen, wie er der Methode 'getFileContent' ein IO::File Objekt übergeben soll!?
Zugegeben, das ist alles ein bischen konstruiert, um nicht zu sagen geschönt. In der Praxis habe ich jedoch noch viel ekelhaftere Sachen gesehen, da wurde oft gar nicht geprüft, i.d.R. wurde nur ein warn("oh hier stimmt was nicht") notiert, dementsprechend sahen die Logfiles der Webserver aus und die Fehlersuche beanspruchte nicht nur Stunden sondern Tage.
Mit solchen Konstrukten macht die Verwendung des Carp-Moduls ja auch keinen Sinn, und genau dieses Modul zeigt bei fachgerechter Anwendung auch ganz genau die richtige Zeile, wo der Fehler aufgetreten ist.
Schönes Wochenende,
Hotti
Folgendes Problem, ich habe ein Objekt notificationCenter das ich einem neu zu erstellenden Objekt mitgeben möchte, es soll aber auch schon im Konstruktor verfügbar sein. Ist das irgendwie möglich?
Ja, nennt sich Dependency Injection. ① ②
Ich dachte da an Interitance oder so.
Nein, einfache Attribute.
Mein Ansatz hier […]
Irgendwie gefällt mir das ganze aber nicht so ganz richtig, weil mir das ExtensibleObject unnötig erscheint, ich habe aber nach alternativen geschaut und leider keine gefunden.
Programmiere ab sofort auf der Basis des weltbesten MOP. Akzeptiere keine minderwertigen Objektsysteme!
Beispiel mit Joose:
Class("NotificationCenter", {
methods: {
log: function (something) {
console.log(something);
}
}
});
Class("Player", {
has: {
name: {
is: "rw",
},
notifier: {
is: "rw",
isa: NotificationCenter,
init: new NotificationCenter()
}
}
});
var aPlayer = new Player({name: 'jeena'});
aPlayer.getNotifier().log("logging something");
var bPlayer = new Player({name: 'cpan', notifier: new NotificationCenter()});
bPlayer.getNotifier().log("and another");
Erwünsche mir Feedback von dir.
Hallo,
Naja die Frage war ja nach einer irgendwie gearteten Alternative zum dem Constructor übergeben. Wenn ich nur ein NotificationCenter hätte wäre ja alles sehr einfach weil ich das global zugänglich machen könnte, aber es sind verschiedene.
Ich dachte da an Interitance oder so.
Nein, einfache Attribute.
Nja das ist schon sehr doof, weil ich die dann immer mit dem Constructor übergeben müsste damit sie da zur Verfügung stehen und das ist sehr hässlich. Und in Zukunft wenn ich da noch was dazu machen will dann müsste ich alle Dateien anfassen, das scheint mir eher suboprimal.
Programmiere ab sofort auf der Basis des weltbesten
Ähm "des weltbesten"? Es fällt mir sehr schwehr nach dieser Ankündigung noch weiter zu lesen.
MOP. Akzeptiere keine minderwertigen Objektsysteme!
Du schlägst also die Krücke Klassen in JavaScript nachzubauen? Ne ich denke ich werde wohl darauf verzichten. Wenn ich Klassen benutzen wollen würde hätte ich auch einfach ActionScript 3 nutzen können. Das Projekt dient auch dazu sich mehr mit JavaScript zu beschäftigen, da ist das Klassendenken fehl am platze.
Ich habe mittlerweile von Tim aus dem #selfhtml-Chat einen Lösungsweg aufgezeigt bekommen der mit Object.create() arbeitet.
function Player(name) {
this.name = name;
this.notificationCenter.log("Player " + this.name + " created");
}
function NotificationCenter() {}
NotificationCenter.prototype.log = function(something) {
console.log(something);
}
function Factory() {
this.notificationCenter = new NotificationCenter()
}
Factory.prototype.create = function (constructor /*, args */) {
// Objekt mit dem NC erstellen
var instance = Object.create(constructor.prototype, {
notificationCenter : {
value : this.notificationCenter
}
});
// Konstruktor methode mit den restlichen Argumenten aufrufen.
constructor.apply(instance, Array.prototype.slice.call(arguments, [1]));
return instance;
}
// this is like you use it:
var myFactory = new Factory();
var aPlayer = myFactory.create(Player, "jeena");
// >> "Player jeena created"
aPlayer.notificationCenter.log("logging something");
// >> "logging something"
console.log(aPlayer.name)
// >> "jeena"
Jeena
Hallo,
Ich habe mittlerweile von Tim aus dem #selfhtml-Chat einen Lösungsweg aufgezeigt bekommen der mit Object.create() arbeitet.
Habe ein vereinfachtes Beispiel auch mal gebloggt Call constructor with Object.create()
Jeena
hi,
Ich habe mittlerweile von Tim aus dem #selfhtml-Chat einen Lösungsweg aufgezeigt bekommen der mit Object.create() arbeitet.
http://javascript.crockford.com/prototypal.html
bzw.
http://www.google.de/search?q=crockford+Object.create()
Ich finde ja Crockfords Einstellung zu "new" recht plausibel ...;
mfg
tami
Hallo,
Ich finde ja Crockfords Einstellung zu "new" recht plausibel ...;
Ich wohl auch, nur bin ich noch nicht ganz darüber im Klaren was das für den Konstruktor bedeutet, bzw. ob das wirklich so gedacht ist wie Tim das aufgezeigt hat oder ob das nur ein workaround ist.
Jeena
Factory.prototype.create = function (constructor /*, args */) {
// Objekt mit dem NC erstellen
var instance = Object.create(constructor.prototype, {
notificationCenter : {
value : this.notificationCenter
}
});// Konstruktor methode mit den restlichen Argumenten aufrufen.
constructor.apply(instance, Array.prototype.slice.call(arguments, [1]));
return instance;
}// this is like you use it:
var myFactory = new Factory();var aPlayer = myFactory.create(Player, "jeena");
// >> "Player jeena created"
Das kommt mir ziemlich gehackt vor. Eigentlich willst du doch nur, dass sich bestimmte Instanzen einen Objektverweis teilen. Warum implementierst du das nicht in den jeweiligen Konstruktoren?
Die obige Lösung ist sehr allgemein und fügt die Funktionalität von außen hinzu. Ich frage mich, muss das so gekapselt sein, dass die letztlichen Konstruktoren nichts wissen, wie es ihnen geschieht? Wieso nicht, sie greifen doch direkt auf this.notificationCenter zu? Wieso sie nicht damit konfigurieren (Objekte by introduction explizit miteinander bekannt machen)?
Was ich nicht verstehe: Die letztlichen Konstruktoren kommen ja nicht ohne die Factory aus, schließlich würde es eine Exception geben, wenn man einfach nur »new Player« aufrufen würde (gegeben dass notificationCenter nicht in der Prototypkette definiert wird). Unter den Umständen ergibt die Trennung wenig Sinn.
Wenn du anfängst, Konstruktoren als einfache Funktionen zu verwenden, die schon ein Objekt mit Eigenschaften hineinbekommen, so kannst du auf den ganzen Factory- und Konstruktorenschmus auch verzichten und rein funktional und prototypisch arbeiten. Dann gibt es einfach Funktionen, die Objekte erzeugen, die ggf. gewisse Prototypen besitzen. Sie können dazu andere Funktionen nutzen, denen sie ggf. eine Konfiguration übergeben.
Funktional gesehen wäre das Erzeugen einer »Factory« nur das Currying der Erzeugerfunktion mit einer gewissen Konfiguration (der notificationCenter-Instanz).
Mathias
Hallo,
Der ganze Code ist sehr vereinfacht dargestellt. Neben dem notificationCenter wird auch die factory mit übergeben so dass jedes dieser Objekte genau diese factory dann benutzen kann um neue Objekte zu erstellen.
var foo = aPlayer.factory.create(Foo, "bar");
Und ich kann mir Vorstellen dass wir in Zukunft noch mehr mit übergeben wollen als diese Zwei ohne dass wir alle Dateien anfassen zu müssen.
Die Struktur des Programs ist in etwa so:
Coordinator (1)
Chanel (n)
User (n)
ServerGame (1)
ServerProcessor (1)
PhysicsEngine (1)
Doll (n)
InputController (1)
... usw.
Das geht noch breiter und tiefer. Es gibt also viele Chanels und alle Objekte die mit einem Chanel (jeder Chanel ist so zu sagen eine Instanz des Spieles) zu tun haben sollen jetzt ein gemeinsames NotificationCenter benutzen um miteinander zu komunizieren. Wenn also z.b. der User ein gameCommand von außen über das socket bekommt dann soll er nur sagen müssen notificationCenter.trigger("gameCommand", [command, args]) und alle die sich dafür registriert haben sollen benachrichtigt werden. Z.b. könnte sich InputController registrieren und dann schauen ob da daten reinkommen die ihn betreffen wie ein "jump"-Befehl oder so und darauf reagieren.
Mein erster Ansatz war einfach diese Methoden in jedes Zwischenobjekt reinzumachen. D.h. User hat nur einen Pointer zu Chanel also habe ich im Chanel.performGameCommand implementiert. Chanel hat nur einen Pointer zu ServerGame also habe ich ServerGame.performGameCommand implementiert. ServerGame hatte einen Pointer zu ServerProcessor also habe ich ServerProcessor.performGameCommand implementiert, das wiederum hatte einen Pointer zu PhysicsEngine also PhysicsEngine.performGameCommand, ich bin noch nicht mal am Ende der Kette aber man merkt dass das kein sinnvoller weg ist, denn der Code wird dadurch unlesbar und unwartbar.
Deshalb die Idee mit dem NotificationCenter. Ich wollte da aber nicht einfach nur wieder das gleiche wie vorher machen dass ich das die ganze Zeit über alle Level hindurch manuell übergebe und wenn ich irgendwann noch ein zweites Objekt mit übergeben möchte dann muss ich wieder alle Dateien anfassen und dieses übergeben.
Ich weiß selbst dass das gehackt aussieht und es für dritte dann etwas schwierig zu verstehen ist woher jetzt in jedem Objekt plötzlich this.notificationCenter auftaucht, die Hoffnung ist dass wie über this.factory.new(Foo, args) stolpern und sich dann auf die Suche machen. Bis auf die Manuelle Übergabe über den Constructor fällt mir aber nichts wirklich besseres ein. Naja doch als ich das jetzt alles aufgeschrieben habe fiel mir ein dass man das ganze vielleicht gleich von Anfang an auf verschiedene richtige Prozesse aufteilen könnte, so dass jeder Chanel ein Prozess ist, das würde dann auch gleich mehr skalieren und man könnte auch mehrere Kerne nutzen und hätte dann immer nur ein globales NotificationCenter. Das muss ich gleich mal weiter untersuchen.
Jeena
Hallo,
Naja doch als ich das jetzt alles aufgeschrieben habe fiel mir ein dass man das ganze vielleicht gleich von Anfang an auf verschiedene richtige Prozesse aufteilen könnte, so dass jeder Chanel ein Prozess ist, das würde dann auch gleich mehr skalieren und man könnte auch mehrere Kerne nutzen und hätte dann immer nur ein globales NotificationCenter. Das muss ich gleich mal weiter untersuchen.
Und genau das haben wir jetzt gemacht. Wir haben heute das ganze Projekt umgeschmissen und komplett neu strukturiert, so dass jeder Chanel jetzt ein eigener Process ist, das bringt den Vorteil dass sie nicht alle auf einem Kernel laufen müssen, und darüber hinaus dass jedes NotificationCenter auch komplett alleine da ist und deshalb nicht mehr übergeben werden muss sondern direkt mit NotificationCenter.on(), .trigger() und .off() aufgerufen werden kann.
Die ganze Aufregung also umsonst? Nein. Wir haben gelernt wie das mit der Vererbung und Object.create() usw. alles funktioniert und dieses Wissen ist mehr als Gold wert! Vielen Dank an alle die sich bemüht haben mitzuhelfen!
Jeena
Hallo,
Class("Player", {
has: {
name: {
is: "rw",
},
notifier: {
is: "rw",
isa: NotificationCenter,
init: new NotificationCenter()
}
}
});
[/code]
Ich habe deinen Code jetzt noch einmal richtig durchgelesen und gemerkt dass du meine Frage nicht richtig verstanden hast. Dein Code erstellt bei jedem Player ein neues NotificationCenter, ich möchte aber eine instanz des NotificationCenter für eine _Gruppe_ von Playern und anderen Objekten haben, so dass sie alle das gleiche Objekt benutzen.
Das NotificationCenter soll dazu da sein dass man sich da anmelden kann um bei gewissen events benachrichtigt zu werden. Es gibt aber verschiedene NotificationCenter für verschiedene so genannte Chanels (Räume im Spiel).
Bisher habe ich das einfach im Konstruktor übergeben aber das ist hässlich und sehr unhandlich.
Jeena