dimanche 23 août 2020

Access from state class to private fields of context class

I am confused about state pattern implementation. Under this pattern, we should extract state management into separate classes. At first glance it allows us to avoid big if ... else ... constructions inside domain entity and it really powerful advantage. We can move all condition checks into state classes and clear our domain entity class.

But how to modify the data encapsulated in the domain object without encapsulation principle violation?

For example, consider the Account entity. In a simplified way, it has two possible states - Active and Blocked and methods for money deposit and withdrawal.

Under the state pattern, we should delegate deposit and withdrawal responsibilities to state classes. UML diagram here.

But how can we modify the money and state fields from AccountState implementations? I see only the way where I have public setters for it. But it violates the encapsulation principle. With this approach, I can also change private fields to the public.

Code example:

class Account {
  private int money;
  private AccountState state;

  public Account() {
    this.money = 0;
    this.state = new Active();
  }

  public void deposit(int amount) {
    this.state.deposit(this, amount);
  }

  public void withdraw(int amount) {
    this.state.withdraw(this, amount);
  }

  public int getMoney() {
    return this.money;
  }

  public AccountState getState() {
    return this.state;
  }
}
interface AccountState {
  public void deposit(Account account, int amount);
  public void withdraw(Account account, int amount);
}
class Active implements AccountState {
  public void deposit(Account account, int amount) {
    // How to change account money and state without setters and public fields usage?
  }
  
  public void withdraw(Account account, int amount) {
    if (account.getState() instanceof Blocked) {
      throw new RuntimeException("Money could not be withdrawn. Account is blocked.");
    }

    if (account.getMoney() - amount <= 0) {
      throw new RuntimeException("Money could not be withdrawn. Insufficient funds.");
    }

    // How to change account money and state without setters and public fields usage?
  }
}

class Blocked implements AccountState {
  public void deposit(Account account, int amount) {
    // How to change account money and state without setters and public fields usage?
  }

  public void withdraw(Account account, int amount) {
    if (account.getMoney() - amount <= 0) {
      throw new RuntimeException("Money could not be withdrawn. Insufficient funds.");
    }

    // How to change account money and state without setters and public fields usage?
  }
}

This is a very simplified example, but it well reflected my problem. Unfortunately, I couldn't found a good solution for it. All examples that I saw use either public setters or public fields. Also, I saw an example from the Refactoring to Patterns book by Joshua Kerievsky. He offers to use setters with package-level access (without access modifiers like private, public, or protected). So, we can change entity data from state classes located in the same package with the domain entity and can not do it from other packages. But this approach is using the language-specific feature - package-level access. In other languages like PHP, it wouldn't work. I'm looking for a conceptual solution.

Can anyone show a real production example solving this problem? I would really appreciate it.

Aucun commentaire:

Enregistrer un commentaire