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 useAtmMachine()
because the constructor needs to beconst
. - 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?