Mikatus Adventskalender 2019 Tag 8 Artikel.
Wenn Sie Reaktiv in der Praxis: Eine vollständige Anleitung zur ereignisgesteuerten Systementwicklung in Java lesen, dann Das algebraische Datentypmuster (algebraisches Datentypmuster) Datentypmuster) Es war interessant festzustellen, dass etwas herauskam. ..
Reactive in der Praxis bietet ein Beispiel für die Darstellung der Auftragstypen im Aktienhandelssystem Stock Trader. Wie jeder weiß, der Aktien gehandelt hat, gibt es die folgenden Arten von Aktienaufträgen.
Zur Darstellung dieser Ordnungen wird ein Entwurfsmuster verwendet, das in der Praxis in Reactive als algebraisches Datentypmuster bezeichnet wird. Warum nicht ein Aufzählungstyp (Enum)? Lassen Sie uns das Rätsel lösen.
Ich möchte auch Lombok verwenden, also werde ich mit dem Gradle-Projekt anstelle von JShell experimentieren. Erstellen Sie ein Gradle-Projekt mit dem folgenden Befehl.
$ mkdir stock-trader
$ cd stock-trader
$ gradle init \
--type java-library \
--dsl groovy \
--test-framework junit \
--project-name stock-trader \
--package com.example
Lassen Sie uns zunächst nur Marktaufträge implementieren und Aufträge als Aufzählungstypen begrenzen. Da Limit Orders einen Limitpreis enthalten müssen, geben Sie dem Aufzählungstyp ein Feld "limitPrice". Es definiert auch eine Getter-Methode "getLimitPrice" und eine Setter-Methode "setLimitPrice". Wir haben eine abstrakte Methode "isExecutable" definiert, die bestimmt, ob die Reihenfolge durchführbar ist, und sie mit jedem Aufzählungswert überschreibt.
src/main/java/com/example/order/OrderType.java
package com.example.order;
public enum OrderType {
MARKET {
@Override
public boolean isExecutable(int currentPrice) {
return true;
}
},
LIMIT {
@Override
public boolean isExecutable(int currentPrice) {
return currentPrice <= getLimitPrice();
}
};
private int limitPrice;
public int getLimitPrice() {
return limitPrice;
}
public void setLimitPrice(int limitPrice) {
this.limitPrice = limitPrice;
}
public abstract boolean isExecutable(int currentPrice);
}
Schreiben wir einen Test, der diesen Aufzählungstyp "OrderType" verwendet.
src/test/java/com/example/order/OrderTypeTest.java
package com.example.order;
import org.junit.Test;
import static org.junit.Assert.*;
public class OrderTypeTest {
@Test
public void testIsExecutableOnMarketOrder() {
OrderType orderType = OrderType.MARKET;
assertTrue(orderType.isExecutable(100));
}
@Test
public void testIsExecutableOnLimitOrder() {
OrderType orderType = OrderType.LIMIT;
orderType.setLimitPrice(100);
assertTrue(orderType.isExecutable(100));
}
}
Tatsächlich weist die Implementierung des Auftragstyps des Aktienhandels als Aufzählungstyp die folgenden Probleme auf.
Es mag verschiedene Lösungen für diese Probleme geben, aber dieses Mal werde ich versuchen, sie mit einem algebraischen Datentypmuster zu lösen.
Ein Artikel mit dem Namen Vielleicht in Java wird in Reactive in der Praxis als versiegeltes Klassenmuster bezeichnet. Lassen Sie uns nur Marktaufträge implementieren und Aufträge als algebraische Datentypen in diesem Schildklassenmuster begrenzen.
src/main/java/com/example/order/OrderType.java
package com.example.order;
public abstract class OrderType {
private OrderType() {
}
public static final class Market extends OrderType {
@Override
public boolean isExecutable(int currentPrice) {
return true;
}
}
public static final class Limit extends OrderType {
private int limitPrice;
public Limit(int limitPrice) {
this.limitPrice = limitPrice;
}
public int getLimitPrice() {
return limitPrice;
}
@Override
public boolean isExecutable(int currentPrice) {
return currentPrice <= limitPrice;
}
}
public abstract boolean isExecutable(int currentPrice);
}
Hier sind einige Ergänzungen zur OrderType-Klasse. Durch Deklarieren von "OrderType" als abstrakte Klasse und Definieren eines privaten Konstruktors werden die Klassen, die "OrderType" erben können, auf die inneren Klassen von "OrderType" beschränkt. Wenn Sie die Klassen "Market" und "Limit" als "endgültige" Klassen deklarieren, können die Klassen "Market" und "Limit" nicht vererbt werden. Ich denke, dies ist der Grund, warum es das Schildklassenmuster genannt wird.
Schreiben wir einen Test, der diese OrderType-Klasse verwendet.
src/test/java/com/example/order/OrderTypeTest.java
package com.example.order;
import org.junit.Test;
import static org.junit.Assert.*;
public class OrderTypeTest {
@Test
public void testIsExecutableOnMarketOrder() {
OrderType orderType = new OrderType.Market();
assertTrue(orderType.isExecutable(100));
}
@Test
public void testIsExecutableOnLimitOrder() {
OrderType orderType = new OrderType.Limit(100);
assertTrue(orderType.isExecutable(100));
}
}
Die folgenden Probleme, die durch die Implementierung des Auftragstyps des Aktienhandels mit dem Aufzählungstyp verursacht werden, können durch Implementierung mit dem algebraischen Datentyp gelöst werden.
Andererseits besteht das Problem, dass es in der Verantwortung des Auftragstyps liegt, zu bestimmen, ob der Auftrag ausgeführt werden kann oder nicht. Dies ist ein Problem, das bei den für Aufzählungs- bzw. algebraische Datentypen dargestellten Implementierungen häufig auftritt. Möglicherweise möchten Sie eine bedingte Verzweigung außerhalb der OrderType-Klasse implementieren, unabhängig davon, ob die Bestellung ausgeführt werden kann oder nicht, wie unten gezeigt.
@Test
public void testSwitchOnLimitOrder() {
OrderType orderType = OrderType.LIMIT;
orderType.setLimitPrice(100);
int currentPrice = 100;
boolean result = false;
switch (orderType) {
case MARKET:
result = true;
break;
case LIMIT:
result = currentPrice <= orderType.getLimitPrice();
break;
default:
throw new UnsupportedOperationException("Unsupported order type");
}
assertTrue(result);
}
@Test
public void testIfOnLimitOrder() {
OrderType orderType = new OrderType.Limit(100);
int currentPrice = 100;
boolean result = false;
if (orderType instanceof OrderType.Market) {
result = true;
} else if (orderType instanceof OrderType.Limit) {
result = currentPrice <= orderType.getLimitPrice();
} else {
throw new UnsupportedOperationException("Unsupported order type");
}
assertTrue(result);
}
Eine solche bedingte Verzweigung hat die folgenden Probleme gemeinsam.
OrderType
-Klasse einen neuen Typ hinzufügen, müssen Sie bedingte Verzweigungen für alle OrderType
s finden und korrigieren.Um dieses Problem zu lösen, führen wir den Mustervergleich mithilfe des Besuchermusters ein.
Wir nennen es der Einfachheit halber Mustervergleich, erwarten jedoch keinen Mustervergleich wie Scala, da nur das Besuchermuster angewendet wird. Außerdem wird die Methode "isExecutable" entfernt, um das Verständnis des Codes zu erleichtern.
src/main/java/com/example/order/OrderTypeTest.java
package com.example.order;
public abstract class OrderType {
private OrderType() {
}
public static final class Market extends OrderType {
@Override
public <T> T match(CaseBlock<T> caseBlock) {
return caseBlock._case(this);
}
}
public static final class Limit extends OrderType {
private int limitPrice;
public Limit(int limitPrice) {
this.limitPrice = limitPrice;
}
public int getLimitPrice() {
return limitPrice;
}
@Override
public <T> T match(CaseBlock<T> caseBlock) {
return caseBlock._case(this);
}
}
public interface CaseBlock<T> {
T _case(Market market);
T _case(Limit limit);
}
public abstract <T> T match(CaseBlock<T> caseBlock);
}
Die abstrakte Methode "match" wird in der abstrakten Klasse "OrderType" deklariert und in jedem Typ implementiert. Die Methode "match" empfängt eine Instanz einer Klasse, die die Schnittstelle "CaseBlock" implementiert und ihre Methode "_case" aufruft. Ich denke nicht, dass es notwendig ist, das Besuchermuster noch einmal zu erklären, aber die Überladung der _case
-Methode hier führt dazu, dass die Verarbeitung je nach Typ verzweigt. Es ist ein Slapstick, aber ich habe ihn "_case" genannt, weil "case" nicht als reserviertes Wort verwendet werden kann.
Schreiben wir einen Test, der diese OrderType-Klasse verwendet. Ich denke, es ist einfacher zu verstehen, wenn man sich ansieht, wie man es benutzt.
src/test/java/com/example/order/OrderTypeTest.java
package com.example.order;
import org.junit.Test;
import static org.junit.Assert.*;
public class OrderTypeTest {
@Test
public void testPatternMatchingOnLimitOrder() {
OrderType orderType = new OrderType.Limit(100);
int currentPrice = 100;
boolean result = orderType.match(new OrderType.CaseBlock<>() {
@Override
public Boolean _case(OrderType.Market market) {
return true;
}
@Override
public Boolean _case(OrderType.Limit limit) {
return currentPrice <= limit.getLimitPrice();
}
});
assertTrue(result);
}
}
Dies löst die folgenden Probleme.
OrderType
-Klasse einen neuen Typ hinzufügen, müssen Sie bedingte Verzweigungen für alle OrderType
s finden und korrigieren.Wenn Sie der OrderType-Klasse einen neuen Typ hinzufügen, müssen Sie natürlich den Code ändern. Solange jedoch der bedingte Zweig durch die oben beschriebene Musterübereinstimmung beschrieben wird, tritt zum Zeitpunkt der Kompilierung ein Fehler auf, so dass der Korrekturteil leicht identifiziert und das Auslassen der Korrektur verhindert werden kann.
Nun stellt sich heraus, dass algebraische Datentypmuster nützlich erscheinen. Die Definition ist jedoch umständlich und der Code ist nicht gut sichtbar. Um diesen Punkt ein wenig zu verbessern, stellen wir hier Lombok vor.
Fügen Sie der Datei "build.gradle" die folgende Zeile hinzu.
--- a/build.gradle
+++ b/build.gradle
@@ -9,6 +9,7 @@
plugins {
// Apply the java-library plugin to add support for Java Library
id 'java-library'
+ id "io.freefair.lombok" version "4.1.5"
}
repositories {
Das Schreiben mit Lombok sieht folgendermaßen aus:
src/main/java/com/example/order/OrderTypeTest.java
package com.example.order;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.Value;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public abstract class OrderType {
@Value
@EqualsAndHashCode(callSuper = false)
public static class Market extends OrderType {
@Override
public <T> T match(CaseBlock<T> caseBlock) {
return caseBlock._case(this);
}
}
@Value
@EqualsAndHashCode(callSuper = false)
public static class Limit extends OrderType {
int limitPrice;
@Override
public <T> T match(CaseBlock<T> caseBlock) {
return caseBlock._case(this);
}
}
public interface CaseBlock<T> {
T _case(Market market);
T _case(Limit limit);
}
public abstract <T> T match(CaseBlock<T> caseBlock);
}
Implementieren Sie die Definition eines privaten Konstruktors mit der Annotation "NoArgsConstructor". Fügen Sie für den Transaktionstyp die Annotation "Wert" als Klasse hinzu, die ein Wertobjekt erstellt. Dies macht sowohl die "Market" -Klasse als auch die "Limit" -Klasse zur "Final" -Klasse. Es vereinfacht die Deklaration von Feldern in der Klasse "Limit", aber alle Felder sind "private final" und Konstruktoren und Getter werden automatisch generiert. Die Annotation "EqualsAndHashCode" wird hinzugefügt, da eine Warnung ausgegeben wird, um die Annotation "EqualsAndHashCode" anzugeben.
Von hier an denke ich, dass es Geschmackssache ist, aber ein statischer Konstruktor macht es eher so.
src/main/java/com/example/order/OrderTypeTest.java
package com.example.order;
import lombok.*;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public abstract class OrderType {
@Value
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public static class Market extends OrderType {
@Override
public <T> T match(CaseBlock<T> caseBlock) {
return caseBlock._case(this);
}
}
public static OrderType market() {
return new Market();
}
@Value
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public static class Limit extends OrderType {
int limitPrice;
@Override
public <T> T match(CaseBlock<T> caseBlock) {
return caseBlock._case(this);
}
}
public static OrderType limit(int limitPrice) {
return new Limit(limitPrice);
}
public interface CaseBlock<T> {
T _case(Market market);
T _case(Limit limit);
}
public abstract <T> T match(CaseBlock<T> caseBlock);
}
Bereiten Sie statische Konstruktoren wie die OrderType.market-Methode und die OrderType.limit-Methode vor. Darüber hinaus werden die Konstruktoren für die Klassen "Market" und "Limit" mit der Annotation "AllArgsConstructor" zu geschützten Konstruktoren.
Der Test ist ebenfalls leicht modifiziert.
src/test/java/com/example/order/OrderTypeTest.java
package com.example.order;
import org.junit.Test;
import static org.junit.Assert.*;
public class OrderTypeTest {
@Test
public void testPatternMatchingOnLimitOrder() {
OrderType orderType = OrderType.limit(100);
int currentPrice = 100;
boolean result = orderType.match(new OrderType.CaseBlock<>() {
@Override
public Boolean _case(OrderType.Market market) {
return true;
}
@Override
public Boolean _case(OrderType.Limit limit) {
return currentPrice <= limit.getLimitPrice();
}
});
assertTrue(result);
}
}
Wenn Sie dies bisher tun, werden die Anmerkungen durcheinander gebracht. Sie können es also gerne verwenden.
Dies kann mit Scala leicht erreicht werden. Versuchen wir es also mit Scalas REPL.
scala> :paste
// Entering paste mode (ctrl-D to finish)
sealed trait OrderType
case object Market extends OrderType
case class Limit(limitPrice: Int) extends OrderType
// Exiting paste mode, now interpreting.
defined trait OrderType
defined object Market
defined class Limit
scala> :paste
// Entering paste mode (ctrl-D to finish)
val orderType: OrderType = Limit(100)
val currentPrice = 100
orderType match {
case Market => true
case Limit(limitPrice) => currentPrice <= limitPrice
}
// Exiting paste mode, now interpreting.
orderType: OrderType = Limit(100)
currentPrice: Int = 100
res3: Boolean = true
Es kann kurz mit Scala geschrieben werden. Kotlin scheint prägnant zu sein, aber ich habe es nicht versucht.
Dieses Mal erklärte ich, was als algebraisches Datentypmuster bezeichnet wird. Irgendwie ist Java sehr ausdrucksstark. Während sich Java weiterentwickelt, werden anscheinend Grammatiken eingeführt, die die Implementierung algebraischer Datentypmuster erleichtern, sodass wir Java in Zukunft im Auge behalten werden. Die Ausdruckskraft von Scala ist jedoch immer noch faszinierend, daher würde ich gerne sowohl Java als auch Scala genießen.
Reactive in practice: A complete guide to event-driven systems development in Java
Recommended Posts