R Language
Implementieren Sie das State Machine Pattern mithilfe der S4-Klasse
Suche…
Einführung
Finite-States-Machine- Konzepte werden normalerweise in OOP-Sprachen (Object Oriented Programming) implementiert, z. B. in Java, basierend auf dem in GOF definierten State-Pattern (verweist auf das Buch: "Design Patterns").
R bietet mehrere Mechanismen, um das OO-Paradigma zu simulieren. Wenden wir das S4-Objektsystem an, um dieses Muster zu implementieren.
Zeilen mit State Machine analysieren
Wenden wir das State Machine-Muster für das Analysieren von Zeilen mit dem spezifischen Muster mithilfe der S4-Klassenfunktion von R an.
PROBLEM ENUNCIATION
Wir müssen eine Datei analysieren, in der jede Zeile Informationen über eine Person enthält, wobei ein Trennzeichen ( ";"
) verwendet wird. Einige Angaben sind jedoch optional. Statt ein leeres Feld anzugeben, fehlt es. In jeder Zeile stehen folgende Informationen zur Verfügung: Name;[Address;]Phone
. Wo die Adressinformationen optional sind, haben wir sie manchmal und manchmal nicht, zum Beispiel:
GREGORY BROWN; 25 NE 25TH; +1-786-987-6543
DAVID SMITH;786-123-4567
ALAN PEREZ; 25 SE 50TH; +1-786-987-5553
Die zweite Zeile enthält keine Adressinformationen. Daher kann die Anzahl der Trennzeichen unterschiedlich sein, wie in diesem Fall mit einem Trennzeichen und für die anderen Zeilen mit zwei Trennzeichen. Da die Anzahl der Trennzeichen variieren kann, besteht eine Möglichkeit, dieses Problem zu lösen, darin, das Vorhandensein oder Nichtvorhandensein eines bestimmten Feldes anhand seines Musters zu erkennen. In diesem Fall können wir einen regulären Ausdruck verwenden, um solche Muster zu identifizieren. Zum Beispiel:
- Name :
"^([AZ]'?\\s+)* *[AZ]+(\\s+[AZ]{1,2}\\.?,? +)*[AZ]+((-|\\s+)[AZ]+)*$"
. Zum Beispiel:RAFAEL REAL, DAVID R. SMITH, ERNESTO PEREZ GONZALEZ, 0' CONNOR BROWN, LUIS PEREZ-MENA
usw. - Adresse :
"^\\s[0-9]{1,4}(\\s+[AZ]{1,2}[0-9]{1,2}[AZ]{1,2}|[AZ\\s0-9]+)$"
. Zum Beispiel:11020 LE JEUNE ROAD
,87 SW 27TH
. Der Einfachheit halber haben wir hier nicht die Postleitzahl, Stadt, Bundesland, aber ich kann in dieses Feld aufgenommen werden oder zusätzliche Felder hinzufügen. - Telefon :
"^\\s*(\\+1(-|\\s+))*[0-9]{3}(-|\\s+)[0-9]{3}(-|\\s+)[0-9]{4}$"
. Zum Beispiel:305-123-4567, 305 123 4567, +1-786-123-4567
.
Anmerkungen :
- Ich denke über das häufigste Muster von US-Adressen und Telefonen nach. Es kann leicht erweitert werden, um allgemeinere Situationen zu berücksichtigen.
- In R hat das Zeichen
"\"
eine besondere Bedeutung für Zeichenvariablen, daher müssen wir es umgehen. - Um das Definieren regulärer Ausdrücke zu vereinfachen, empfiehlt es sich, die folgende Webseite zu verwenden: regex101.com , damit Sie mit einem bestimmten Beispiel damit spielen können, bis Sie das erwartete Ergebnis für alle möglichen Kombinationen erhalten.
Die Idee ist, jedes Linienfeld anhand zuvor definierter Muster zu identifizieren. Das Zustandsmuster definiert die folgenden Entitäten (Klassen), die zusammenarbeiten, um das spezifische Verhalten zu steuern (Das Zustandsmuster ist ein Verhaltensmuster):
Beschreiben wir jedes Element unter Berücksichtigung des Kontexts unseres Problems:
-
Context
: Speichert die Kontextinformationen des Parsing-Prozesses, dh den aktuellen Status und behandelt den gesamten State Machine-Prozess. Für jeden Status wird eine Aktion ausgeführt (handle()
), die jedoch vom Kontext abhängig von der für einen bestimmten Status (handle()
vonState
Klasse) definierten Aktionsmethode delegiert wird. Es definiert die Schnittstelle, die für Kunden von Interesse ist. UnsereContext
Klasse kann folgendermaßen definiert werden:- Attribute:
state
- Methoden:
handle()
, ...
- Attribute:
-
State
: Die abstrakte Klasse, die einen beliebigen Status der State Machine darstellt. Sie definiert eine Schnittstelle zum Einkapseln des Verhaltens, das einem bestimmten Zustand des Kontextes zugeordnet ist. Es kann wie folgt definiert werden:- Attribute:
name, pattern
- Methoden:
doAction()
,isState
(mitpattern
- Attribute überprüfen , ob das Eingabeargument zu diesem Zustand Mustern gehört oder nicht), ...
- Attribute:
-
Concrete States
(Statusunterklassen): Jede Unterklasse der KlasseState
, die ein Verhalten implementiert, das einem Zustand desContext
. Unsere Unterklassen sind:InitState
,NameState
,AddressState
,PhoneState
. Solche Klassen implementieren nur die generische Methode unter Verwendung der spezifischen Logik für solche Zustände. Es sind keine zusätzlichen Attribute erforderlich.
Hinweis: Es ist eine Frage der Präferenz, wie die Methode benannt wird, die die Aktion ausführt, handle()
, doAction()
oder goNext()
. Der Methodenname doAction()
kann für beide Klassen ( State
oder Context
) derselbe sein, den wir als handle()
in der Context
Klasse benannt haben, um Verwirrung zu vermeiden, wenn zwei generische Methoden mit denselben Eingabeargumenten, jedoch unterschiedlichen Klassen definiert werden.
PERSONENKLASSE
Mit der S4-Syntax können wir eine Person-Klasse folgendermaßen definieren:
setClass(Class = "Person",
slots = c(name = "character", address = "character", phone = "character")
)
Es ist eine gute Empfehlung, die Klassenattribute zu initialisieren. Die setClass
Dokumentation schlägt vor, eine generische Methode zu verwenden, die als "initialize"
, anstatt veraltete Attribute wie prototype, representation
.
setMethod("initialize", "Person",
definition = function(.Object, name = NA_character_,
address = NA_character_, phone = NA_character_) {
.Object@name <- name
.Object@address <- address
.Object@phone <- phone
.Object
}
)
Da die Methode initialize bereits eine generische Standardmethode Paket ist methods
, müssen wir die ursprüngliche Argument Definition respektieren. Wir können die Eingabe an der Eingabeaufforderung R überprüfen:
> initialize
Es gibt die gesamte Funktionsdefinition zurück. Sie können oben sehen, wer die Funktion wie folgt definiert:
function (.Object, ...) {...}
Wenn wir setMethod
verwenden, setMethod
wir daher genau dieselbe Syntax ( .Object
) verwenden.
Eine andere existierende generische Methode ist show
, sie entspricht toString()
Methode von Java und es ist eine gute Idee, eine spezifische Implementierung für die Klassendomäne zu haben:
setMethod("show", signature = "Person",
definition = function(object) {
info <- sprintf("%s@[name='%s', address='%s', phone='%s']",
class(object), object@name, object@address, object@phone)
cat(info)
invisible(NULL)
}
)
Hinweis : Wir verwenden dieselbe Konvention wie in der Standard-Java-Implementierung toString()
.
Angenommen, wir möchten die geparsten Informationen (eine Liste von Person
) in einem Dataset speichern. Dann sollten wir zuerst eine Liste von Objekten in etwas konvertieren können, das von R transformiert werden kann (z. B. das Objekt als Liste zwingen). Wir können die folgende zusätzliche Methode definieren (für weitere Details siehe den Beitrag )
setGeneric(name = "as.list", signature = c('x'),
def = function(x) standardGeneric("as.list"))
# Suggestion taken from here:
# http://stackoverflow.com/questions/30386009/how-to-extend-as-list-in-a-canonical-way-to-s4-objects
setMethod("as.list", signature = "Person",
definition = function(x) {
mapply(function(y) {
#apply as.list if the slot is again an user-defined object
#therefore, as.list gets applied recursively
if (inherits(slot(x,y),"Person")) {
as.list(slot(x,y))
} else {
#otherwise just return the slot
slot(x,y)
}
},
slotNames(class(x)),
SIMPLIFY=FALSE)
}
)
R bietet keine Zuckersyntax für OO, da die Sprache ursprünglich so konzipiert wurde, dass sie für Statistiker wertvolle Funktionen bereitstellt. Daher erfordert jede Benutzermethode zwei Teile: 1) den Definitionsteil (über setGeneric
) und 2) den Implementierungsteil (über setMethod
). Wie im obigen Beispiel.
STAATSKLASSE
Nach der S4-Syntax definieren wir die abstrakte State
Klasse.
setClass(Class = "State", slots = c(name = "character", pattern = "character"))
setMethod("initialize", "State",
definition = function(.Object, name = NA_character_, pattern = NA_character_) {
.Object@name <- name
.Object@pattern <- pattern
.Object
}
)
setMethod("show", signature = "State",
definition = function(object) {
info <- sprintf("%s@[name='%s', pattern='%s']", class(object),
object@name, object@pattern)
cat(info)
invisible(NULL)
}
)
setGeneric(name = "isState", signature = c('obj', 'input'),
def = function(obj, input) standardGeneric("isState"))
setGeneric(name = "doAction", signature = c('obj', 'input', 'context'),
def = function(obj, input, context) standardGeneric("doAction"))
Jede Unterklasse von State
hat einen name
und ein pattern
, aber auch eine Möglichkeit, um zu ermitteln, ob eine bestimmte Eingabe zu diesem Status gehört ( isState()
Methode), und die entsprechenden Aktionen für diesen Status zu doAction()
Methode).
Um den Prozess zu verstehen, definieren wir die Übergangsmatrix für jeden Status basierend auf der erhaltenen Eingabe:
Eingang / aktueller Status | Drin | Name | Adresse | Telefon |
---|---|---|---|---|
Name | Name | |||
Adresse | Adresse | |||
Telefon | Telefon | Telefon | ||
Ende | Ende |
Hinweis: Die Zelle [row, col]=[i,j]
repräsentiert den Zielzustand für den aktuellen Zustand j
, wenn sie die Eingabe i
empfängt.
Dies bedeutet, dass unter dem Status Name zwei Eingaben empfangen werden können: eine Adresse oder eine Telefonnummer. Eine andere Möglichkeit, die Transaktionstabelle darzustellen, ist das folgende UML State Machine- Diagramm:
Lassen Sie uns jeden einzelnen Zustand als einen Unterzustand der Klasse State
implementieren
STAATLICHE UNTERKLASSEN
Init State :
Der Anfangszustand wird über folgende Klasse implementiert:
setClass("InitState", contains = "State")
setMethod("initialize", "InitState",
definition = function(.Object, name = "init", pattern = NA_character_) {
.Object@name <- name
.Object@pattern <- pattern
.Object
}
)
setMethod("show", signature = "InitState",
definition = function(object) {
callNextMethod()
}
)
In R bezeichnet eine Klasse eine Unterklasse einer anderen Klasse, die das Attribut contains
und den Klassennamen der übergeordneten Klasse angibt.
Da die Unterklassen nur die generischen Methoden implementieren, ohne zusätzliche Attribute hinzuzufügen, rufen Sie die show
Methode einfach die entsprechende Methode von der oberen Klasse auf (über die Methode callNextMethod()
).
Dem Anfangszustand ist kein Muster zugeordnet, er stellt lediglich den Anfang des Prozesses dar. Dann initialisieren wir die Klasse mit einem NA
Wert.
Jetzt können Sie die generischen Methoden aus der State
Klasse implementieren:
setMethod(f = "isState", signature = "InitState",
definition = function(obj, input) {
nameState <- new("NameState")
result <- isState(nameState, input)
return(result)
}
)
Für diesen bestimmten Zustand (ohne pattern
) wird die Idee, dass der Parsing-Prozess mit dem ersten Feld nur initialisiert wird, ein name
, andernfalls ein Fehler.
setMethod(f = "doAction", signature = "InitState",
definition = function(obj, input, context) {
nameState <- new("NameState")
if (isState(nameState, input)) {
person <- context@person
person@name <- trimws(input)
context@person <- person
context@state <- nameState
} else {
msg <- sprintf("The input argument: '%s' cannot be identified", input)
stop(msg)
}
return(context)
}
)
Die Methode doAction
stellt den Übergang bereit und aktualisiert den Kontext mit den extrahierten Informationen. Hier greifen wir über den @-operator
. Stattdessen können wir get/set
Methoden definieren, um diesen Prozess zu kapseln (wie es in den OO-Best Practices vorgeschrieben ist: encapsulation). Dies würde jedoch vier weitere Methoden pro get-set
hinzufügen get-set
ohne für dieses Beispiel einen Mehrwert zu schaffen.
Es ist eine gute Empfehlung in allen doAction
Implementierungen, einen Schutz hinzuzufügen, wenn das Eingabeargument nicht richtig identifiziert wird.
Name Staat
Hier ist die Definition dieser Klassendefinition:
setClass ("NameState", contains = "State")
setMethod("initialize","NameState",
definition=function(.Object, name="name",
pattern = "^([A-Z]'?\\s+)* *[A-Z]+(\\s+[A-Z]{1,2}\\.?,? +)*[A-Z]+((-|\\s+)[A-Z]+)*$") {
.Object@pattern <- pattern
.Object@name <- name
.Object
}
)
setMethod("show", signature = "NameState",
definition = function(object) {
callNextMethod()
}
)
Wir verwenden die Funktion grepl
um zu überprüfen, grepl
die Eingabe zu einem gegebenen Muster gehört.
setMethod(f="isState", signature="NameState",
definition=function(obj, input) {
result <- grepl(obj@pattern, input, perl=TRUE)
return(result)
}
)
Jetzt definieren wir die Aktion, die für einen bestimmten Zustand ausgeführt werden soll:
setMethod(f = "doAction", signature = "NameState",
definition=function(obj, input, context) {
addressState <- new("AddressState")
phoneState <- new("PhoneState")
person <- context@person
if (isState(addressState, input)) {
person@address <- trimws(input)
context@person <- person
context@state <- addressState
} else if (isState(phoneState, input)) {
person@phone <- trimws(input)
context@person <- person
context@state <- phoneState
} else {
msg <- sprintf("The input argument: '%s' cannot be identified", input)
stop(msg)
}
return(context)
}
)
Hier betrachten wir mögliche Übergänge: einen für den Adressstatus und den anderen für den Telefonstatus. In allen Fällen aktualisieren wir die Kontextinformationen:
- Die
person
:address
oderphone
mit dem Eingabeargument. - Der
state
des Prozesses
Um den Zustand zu identifizieren, rufen Sie die Methode auf: isState()
für einen bestimmten Zustand. Wir erstellen standardmäßige Zustände ( addressState, phoneState
) und fragen nach einer bestimmten Validierung.
Die Logik für die Implementierung der anderen Unterklassen (eine pro Status) ist sehr ähnlich.
Adressstatus
setClass("AddressState", contains = "State")
setMethod("initialize", "AddressState",
definition = function(.Object, name="address",
pattern = "^\\s[0-9]{1,4}(\\s+[A-Z]{1,2}[0-9]{1,2}[A-Z]{1,2}|[A-Z\\s0-9]+)$") {
.Object@pattern <- pattern
.Object@name <- name
.Object
}
)
setMethod("show", signature = "AddressState",
definition = function(object) {
callNextMethod()
}
)
setMethod(f="isState", signature="AddressState",
definition=function(obj, input) {
result <- grepl(obj@pattern, input, perl=TRUE)
return(result)
}
)
setMethod(f = "doAction", "AddressState",
definition=function(obj, input, context) {
phoneState <- new("PhoneState")
if (isState(phoneState, input)) {
person <- context@person
person@phone <- trimws(input)
context@person <- person
context@state <- phoneState
} else {
msg <- sprintf("The input argument: '%s' cannot be identified", input)
stop(msg)
}
return(context)
}
)
Telefonzustand
setClass("PhoneState", contains = "State")
setMethod("initialize", "PhoneState",
definition = function(.Object, name = "phone",
pattern = "^\\s*(\\+1(-|\\s+))*[0-9]{3}(-|\\s+)[0-9]{3}(-|\\s+)[0-9]{4}$") {
.Object@pattern <- pattern
.Object@name <- name
.Object
}
)
setMethod("show", signature = "PhoneState",
definition = function(object) {
callNextMethod()
}
)
setMethod(f = "isState", signature = "PhoneState",
definition = function(obj, input) {
result <- grepl(obj@pattern, input, perl = TRUE)
return(result)
}
)
Hier fügen wir die Personeninformationen in die Liste der persons
des context
.
setMethod(f = "doAction", "PhoneState",
definition = function(obj, input, context) {
context <- addPerson(context, context@person)
context@state <- new("InitState")
return(context)
}
)
KONTEXTKLASSE
Nun erklären Context
Implementierung der Context
Klasse. Wir können es unter Berücksichtigung der folgenden Attribute definieren:
setClass(Class = "Context",
slots = c(state = "State", persons = "list", person = "Person")
)
Woher
-
state
: Der aktuelle Status des Prozesses -
person
: Die aktuelle Person, die die Informationen darstellt, die wir bereits aus der aktuellen Zeile analysiert haben. -
persons
: Die Liste der analysierten Personen.
Anmerkung : Optional können wir einen name
hinzufügen, um den Kontext anhand des name
zu identifizieren, falls mit mehr als einem Parser-Typ gearbeitet wird.
setMethod(f="initialize", signature="Context",
definition = function(.Object) {
.Object@state <- new("InitState")
.Object@persons <- list()
.Object@person <- new("Person")
return(.Object)
}
)
setMethod("show", signature = "Context",
definition = function(object) {
cat("An object of class ", class(object), "\n", sep = "")
info <- sprintf("[state='%s', persons='%s', person='%s']", object@state,
toString(object@persons), object@person)
cat(info)
invisible(NULL)
}
)
setGeneric(name = "handle", signature = c('obj', 'input', 'context'),
def = function(obj, input, context) standardGeneric("handle"))
setGeneric(name = "addPerson", signature = c('obj', 'person'),
def = function(obj, person) standardGeneric("addPerson"))
setGeneric(name = "parseLine", signature = c('obj', 's'),
def = function(obj, s) standardGeneric("parseLine"))
setGeneric(name = "parseLines", signature = c('obj', 's'),
def = function(obj, s) standardGeneric("parseLines"))
setGeneric(name = "as.df", signature = c('obj'),
def = function(obj) standardGeneric("as.df"))
Mit solchen generischen Methoden kontrollieren wir das gesamte Verhalten des Analyseprozesses:
-
handle()
:doAction()
die bestimmtedoAction()
-Methode des aktuellenstate
. -
addPerson
: Sobald wir denaddPerson
erreicht haben, müssen wir eineperson
zur Liste derpersons
hinzufügen, die wir analysiert haben. -
parseLine()
: Analysiert eine einzelne Zeile -
parseLines()
: Analysiert mehrere Zeilen (einparseLines()
) -
as.df()
: Extrahieren Sie die Informationen aus derpersons
in einas.df()
.
Lassen Sie uns nun mit den entsprechenden Implementierungen fortfahren:
handle()
-Methode, delegiert die doAction()
-Methode aus dem aktuellen state
des context
:
setMethod(f = "handle", signature = "Context",
definition = function(obj, input) {
obj <- doAction(obj@state, input, obj)
return(obj)
}
)
setMethod(f = "addPerson", signature = "Context",
definition = function(obj, person) {
obj@persons <- c(obj@persons, person)
return(obj)
}
)
Zuerst teilen wir die ursprüngliche Zeile in einem Array mit dem Trennzeichen auf, um jedes Element über die R-Funktion strsplit()
zu identifizieren, und strsplit()
dann jedes Element als Eingabewert für einen bestimmten Zustand. Die Methode handle()
gibt den context
mit den aktualisierten Informationen ( state
, person
, persons
) erneut zurück.
setMethod(f = "parseLine", signature = "Context",
definition = function(obj, s) {
elements <- strsplit(s, ";")[[1]]
# Adding an empty field for considering the end state.
elements <- c(elements, "")
n <- length(elements)
input <- NULL
for (i in (1:n)) {
input <- elements[i]
obj <- handle(obj, input)
}
return(obj@person)
}
)
Becuase R kopiert das Eingabeargument. Wir müssen den Kontext ( obj
) zurückgeben:
setMethod(f = "parseLines", signature = "Context",
definition = function(obj, s) {
n <- length(s)
listOfPersons <- list()
for (i in (1:n)) {
ipersons <- parseLine(obj, s[i])
listOfPersons[[i]] <- ipersons
}
obj@persons <- listOfPersons
return(obj)
}
)
Das Attribut persons
ist eine Liste von Exemplaren der Klasse S4 Person
. Dieses Etwas kann nicht zu einem Standardtyp erzwungen werden, da R nicht weiß, dass es eine Instanz einer benutzerdefinierten Klasse behandelt. Die Lösung besteht darin, eine Person
mithilfe der zuvor definierten as.list
Methode in eine Liste zu konvertieren. Dann können wir diese Funktion auf jedes Element der Liste gelten persons
, über die lapply()
Funktion. data.frame
dann beim nächsten Aufruf von lappy()
Funktion data.frame
, um jedes Element der persons.list
in einen data.frame
zu konvertieren. Schließlich wird die Funktion rbind()
aufgerufen, um jedes Element, das als neue Zeile des generierten rbind()
konvertiert wurde, rbind()
hierzu finden Sie in diesem Beitrag ).
# Sugestion taken from this post:
# http://stackoverflow.com/questions/4227223/r-list-to-data-frame
setMethod(f = "as.df", signature = "Context",
definition = function(obj) {
persons <- obj@persons
persons.list <- lapply(persons, as.list)
persons.ds <- do.call(rbind, lapply(persons.list, data.frame, stringsAsFactors = FALSE))
return(persons.ds)
}
)
ALLES ZUSAMMENSTELLEN
Lassen Sie uns schließlich die gesamte Lösung testen. Definieren Sie die Zeilen, in denen analysiert werden soll, wo für die zweite Zeile die Adressinformationen fehlen.
s <- c(
"GREGORY BROWN; 25 NE 25TH; +1-786-987-6543",
"DAVID SMITH;786-123-4567",
"ALAN PEREZ; 25 SE 50TH; +1-786-987-5553"
)
Jetzt initialisieren wir den context
und parsen die Zeilen:
context <- new("Context")
context <- parseLines(context, s)
Holen Sie sich schließlich den entsprechenden Datensatz und drucken Sie ihn aus:
df <- as.df(context)
> df
name address phone
1 GREGORY BROWN 25 NE 25TH +1-786-987-6543
2 DAVID SMITH <NA> 786-123-4567
3 ALAN PEREZ 25 SE 50TH +1-786-987-5553
Testen wir jetzt die show
Methoden:
> show(context@persons[[1]])
Person@[name='GREGORY BROWN', address='25 NE 25TH', phone='+1-786-987-6543']
Und für einige Unterzustände:
>show(new("PhoneState"))
PhoneState@[name='phone', pattern='^\s*(\+1(-|\s+))*[0-9]{3}(-|\s+)[0-9]{3}(-|\s+)[0-9]{4}$']
Testen Sie as.list()
die as.list()
-Methode:
> as.list(context@persons[[1]])
$name
[1] "GREGORY BROWN"
$address
[1] "25 NE 25TH"
$phone
[1] "+1-786-987-6543"
>
FAZIT
Dieses Beispiel zeigt, wie das Zustandsmuster implementiert wird, wobei einer der verfügbaren Mechanismen von R für die Verwendung des OO-Paradigmas verwendet wird. Dennoch ist die R OO-Lösung nicht benutzerfreundlich und unterscheidet sich so sehr von anderen OOP-Sprachen. Sie müssen Ihre Denkweise ändern, da die Syntax völlig anders ist und mehr an das Paradigma der funktionalen Programmierung erinnert. Zum Beispiel anstelle von object.setID("A1")
wie in Java / C #, müssen Sie für R die Methode folgendermaßen aufrufen: setID(object, "A1")
. Daher müssen Sie das Objekt immer als Eingabeargument angeben, um den Kontext der Funktion bereitzustellen. Auf dieselbe Weise gibt es kein spezielles Attribut this
Klasse und entweder ein "."
Notation für den Zugriff auf Methoden oder Attribute der angegebenen Klasse. Es ist eine weitere Fehleraufforderung, da das Verweisen auf eine Klasse oder Methoden über den Attributwert ( "Person"
, "isState"
usw.) erfolgt.
Die obige S4-Klassenlösung erfordert viel mehr Codezeilen als herkömmliche Java / C # -Sprachen, um einfache Aufgaben auszuführen. Wie auch immer, das State Pattern ist eine gute und generische Lösung für solche Probleme. Es vereinfacht den Prozess, der die Logik in einen bestimmten Zustand delegiert. Anstelle eines großen if-else
Blocks zum Steuern aller Situationen haben wir in jeder State
Unterklassenimplementierung kleinere if-else
Blöcke zur Implementierung der in jedem Staat auszuführenden Aktion.
Anhang : Hier können Sie das gesamte Skript herunterladen.
Jeder Vorschlag ist willkommen.