Discussion:
Strategy-Objekte
(zu alt für eine Antwort)
Christian H. Kuhn
2019-05-02 20:31:06 UTC
Permalink
Liebe Gemeinde,

bevor hier ganz zugemacht wird:

Ich bastele an einem Programm zur Verwaltung von Schachturnieren. Die
möglichen Ergebnisse einer Partie habe ich in einem Enum Chessresult mit
den Werten WIN, DRAW, LOSS dargestellt. Für die Berechnung der Paarungen
der nächsten Runde nach Schweizer System ist der aktuelle Punktestand zu
bestimmen. Der kann aus diesen drei Werten durch mindestens drei
gebräuchliche Wertgruppen berechnet werden:

- 1, 0,5, 0 (bei Einzelpartien gebräuchlich)
- 2, 1, 0 (eher für Mannschaftskämpfe)
- 3, 1, 0 (sowohl bei Einzel- als auch Mannschaftsturnieren gelegentlich
anzutreffen)

Das riecht nach dem Strategy Pattern. Also etwa

interface ResultToScore {
public float score(Chessresult result);
}

Die einzelnen Strategien möchte ich als nicht-anonyme Klasse definieren.
Eine Lambda-Expression an geeigneter Stelle liest sich elegant, ist aber
nicht wiederverwendbar. Egal, wo ich lese, ist die Implementation sowas wie

public class OnePoint implements ResultToScore {
@Override
public float score(Chessresult result) {
switch (result) {
case Chessresult.WIN: return 1;
case Chessresult.DRAW: return 0.5;
case Chessresult.LOSS:
default: return 0;
}
}
}

Ich brauche aber gar kein Objekt, es würde mir reichen, wenn score()
static wäre. Wenn ich ein Objekt nehme, reicht mir ein Singleton. Und
überhaupt haben alle diese Strategien keinen Zustand und sind
automatisch immutable. Gibt es einen Grund, warum in allen Beispielen,
die ich finde, die Strategy-Klassen so implementiert werden, dass es von
jeder verschiedene unterscheidbare Instanzen gibt, obwohl das auch in
den meisten Beispielen sinnlos ist? Oder mache ich mir zuviel Gedanken?

TIA
QNo
Patrick Roemer
2019-05-03 08:58:34 UTC
Permalink
Post by Christian H. Kuhn
Das riecht nach dem Strategy Pattern. Also etwa
interface ResultToScore {
public float score(Chessresult result);
}
Die einzelnen Strategien möchte ich als nicht-anonyme Klasse definieren.
Eine Lambda-Expression an geeigneter Stelle liest sich elegant, ist aber
nicht wiederverwendbar. Egal, wo ich lese, ist die Implementation sowas wie
public class OnePoint implements ResultToScore {
@Override
public float score(Chessresult result) {
switch (result) {
case Chessresult.WIN: return 1;
case Chessresult.DRAW: return 0.5;
default: return 0;
}
}
}
Ich brauche aber gar kein Objekt, es würde mir reichen, wenn score()
static wäre. Wenn ich ein Objekt nehme, reicht mir ein Singleton. Und
überhaupt haben alle diese Strategien keinen Zustand und sind
automatisch immutable. Gibt es einen Grund, warum in allen Beispielen,
die ich finde, die Strategy-Klassen so implementiert werden, dass es von
jeder verschiedene unterscheidbare Instanzen gibt, obwohl das auch in
den meisten Beispielen sinnlos ist? Oder mache ich mir zuviel Gedanken?
Irgendwie schon.

Die einfache Klasse dürfte dem Tutorial-Autor schlicht didaktisch
intuitiver vorkommen als ein Mix von Strategy und Singleton. In
"richtigem" Code hätte ich wohl eine leichte Präferenz für die
Singleton-Variante - mehr Boilerplate, aber saubere Objektidentität.

In Scala hingegen gibt es Singletons als Sprachkonstrukt, da hat man
diesen Tradeoff nicht:

<snip>
type ResultToScore = Chessresult => Float

object OnePoint extends ResultToScore {
override def apply(result: Chessresult): Float =
result match {
case Chessresult.WIN => 1
case Chessresult.DRAW => 0.5F
case Chessresult.LOSS => 0
}
}

println(OnePoint(Chessresult.WIN))
</snip>

Viele Grüße
Patrick
Patrick Roemer
2019-05-03 10:32:38 UTC
Permalink
Post by Christian H. Kuhn
Das riecht nach dem Strategy Pattern. Also etwa
interface ResultToScore {
public float score(Chessresult result);
}
[...]
Post by Christian H. Kuhn
Ich brauche aber gar kein Objekt, es würde mir reichen, wenn score()
static wäre.
Ergänzend: Du kannst das natürlich auch basierend auf Methodenreferenzen
implementieren...

public static final class OnePoint {

private OnePoint() {}

public static float score(Chessresult result) {
switch(result) {
case WIN: return 1;
case DRAW: return 0.5F;
case LOSS: return 0;
default: throw new IllegalStateException();
}
}
}

...und dann "OnePoint::score" verwenden, wo ein ResultToScore benötigt
wird. Das Strategy-Pattern ist damit weniger deutlich erkenntlich, aber
das ist eher ein Feature als ein Bug - Patterns sind halt Workarounds um
Unzulänglichkeiten der Sprache...

Leider hat man damit immer noch keinen knackigen, atomaren Namen für die
Implementierung - statt "new OnePoint()" oder "OnePoint.INSTANCE" jetzt
halt "OnePoint::score". In Scala könnte man die Methode (oder Funktion)
onePoint() nennen und ins Package-Objekt packen.

Viele Grüße
Patrick
Joerg Meier
2019-05-03 11:37:49 UTC
Permalink
Post by Christian H. Kuhn
Ich brauche aber gar kein Objekt, es würde mir reichen, wenn score()
static wäre. Wenn ich ein Objekt nehme, reicht mir ein Singleton. Und
überhaupt haben alle diese Strategien keinen Zustand und sind
automatisch immutable.
Die Strategieen SIND aber der state. Also sie selbst beinhalten zwar keinen
state, aber die Information, welche Strategie genutzt werden soll, muss ja
irgendwo gespeichert sein. Und wie willst Du die Information, welche Klasse
Du nutzen moechtest speichern, wenn nicht als Instanz dieser Klasse ?

Klar, Du koenntest eine Class<? extends ResultToScore> strategy; oder so
speichern, aber das macht es auch nicht viel besser (imo eher noch
schlechter). Aufrufe von statischen Methoden sind in Java eben nicht
dynamisch, und ausser mit Reflection kommst Du da auch nicht drum rum.

Als alternativer Vorschlag, wie Du Dein Problem elegant schreiben
koenntest:

public enum Scoring {
SINGLE(1, 0.5, 0),
TOURNEY(2, 1, 0),
MIXED(3, 1, 0);

private final double win;
private final double draw;
private final double loss;

private Scoring(double win, double draw, double loss) {
this.win = win; this.draw = draw; this.loss = loss;
}

public double score(Chessresult result) {
switch (result) {
case WIN: return win;
case DRAW: return draw;
case LOSS:
default: return loss;
}
}
}

Dann einfach Scoring scoring = SINGLE; double points =
scoring.score(result);

Statelose Objekte, bei denen nur die Objektidentitaet state ist, sind fuer
java-Enums der perfekte use case.

Liebe Gruesse,
Joerg
--
Ich lese meine Emails nicht, replies to Email bleiben also leider
ungelesen.
Patrick Roemer
2019-05-03 12:42:03 UTC
Permalink
Post by Joerg Meier
Post by Christian H. Kuhn
Ich brauche aber gar kein Objekt, es würde mir reichen, wenn score()
static wäre. Wenn ich ein Objekt nehme, reicht mir ein Singleton. Und
überhaupt haben alle diese Strategien keinen Zustand und sind
automatisch immutable.
Die Strategieen SIND aber der state. Also sie selbst beinhalten zwar keinen
state, aber die Information, welche Strategie genutzt werden soll, muss ja
irgendwo gespeichert sein. Und wie willst Du die Information, welche Klasse
Du nutzen moechtest speichern, wenn nicht als Instanz dieser Klasse ?
Das "Problem" ist, dass man mehrere Exemplare dieser Klasse erzeugen
kann, die funktional identisch sind. Das ist undramatisch, aber aus
ästhetischer Sicht etwas störend und könnte in äußerst
unwahrscheinlichen Szenarien tatsächlich zu Problemen führen, etwa wenn
man solche Scorer-Objekte in einem Set hält und dann erwartet, dass jede
Implementierung nur einmal drin sein kann.

Vermeiden kann man das z.B. durch das klassische Singleton-Pattern oder
über "Konstanten" mit anonymen Implementierungen des Interface.
Post by Joerg Meier
Klar, Du koenntest eine Class<? extends ResultToScore> strategy; oder so
speichern, aber das macht es auch nicht viel besser (imo eher noch
schlechter). Aufrufe von statischen Methoden sind in Java eben nicht
dynamisch, und ausser mit Reflection kommst Du da auch nicht drum rum.
Man kann sie als Methodenreferenzen auf eine Implementierung des
Scorer-Interface mappen. Die Singleton-Eigenschaft bekommt man damit
allerdings nicht.
Post by Joerg Meier
public enum Scoring {
SINGLE(1, 0.5, 0),
TOURNEY(2, 1, 0),
MIXED(3, 1, 0);
[...]
Post by Joerg Meier
Statelose Objekte, bei denen nur die Objektidentitaet state ist, sind fuer
java-Enums der perfekte use case.
Aber nur, wenn die Menge der möglichen Implementierungen fix und
abgeschlossen ist und an einer einzigen Codestelle deklariert werden
kann. Den Weg, mal eben ein 5-1-0-Scoring einzuführen, hat man sich dann
verbaut.

Viele Grüße
Patrick
Christian H. Kuhn
2019-05-03 17:13:39 UTC
Permalink
Post by Patrick Roemer
Das ist undramatisch, aber aus
ästhetischer Sicht etwas störend und könnte in äußerst
unwahrscheinlichen Szenarien tatsächlich zu Problemen führen,
Es geht tatsächlich um die Ästhetik und die „reine Lehre“. Zur Zeit gibt
es kein mir bekanntes Programm unter offener Lizenz, das die Regeln des
Weltschachverbandes FIDE für das Schweizer System in allen vier dort
genannten Varianten KORREKT implementiert. Eine offene
JAVA-Implementierung (wenn ich sie denn tatsächlich eines Tages mal
fertig stellen sollte) wäre also gewissermaßen so eine Art
Referenz-Implementierung, und da will man auch die kleinen Dinge
vorzeigbar gestalten :-)
Post by Patrick Roemer
Post by Joerg Meier
Klar, Du koenntest eine Class<? extends ResultToScore> strategy; oder so
speichern, aber das macht es auch nicht viel besser (imo eher noch
schlechter). Aufrufe von statischen Methoden sind in Java eben nicht
dynamisch, und ausser mit Reflection kommst Du da auch nicht drum rum.
Da verstehe ich nicht, was das eine mit dem anderen zu tun hat. Ich
speichere im Turnier ein Strategie-Objekt der Klasse, die die gewünschte
Strategie implementiert. Da brauche ich nichts static. Wenn static, dann
als Utility class, in der die verschiedenen Berechnungen als je eine
statische Methode enthalten sind.
Post by Patrick Roemer
Post by Joerg Meier
Statelose Objekte, bei denen nur die Objektidentitaet state ist, sind fuer
java-Enums der perfekte use case.
Aber nur, wenn die Menge der möglichen Implementierungen fix und
abgeschlossen ist und an einer einzigen Codestelle deklariert werden
kann. Den Weg, mal eben ein 5-1-0-Scoring einzuführen, hat man sich dann
verbaut.
Und so etwas könnte halt eben passieren.

lg
Christian
Thomas Noll
2019-05-03 17:58:16 UTC
Permalink
Post by Christian H. Kuhn
Es geht tatsächlich um die Ästhetik und die „reine Lehre“. Zur Zeit gibt
es kein mir bekanntes Programm unter offener Lizenz, das die Regeln des
Weltschachverbandes FIDE für das Schweizer System in allen vier dort
genannten Varianten KORREKT implementiert. Eine offene
JAVA-Implementierung (wenn ich sie denn tatsächlich eines Tages mal
fertig stellen sollte) wäre also gewissermaßen so eine Art
Referenz-Implementierung, und da will man auch die kleinen Dinge
vorzeigbar gestalten :-)
Es gibt allerdings keine kleinen Dinge mehr, wenn man in ein Mapping
von 3 Zuständen in je eine Zahl Ästhetik und reine Lehre reinsteckt.
Christian H. Kuhn
2019-05-03 18:58:08 UTC
Permalink
Post by Thomas Noll
Es gibt allerdings keine kleinen Dinge mehr, wenn man in ein Mapping
von 3 Zuständen in je eine Zahl Ästhetik und reine Lehre reinsteckt.
gesiggt.

Meine Güte. Ich habe googeln müssen, um herauszufinden, wie wir damals
im FIDO-Netz Sätze geliked haben :-)
--
Es gibt allerdings keine kleinen Dinge mehr, wenn man in ein Mapping
von 3 Zuständen in je eine Zahl Ästhetik und reine Lehre reinsteckt.
Christian H. Kuhn
2019-05-03 17:05:47 UTC
Permalink
Post by Joerg Meier
Die Strategieen SIND aber der state. Also sie selbst beinhalten zwar keinen
state, aber die Information, welche Strategie genutzt werden soll, muss ja
irgendwo gespeichert sein. Und wie willst Du die Information, welche Klasse
Du nutzen moechtest speichern, wenn nicht als Instanz dieser Klasse ?
Die konkret genutzte Strategie hat keinen state, sondern trägt zum state
des Turniers bei. Also beim Nutzer der ganzen Konstruktion. Die übliche
Implementation (da will ich dem Verwender aber alle Freiheiten lassen)
sieht so aus, dass man in der GUI die gewünschte Variante anclickt, die
dann als (privates) Feld des Objekts der entsprechenden Turnierklasse
gespeichert wird.

lg
QNo
Loading...