In den Verhaltens-Entwurfsmustern sind das Zustandsmuster und das Strategiemuster enge Brüder; sie sind sich sehr ähnlich. Schauen wir uns zunächst ihr gemeinsames Klassendiagramm an und vergleichen sie Seite an Seite, wie in der Abbildung gezeigt:

Sie sehen sich sehr ähnlich, nicht wahr? Wenn wir nur diese UML betrachten, können wir den Unterschied nicht erkennen. Als Nächstes vergleichen wir anhand von Beispielen die Unterschiede zwischen den beiden. Das folgende Beispiel stammt aus "Head First Design Patterns".
Strategie-Muster
Das Strategiemuster definiert eine Familie von Algorithmen, kapselt jeden einzelnen und macht sie austauschbar. Dieses Muster ermöglicht es, dass der Algorithmus unabhängig von den Clients variiert, die ihn verwenden.
Ein Unternehmen entwickelte ein Entenspiel, das verschiedene Arten von Enten enthält: Stockenten, Rotkopfenten, Gummienten... Wie können wir diese Enten in einem Programm implementieren? Bei diesem Szenario denkt man leicht daran, zunächst eine Oberklasse Duck für alle Enten zu definieren und die gemeinsamen Methoden und Eigenschaften darin zu kapseln. Zum Beispiel können alle Enten schwimmen, und alle Enten haben ihr eigenes Aussehen:
Beispiel
public void swim(){
System.out.println("All duck can swim!");
}
public abstract void display();
public void fly(){
System.out.println("Fly~~~");
}
public void quack(){
System.out.println("Quack quack quack~");
}
}
Doch bald fällt uns ein Problem mit dieser Definition der Oberklasse auf. Nicht alle Enten können fliegen, und nicht alle Enten quaken (angenommen, Gummienten quietschen). Sobald sie diese Oberklasse erben, werden alle Enten fliegen, und alle Enten werden quaken.
Was ist zu tun?
Lösung 1
Die erste Lösung, die einem in den Sinn kommt, ist das Überschreiben von Methoden in Unterklassen. Ganz einfach: Man überschreibt einfach die Methode fly in Unterklassen, die nicht fliegen können.
Aber der Nachteil ist offensichtlich: Müssten dann nicht alle nicht fliegenden Unterklassen sie überschreiben? Angenommen, 50 Entenarten können nicht fliegen; wie riesig wäre der Aufwand für Umschreiben und Wartung?
Lösung 2
Okay, also denken wir an eine zweite Methode: weiterhin abstrakte Unterklassen entwerfen, wie die abstrakte Unterklasse FlyNoQuackDuck, die fliegen, aber nicht quaken kann, die abstrakte Unterklasse QuackNoFlyDuck, die quaken, aber nicht fliegen kann, die abstrakte Unterklasse NoQuackNoFlyDuck, die weder quaken noch fliegen kann, die abstrakte Unterklasse FlyAndQuackDuck, die sowohl fliegen als auch quaken kann...
Während wir schreiben, stellen wir fest, dass diese Methode ebenfalls nicht funktioniert; sie ist zu unflexibel, und je mehr Teile sich ändern, desto mehr solcher abstrakten Unterklassen müssen wir definieren.
Lösung 3
Welche Methode ist dann gut? Wir überlegen, dass das obige Problem entsteht, weil wir gewohnheitsmäßig versuchen, die sich ändernden Teile mit Vererbung zu implementieren. Tatsächlich können wir die sich ändernden Teile herausziehen und mit Komposition implementieren.
Hier werden drei Entwurfsprinzipien verwendet:
- Identifizieren Sie die Aspekte Ihrer Anwendung, die sich ändern können, und trennen Sie sie.
- Programmieren Sie gegen eine Schnittstelle, nicht gegen eine Implementierung.
- Bevorzugen Sie Komposition gegenüber Vererbung.
Unter Anwendung des ersten Entwurfsprinzips trennen wir die sich ändernden Methoden fly() und quack() und kapseln sie in zwei Verhaltensschnittstellen. Dann entwerfen wir je nach unterschiedlichen Anforderungen verschiedene Verhaltensklassen, die die Schnittstellen implementieren.

Unter Anwendung des zweiten und dritten Entwurfsprinzips komponieren wir die beiden Schnittstellen als Membervariablen in die Klasse Duck und weisen sie in Unterklassen dynamisch zu.

Das gesamte "Klassendiagramm" sieht wie folgt aus:

Zusammenfassung
Diese Lösung löst unser Problem perfekt. Mit der Idee des "Strategie-Musters" extrahieren wir die sich ändernden Teile und fügen sie in die Klasse ein. Je nach unterschiedlichen Unterklassen können wir verschiedene Verhaltens-Unterklassen "setzen", um das Verhalten dynamisch zu ändern.
Code-Implementierung
Zwei Verhaltens-Schnittstellenklassen
Beispiel
public void fly();
}
public interface QuackBehavior {
public void quack();
}
Verschiedene Verhaltensklassen, die die Flug-Schnittstelle implementieren
Beispiel
public void fly(){
System.out.println("Ich kann nicht fliegen...");
}
}
public class FlyWithWings implements FlyBehavior{
public void fly(){
System.out.println("Fly~~~");
}
}
public class FlyWithRocket implements FlyBehavior{
public void fly(){
System.out.println("Mit einem Raketenwerfer, flieg~~~");
}
}
Verschiedene Verhaltensklassen, die das Quaken implementieren
Beispiel
public void quack(){
System.out.println("Quack quack quack~");
}
}
public class Squeak implements QuackBehavior{
public void quack(){
System.out.println("Quiek quiek quiek~");
}
}
public class MuteQuack implements QuackBehavior{
public void quack(){
System.out.println("Ich kann nicht quaken...");
}
}
Die Superklasse kombiniert mit implementierenden Schnittstellen
Beispiel
protected FlyBehavior flyBehavior;
protected QuackBehavior quackBehavior;
public void swim(){
System.out.println("All duck can swim!");
}
public abstract void display();
/**
* Flugverhalten dynamisch ändern
*/
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
/**
* Quakverhalten dynamisch ändern
*/
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
public void performFly(){
flyBehavior.fly();
}
public void performQuack(){
quackBehavior.quack();
}
}
Verschiedene Entenklassen
Beispiel
* Stockente
*/
public class MallarDuck extends Duck{
public MallarDuck() {
//Kann fliegen
flyBehavior = new FlyWithWings();
//Quakt
quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("Sieht aus wie eine Stockente");
}
}
/**
* Rotkopfente
*/
public class RedHeadDuck extends Duck{
public RedHeadDuck() {
//Kann fliegen
flyBehavior = new FlyWithWings();
//Quakt
quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("Sieht aus wie eine Rotkopfente");
}
}
/**
* Gummiente
*/
public class RubberDuck extends Duck{
public RubberDuck() {
//Kann nicht fliegen
flyBehavior = new FlyNoWay();
//Quietschen
quackBehavior = new Squeak();
}
@Override
public void display() {
System.out.println("Sieht aus wie eine Gummiente");
}
}
Zustands-Muster
Das State-Muster ermöglicht es einem Objekt, sein Verhalten zu ändern, wenn sich sein interner Zustand ändert. Das Objekt scheint seine Klasse zu ändern.
Das State-Muster ist dem Strategie-Muster sehr ähnlich. Es kapselt außerdem den "Zustand" der Klasse und wechselt bei der Ausführung von Aktionen automatisch, sodass dieselbe Aktion der Klasse in verschiedenen Zuständen unterschiedliche Ergebnisse zeigt. Der Unterschied zum Strategie-Muster besteht darin, dass dieser Übergang "automatisch" und "unbewusst" erfolgt.
Das Klassendiagramm des State-Musters ist wie folgt

Das Klassendiagramm des State-Musters ist genau dasselbe wie das des Strategie-Musters; der Unterschied liegt in ihrer Absicht. Das Strategie-Muster steuert, welche Strategie das Objekt verwendet, während das State-Muster den Zustand automatisch ändert. Nachdem Sie den folgenden Fall gelesen haben, sollte Ihnen das klar sein.
Jetzt haben Sie eine Anforderung für einen Süßigkeitenautomaten, und er muss in Java implementiert werden.
Lassen Sie uns das analysieren. Die Funktionalität des Süßigkeitenautomaten kann in vier Aktionen und vier Zustände unterteilt werden, wie in der folgenden Abbildung dargestellt:

In verschiedenen Zuständen hat dieselbe Aktion unterschiedliche Ergebnisse. Zum Beispiel gibt im Zustand "25 Cent eingeworfen" das Drehen der Kurbel Süßigkeiten aus; aber im Zustand "keine 25 Cent eingeworfen" fordert das Drehen der Kurbel Sie auf, zuerst eine Münze einzuwerfen.
Nach kurzem Überlegen schreiben wir den folgenden Implementierungscode für den Süßigkeitenautomaten
Beispiel
/*
* Vier Zustände
*/
/**Zustand: keine Münze*/
private final static int NO_QUARTER = 0;
/**Zustand: Münze eingeworfen*/
private final static int HAS_QUARTER = 1;
/**Zustand: verkauft*/
private final static int SOLD = 2;
/**Zustand: ausverkauft*/
private final static int SOLD_OUT = 3;
private int state = SOLD_OUT;
private int candyCount = 0;
public NoPatternGumballMachine(int count) {
this.candyCount = count;
if(candyCount > 0)
state = NO_QUARTER;
}
/*
* Vier Aktionen
*/
/**
* Münze einwerfen
*/
public void insertQuarter() {
if(NO_QUARTER == state){
System.out.println("Münze einwerfen");
state = HAS_QUARTER;
}
else if(HAS_QUARTER == state){
System.out.println("Bitte werfen Sie nicht wiederholt eine Münze ein!");
returnQuarter();
}
else if(SOLD == state){
System.out.println("Münze eingeworfen, bitte warten Sie auf die Süßigkeit");
returnQuarter();
}else if(SOLD_OUT == state){
System.out.println("Süßigkeit ist ausverkauft");
returnQuarter();
}
}
/**
* Münze zurückgeben
*/
public void ejectQuarter() {
if(NO_QUARTER == state){
System.out.println("Keine Münze, Auswurf nicht möglich");
}
else if(HAS_QUARTER == state){
returnQuarter();
state = NO_QUARTER;
}
else if(SOLD == state){
System.out.println("Münze kann nicht zurückgegeben werden, Süßigkeit wird ausgegeben, bitte warten");
}else if(SOLD_OUT == state){
System.out.println("Keine Münze eingeworfen, Rückgabe nicht möglich");
}
}
/**
* Kurbel drehen, um Süßigkeit auszugeben
*/
public void turnCrank() {
if(NO_QUARTER == state){
System.out.println("Bitte zuerst eine Münze einwerfen");
}
else if(HAS_QUARTER == state){
System.out.println("Kurbel drehen, bereit, Süßigkeit auszugeben");
state = SOLD;
}
else if(SOLD == state){
System.out.println("Kurbel bereits gedreht, bitte warten");
}else if(SOLD_OUT == state){
System.out.println("Süßigkeit ist ausverkauft");
}
}
/**
* Süßigkeit ausgeben
*/
public void dispense() {
if(NO_QUARTER == state){
System.out.println("Keine Münze eingeworfen, Süßigkeit kann nicht ausgegeben werden");
}
else if(HAS_QUARTER == state){
System.out.println("Diese Methode wird nicht unterstützt");
}
else if(SOLD == state){
if(candyCount > 0){
System.out.println("Süßigkeit wird ausgegeben");
candyCount --;
state = NO_QUARTER;
}
else{
System.out.println("Entschuldigung, Süßigkeit ist ausverkauft");
state = SOLD_OUT;
}
}else if(SOLD_OUT == state){
System.out.println("Entschuldigung, Süßigkeit ist ausverkauft");
}
}
/**
* Münze zurückgeben
*/
protected void returnQuarter() {
System.out.println("Returning coin...");
}
}
Wie aus dem Code ersichtlich ist, liefert der Süßigkeitenautomat je nach aktuellem Zustand unterschiedliche Ergebnisse für entsprechende Aktionen. Dieser Code kann unsere grundlegenden Anforderungen bereits erfüllen, aber wenn man ein wenig nachdenkt, hat man das Gefühl, dass dieser Implementierungscode zu kompliziert ist, schlecht erweiterbar ist und keinen objektorientierten Stil hat.
Angenommen, eine neue Anforderung zwingt uns, einen Zustand hinzuzufügen; dann müssen wir jede Aktionsmethode ändern und jedes Mal eine weitere else-Anweisung hinzufügen. Und wenn sich die Anforderungen ändern und eine Aktion in einem bestimmten Zustand geändert werden muss, müssen wir außerdem vier Methoden gleichzeitig ändern. Solche Arbeit wäre mühsam und kopfschmerzverursachend.
Was tun? Eines der sechs Designprinzipien
Identifizieren Sie die Aspekte Ihrer Anwendung, die sich ändern können, und trennen Sie sie.
Im Süßigkeitenautomaten ist der Zustand der Teil, der sich ständig ändert; verschiedene Zustände haben verschiedene Aktionen. Wir können ihn auf jeden Fall extrahieren.

Die neue Designidee ist wie folgt:
Zuerst definieren wir eine State-Schnittstelle. In dieser Schnittstelle hat jede Aktion des Süßigkeitenautomaten eine entsprechende Methode.
Dann implementieren wir eine Zustandsklasse für jeden Zustand des Automaten. Diese Klassen sind für das Verhalten des Automaten im entsprechenden Zustand verantwortlich.
Schließlich müssen wir den alten Bedingungscode loswerden und ihn dadurch ersetzen, dass wir Aktionen an die Zustandsklassen delegieren.
Eine State-Schnittstelle definieren
Beispiel
/**
* Münze einwerfen
*/
public abstract void insertQuarter();
/**
* Münze zurückgeben
*/
public abstract void ejectQuarter();
/**
* Kurbel drehen, um Süßigkeit auszugeben
*/
public abstract void turnCrank();
/**
* Süßigkeit ausgeben
*/
public abstract void dispense();
/**
* Münze zurückgeben
*/
protected void returnQuarter() {
System.out.println("Returning coin...");
}
}
Implementiere Zustandsklassen für jeden Zustand der Maschine
Beispiel
* Zustand ohne Münze
*/
public class NoQuarterState extends State{
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("Sie haben eine Münze eingeworfen");
//Übergang zum Zustand „Münze eingeworfen“
gumballMachine.setState(gumballMachine.hasQuarterState);
}
@Override
public void ejectQuarter() {
System.out.println("Keine Münze, Auswurf nicht möglich");
}
@Override
public void turnCrank() {
System.out.println("Bitte zuerst eine Münze einwerfen");
}
@Override
public void dispense() {
System.out.println("Keine Münze eingeworfen, Süßigkeit kann nicht ausgegeben werden");
}
}
/**
* Zustand: Münze eingeworfen
*/
public class HasQuarterState extends State{
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("Bitte werfen Sie nicht wiederholt eine Münze ein!");
returnQuarter();
}
@Override
public void ejectQuarter() {
returnQuarter();
gumballMachine.setState(gumballMachine.noQuarterState);
}
@Override
public void turnCrank() {
System.out.println("Kurbel drehen, bereit, Süßigkeit auszugeben");
gumballMachine.setState(gumballMachine.soldState);
}
@Override
public void dispense() {
System.out.println("Diese Methode wird nicht unterstützt");
}
}
/**
* Zustand: verkauft
*/
public class SoldState extends State{
GumballMachine gumballMachine;
public SoldState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("Münze eingeworfen, bitte warten Sie auf die Süßigkeit");
returnQuarter();
}
@Override
public void ejectQuarter() {
System.out.println("Münze kann nicht zurückgegeben werden, Süßigkeit wird ausgegeben, bitte warten");
}
@Override
public void turnCrank() {
System.out.println("Kurbel wurde bereits gedreht, bitte warten");
}
@Override
public void dispense() {
int candyCount = gumballMachine.getCandyCount();
if(candyCount > 0){
System.out.println("Eine Süßigkeit wird ausgegeben");
candyCount--;
gumballMachine.setCandyCount(candyCount);
if(candyCount > 0){
gumballMachine.setState(gumballMachine.noQuarterState);
return;
}
}
System.out.println("Entschuldigung, Süßigkeit ist ausverkauft");
gumballMachine.setState(gumballMachine.soldOutState);
}
}
/**
* Ausverkaufter Zustand
*/
public class SoldOutState extends State{
GumballMachine gumballMachine;
public SoldOutState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("Süßigkeit ist ausverkauft");
returnQuarter();
}
@Override
public void ejectQuarter() {
System.out.println("Keine Münze eingeworfen, Rückgabe nicht möglich");
}
@Override
public void turnCrank() {
System.out.println("Süßigkeit ist ausverkauft");
}
@Override
public void dispense() {
System.out.println("Süßigkeit ist ausverkauft");
}
}
Delegieren Sie die Aktionen des Süßigkeitenautomaten an die Zustandsklasse
Beispiel
public State noQuarterState = new NoQuarterState(this);
public State hasQuarterState = new HasQuarterState(this);
public State soldState = new SoldState(this);
public State soldOutState = new SoldOutState(this);
private State state = soldOutState;
private int candyCount = 0;
public GumballMachine(int count) {
this.candyCount = count;
if(count > 0)
setState(noQuarterState);
}
@Override
public void insertQuarter() {
state.insertQuarter();
}
@Override
public void ejectQuarter() {
state.ejectQuarter();
}
@Override
public void turnCrank() {
state.turnCrank();
}
@Override
public void dispense() {
state.dispense();
}
public void setState(State state) {
this.state = state;
}
public State getState() {
return state;
}
public void setCandyCount(int candyCount) {
this.candyCount = candyCount;
}
public int getCandyCount() {
return candyCount;
}
}
Man erkennt, dass die Süßigkeitenmaschine bei diesem Design überhaupt nichts über Zustandsänderungen wissen muss; sie muss nur die Methoden des Zustands aufrufen. Zustandsänderungen geschehen innerhalb des Zustands. Dies ist das „State Pattern“.
Wenn an dieser Stelle ein weiterer Zustand hinzugefügt wird, muss die Süßigkeitenmaschine keine Änderungen vornehmen; wir müssen nur eine weitere Zustandsklasse hinzufügen und dann den Übergangsprozess in den Methoden der zugehörigen Zustandsklasse ergänzen.
Nicht verstanden? Es gibt auch einen Artikel über das State Pattern:https://dzone.com/articles/state-pattern-simplified
Original-Link: https://blog.csdn.net/z55887/article/details/73198039
Original-Link: https://blog.csdn.net/z55887/article/details/60608898