如何用飞镖增强的枚举应用状态模式

发布于 2025-02-10 17:19:37 字数 4772 浏览 1 评论 0 原文

DART 2.17现在有了增强的枚举。 matt Carroll在他的评论中提到的您可以使用新的枚举来实现状态模式。我对此有麻烦,因为我在状态模式和增强的枚举方面都很弱。

这是状态模式的一个示例,没有这个youtube视频我转换为DART:

// https://www.youtube.com/watch?v=MGEx35FjBuo&t=12s

void main() {
  final atmMachine = AtmMachine();
  atmMachine.insertCard();
  atmMachine.ejectCard();
  atmMachine.insertCard();
  atmMachine.insertPin(1234);
  atmMachine.requestCash(2000);
  atmMachine.insertCard();
  atmMachine.insertPin(1234);
}

abstract class AtmState {
  void insertCard();
  void ejectCard();
  void insertPin(int pin);
  void requestCash(int cashToWithdraw);
}

class AtmMachine {
  AtmMachine() {
    _hasCard = HasCard(this);
    _noCard = NoCard(this);
    _hasPin = HasPin(this);
    _outOfMoney = NoCash(this);

    atmState = _noCard;

    if (cashInMachine <= 0) {
      atmState = _outOfMoney;
    }
  }

  late AtmState _hasCard;
  late AtmState _noCard;
  late AtmState _hasPin;
  late AtmState _outOfMoney;

  late AtmState atmState;

  int cashInMachine = 2000;
  bool correctPinEntered = false;

  void setNewAtmState(AtmState value) {
    atmState = value;
  }

  void setCashInMachine(int value) {
    cashInMachine = value;
  }

  void insertCard() {
    atmState.insertCard();
  }

  void ejectCard() {
    atmState.ejectCard();
  }

  void requestCash(int amount) {
    atmState.requestCash(amount);
  }

  void insertPin(int pin) {
    atmState.insertPin(pin);
  }

  AtmState get hasCardState => _hasCard;
  AtmState get noCardState => _noCard;
  AtmState get hasPinState => _hasPin;
  AtmState get outOfMoneyState => _outOfMoney;
}

class HasCard implements AtmState {
  HasCard(this.atmMachine);
  final AtmMachine atmMachine;

  @override
  void ejectCard() {
    print('Card ejected');
    atmMachine.setNewAtmState(atmMachine.noCardState);
  }

  @override
  void insertCard() {
    print('You cannot enter more than one card.');
  }

  @override
  void insertPin(int pin) {
    if (pin == 1234) {
      print('Correct pin');
      atmMachine.correctPinEntered = true;
      atmMachine.setNewAtmState(atmMachine.hasPinState);
    } else {
      print('Wrong pin');
      atmMachine.correctPinEntered = false;
      print('Card ejected');
      atmMachine.setNewAtmState(atmMachine.noCardState);
    }
  }

  @override
  void requestCash(int cashToWithdraw) {
    print('Enter pin first');
  }
}

class NoCard implements AtmState {
  NoCard(this.atmMachine);
  final AtmMachine atmMachine;

  @override
  void ejectCard() {
    print('Please enter a card first.');
  }

  @override
  void insertCard() {
    print('Please enter a pin.');
    atmMachine.setNewAtmState(atmMachine.hasCardState);
  }

  @override
  void insertPin(int pin) {
    print('Please enter a card first.');
  }

  @override
  void requestCash(int cashToWithdraw) {
    print('Please enter a card first.');
  }
}

class HasPin implements AtmState {
  HasPin(this.atmMachine);
  final AtmMachine atmMachine;

  @override
  void ejectCard() {
    print('Card ejected');
    atmMachine.setNewAtmState(atmMachine.noCardState);
  }

  @override
  void insertCard() {
    print('You cannot enter more than one card.');
  }

  @override
  void insertPin(int pin) {
    print('Already entered pin');
  }

  @override
  void requestCash(int cashToWithdraw) {
    if (cashToWithdraw > atmMachine.cashInMachine) {
      print('There is not enough cash in this machine');
      ejectCard();
    } else {
      print('You got $cashToWithdraw dollars');
      atmMachine.setCashInMachine(atmMachine.cashInMachine - cashToWithdraw);
      ejectCard();
      if (atmMachine.cashInMachine <= 0) {
        atmMachine.setNewAtmState(atmMachine.outOfMoneyState);
      }
    }
  }
}

class NoCash implements AtmState {
  NoCash(this.atmMachine);
  final AtmMachine atmMachine;

  @override
  void ejectCard() {
    print('We do not have money');
  }

  @override
  void insertCard() {
    print('We do not have money');
  }

  @override
  void insertPin(int pin) {
    print('We do not have money');
  }

  @override
  void requestCash(int cashToWithdraw) {
    print('We do not have money');
  }
}

我的下一步将是将状态类转换为枚举,但这是我遇到的困难:

  • 我无法将 atmmachine 上下文传递给枚举constructor因为我需要为每个枚举状态都有一个初始值,并且我无法使用 atmmachine(),因为构造函数需要为 const
  • 我想我可以将对 atmmachine 的引用传递到每个方法中,然后对状态进行开关,但是该状态模式应该如何工作?状态模式不应该避免大量的开关语句吗?还是在州以外?

有没有人使用DART的增强枚举来实施状态模式?

Dart 2.17 now has enhanced enums. Matt Carroll mentioned in his review that you could use the new enums to implement the State Pattern. I'm having trouble with this because I'm weak on both the State Pattern and on enhanced enums.

Here is an example of the State Pattern without enums from this YouTube video that I converted to Dart:

// https://www.youtube.com/watch?v=MGEx35FjBuo&t=12s

void main() {
  final atmMachine = AtmMachine();
  atmMachine.insertCard();
  atmMachine.ejectCard();
  atmMachine.insertCard();
  atmMachine.insertPin(1234);
  atmMachine.requestCash(2000);
  atmMachine.insertCard();
  atmMachine.insertPin(1234);
}

abstract class AtmState {
  void insertCard();
  void ejectCard();
  void insertPin(int pin);
  void requestCash(int cashToWithdraw);
}

class AtmMachine {
  AtmMachine() {
    _hasCard = HasCard(this);
    _noCard = NoCard(this);
    _hasPin = HasPin(this);
    _outOfMoney = NoCash(this);

    atmState = _noCard;

    if (cashInMachine <= 0) {
      atmState = _outOfMoney;
    }
  }

  late AtmState _hasCard;
  late AtmState _noCard;
  late AtmState _hasPin;
  late AtmState _outOfMoney;

  late AtmState atmState;

  int cashInMachine = 2000;
  bool correctPinEntered = false;

  void setNewAtmState(AtmState value) {
    atmState = value;
  }

  void setCashInMachine(int value) {
    cashInMachine = value;
  }

  void insertCard() {
    atmState.insertCard();
  }

  void ejectCard() {
    atmState.ejectCard();
  }

  void requestCash(int amount) {
    atmState.requestCash(amount);
  }

  void insertPin(int pin) {
    atmState.insertPin(pin);
  }

  AtmState get hasCardState => _hasCard;
  AtmState get noCardState => _noCard;
  AtmState get hasPinState => _hasPin;
  AtmState get outOfMoneyState => _outOfMoney;
}

class HasCard implements AtmState {
  HasCard(this.atmMachine);
  final AtmMachine atmMachine;

  @override
  void ejectCard() {
    print('Card ejected');
    atmMachine.setNewAtmState(atmMachine.noCardState);
  }

  @override
  void insertCard() {
    print('You cannot enter more than one card.');
  }

  @override
  void insertPin(int pin) {
    if (pin == 1234) {
      print('Correct pin');
      atmMachine.correctPinEntered = true;
      atmMachine.setNewAtmState(atmMachine.hasPinState);
    } else {
      print('Wrong pin');
      atmMachine.correctPinEntered = false;
      print('Card ejected');
      atmMachine.setNewAtmState(atmMachine.noCardState);
    }
  }

  @override
  void requestCash(int cashToWithdraw) {
    print('Enter pin first');
  }
}

class NoCard implements AtmState {
  NoCard(this.atmMachine);
  final AtmMachine atmMachine;

  @override
  void ejectCard() {
    print('Please enter a card first.');
  }

  @override
  void insertCard() {
    print('Please enter a pin.');
    atmMachine.setNewAtmState(atmMachine.hasCardState);
  }

  @override
  void insertPin(int pin) {
    print('Please enter a card first.');
  }

  @override
  void requestCash(int cashToWithdraw) {
    print('Please enter a card first.');
  }
}

class HasPin implements AtmState {
  HasPin(this.atmMachine);
  final AtmMachine atmMachine;

  @override
  void ejectCard() {
    print('Card ejected');
    atmMachine.setNewAtmState(atmMachine.noCardState);
  }

  @override
  void insertCard() {
    print('You cannot enter more than one card.');
  }

  @override
  void insertPin(int pin) {
    print('Already entered pin');
  }

  @override
  void requestCash(int cashToWithdraw) {
    if (cashToWithdraw > atmMachine.cashInMachine) {
      print('There is not enough cash in this machine');
      ejectCard();
    } else {
      print('You got $cashToWithdraw dollars');
      atmMachine.setCashInMachine(atmMachine.cashInMachine - cashToWithdraw);
      ejectCard();
      if (atmMachine.cashInMachine <= 0) {
        atmMachine.setNewAtmState(atmMachine.outOfMoneyState);
      }
    }
  }
}

class NoCash implements AtmState {
  NoCash(this.atmMachine);
  final AtmMachine atmMachine;

  @override
  void ejectCard() {
    print('We do not have money');
  }

  @override
  void insertCard() {
    print('We do not have money');
  }

  @override
  void insertPin(int pin) {
    print('We do not have money');
  }

  @override
  void requestCash(int cashToWithdraw) {
    print('We do not have money');
  }
}

My next step was going to be to convert the state class to an enum, but here are the difficulties I had:

  • I can't pass in the AtmMachine context to the enum constructor because I would need to have an initial value for each of the enum states and I couldn't use AtmMachine() because the constructor needs to be const.
  • I suppose I could pass a reference to AtmMachine into each of the methods and then do a switch on the state, but is that how the State Pattern is supposed to work? Isn't the State Pattern supposed to avoid lots of switch statements? Or is that just outside the state?

Does anyone have an example of implementing the State Pattern with Dart's enhanced enums?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

酒解孤独 2025-02-17 17:19:37

状态模式

对于我的普通开发人员来说,模式通常会产生误导。您的问题使我将“状态模式”与实现“有限状态机”相关联。这并不是状态模式的目的。相反,它允许根据其为状态修改对象的行为。这意味着在对象上调用相同的功能将具有不同的效果/结果/结果,具体取决于其当前状态。

在您的示例中,ATM有限状态机看起来有点像这样:

这不是完整的,也不是100%准确,只是为了说明这一点。您还需要多个操作,因此您需要根据每个状态处理每个操作。与往常一样,根据用例,您应该避免尝试以任何代价应用模式(“我可以吗?”与“我应该这样做吗?”)。

此QA 对枚举与状态模式有一些有趣的见解。

第一个说明是,当您从当前状态有多个过渡时,无论您使用哪种语言或模式,都将有一个条件检查以确定下一个状态。在这种情况下,, switches , bools 等。当过渡从一个状态到另一个状态唯一时,状态模式将避免这些模式(请参见上面的Java文档)。这是DART中的类似示例:

dart中的状态模式

增强的枚举
这些基本上是成员的枚举。显然已经有其他语言可用(例如Java),现在已经在飞镖中实现。在规格中,我们可以看到枚举如何将“ desugars”“纳入一类”。重要的是要注意:在写作时仍有一个限制,可以防止向枚举成员添加函数:
https://github.com/dart-lang/lang/lang/langyage/lange/sissues/2241
https://github.com/dart-lang/lang/lange/lange/lange/lange/lange/sissues/1048

但是,可以通过解决方法进行操作(这是我在下面的代码中所做的;请参阅上述问题2241的评论: https://github.com/dart-lang/lang/lange/lange/sissues/2241#issuecomment-1126322956 )。

在状态模式中使用
为了说明这一点,我在上面的Dart(Lightswitch)中以示例模式进行了示例模式,并用枚举代替了州类。代码的其余部分几乎相同。我删除了一些东西,例如 toString()替代,因为这很容易理解:

abstract class State {
  void handler(Stateful context);
}

// This is the enum that replace the different states class.
// Each state is an enum member. As you can see, the "static function" trick
// is required to associate a handler function to each state. Otherwise, code
// would be even simpler.
enum MyState implements State {
  stateOff("off", "  Handler of StatusOff is being called!", setStatusOn),
  stateOn("on", "  Handler of StatusOn is being called!", setStatusOff);
  
  final String name;
  final String message;
  final void Function(Stateful) switchStatus;
  
  const MyState(this.name, this.message, this.switchStatus);
  
  @override
  void handler(Stateful context){
    print(message);
    switchStatus(context);
  }
  
  static void setStatusOff(Stateful ctx) => ctx.state = stateOff;
  static void setStatusOn(Stateful ctx) => ctx.state = stateOn;
}

// From here, the rest of the code is exactly the same as in the example.
// This illustrates how State class implementations can be replaced with
// the enhanced enum above.
class Stateful {
  State _state;

  Stateful(this._state);

  State get state => _state;
  set state(State newState) => _state = newState;

  void touch() {
    print("  Touching the Stateful...");
    _state.handler(this);
  }
}

void main() {
  var lightSwitch = Stateful(MyState.stateOff);
  print("The light switch is ${lightSwitch.state}.");
  print("Toggling the light switch...");
  lightSwitch.touch();
  print("The light switch is ${lightSwitch.state}.");
}

作为参考,如果存储库消失,则这些是已被示例中的枚举替换的状态类:

class StatusOn implements State {
  handler(Stateful context) {
    print("  Handler of StatusOn is being called!");
    context.state = StatusOff();
  }

  @override String toString() {
    return "on";
  }
}

class StatusOff implements State {
  handler(Stateful context) {
    print("  Handler of StatusOff is being called!");
    context.state = StatusOn();
  }

  @override String toString() {
    return "off";
  }
}

State pattern

To the average developer that I am, patterns are often misleading. Your question made me associate the "State pattern" with a way to implement a "Finite state machine". This is not exactly the purpose of the state pattern. Instead, it allows to modify the behaviour of an object based on the state it is. This means calling the same function on the object will have a different effect/result/consequence depending on which is its current state.

Basic explanation (Java)

In your example, the ATM finite state machine looks a bit like this:

enter image description here

This is not complete nor 100% accurate, it's only to illustrate the point. You also have more than one operation, so you need to handle each operation based on each state. As always, depending on the use case, you should avoid trying to apply a pattern at any cost ("can I do it?" vs. "should I do it?").

This QA has some interesting insight about enums vs state pattern.

A first remark is that when you have more than one transitions possible from your current state, no matter which language or pattern you use, there will be a condition to be checked to determine what will be the next state. In this case ifs, switches, bools etc. will be required anyway. The State pattern will avoid these when the transitions are unique from one state to the other (see Java document above). Here's a similar example in Dart:

State pattern in Dart

Enhanced enums
These are basically enums with members. Apparently already available in other languages (ex. Java), this has now been implemented in Dart. In the specs, we can see how the enum "desugars" into a class. Important to note: there is at the time of writting still a limitation that prevents adding a function to an enum member:
https://github.com/dart-lang/language/issues/2241
https://github.com/dart-lang/language/issues/1048

However, it is possible to do it with a workaround (this is what I did in my code below; see this comment on issue 2241 above: https://github.com/dart-lang/language/issues/2241#issuecomment-1126322956).

Usage in State pattern
To illustrate this, I took the example pattern in Dart above (Lightswitch) and replaced the State classes with an enum. The rest of the code is pretty much the same. I removed some stuff like the toString() override as this is simple to understand:

abstract class State {
  void handler(Stateful context);
}

// This is the enum that replace the different states class.
// Each state is an enum member. As you can see, the "static function" trick
// is required to associate a handler function to each state. Otherwise, code
// would be even simpler.
enum MyState implements State {
  stateOff("off", "  Handler of StatusOff is being called!", setStatusOn),
  stateOn("on", "  Handler of StatusOn is being called!", setStatusOff);
  
  final String name;
  final String message;
  final void Function(Stateful) switchStatus;
  
  const MyState(this.name, this.message, this.switchStatus);
  
  @override
  void handler(Stateful context){
    print(message);
    switchStatus(context);
  }
  
  static void setStatusOff(Stateful ctx) => ctx.state = stateOff;
  static void setStatusOn(Stateful ctx) => ctx.state = stateOn;
}

// From here, the rest of the code is exactly the same as in the example.
// This illustrates how State class implementations can be replaced with
// the enhanced enum above.
class Stateful {
  State _state;

  Stateful(this._state);

  State get state => _state;
  set state(State newState) => _state = newState;

  void touch() {
    print("  Touching the Stateful...");
    _state.handler(this);
  }
}

void main() {
  var lightSwitch = Stateful(MyState.stateOff);
  print("The light switch is ${lightSwitch.state}.");
  print("Toggling the light switch...");
  lightSwitch.touch();
  print("The light switch is ${lightSwitch.state}.");
}

As reference in case the repo disappears, these are the State classes that have been replaced with the enum in the example:

class StatusOn implements State {
  handler(Stateful context) {
    print("  Handler of StatusOn is being called!");
    context.state = StatusOff();
  }

  @override String toString() {
    return "on";
  }
}

class StatusOff implements State {
  handler(Stateful context) {
    print("  Handler of StatusOff is being called!");
    context.state = StatusOn();
  }

  @override String toString() {
    return "off";
  }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文