1

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.

Al Ryz
  • 65
  • 1
  • 8

4 Answers4

0

Public setters (or actually setters in general regardless of access modifier) do not violate encapsulation. Encapsulation means we set up the class so only methods in the class with the variables can refer to the instance variables. In correctly encapsulated classes, callers are thus required to use these methods if they want to modify class fields.

JustAnotherDeveloper
  • 2,061
  • 2
  • 10
  • 24
  • I try not to use public setters because they allow anyone to change internal object data directly. If I, for example, add `public void setMoney(int amount)` method, someone can call it and change internal object data out of my control. I think if I want to allow to change the state of my object, I should implement appropriate method that would do it transactionally. – Al Ryz Aug 23 '20 at 09:26
  • But that's not a violation of encapsulation, which was your concern. If making the setter public is a problem for reasons other than breaking encapsulation (which again, setters don't break, even if they are public), then you can make the setter protected or whatever suits you and use what the language offers you to limit where and how it can be called. – JustAnotherDeveloper Aug 23 '20 at 09:32
0

To allow calls only from specific classes you could use reflection.

Example in Java: How to get the caller class in Java

Example in PHP: https://stackoverflow.com/a/6927569/724099

questioner
  • 1,144
  • 4
  • 14
  • 22
0

I would either:

  • Move money into AccountState (as the AccountState largely operates with money in this example)
  • Provide a method which manipulates Account in a way you prescribe. This may be through a method like Account#transact(String label, double amount), allowing you to manipulate the balance without exposing the member.
  • Remove AccountState as a redundant class, since the fields of a class are to represent the state of an object.

The second can be done through the function API as well, but do not confuse the mutability of a class member with breaking encapsulation; the purpose of encapsulation is to disallow unwanted behavior (like arbitrary math or access to internal collections). This prevents the class from entering an erroneous state.

Rogue
  • 11,105
  • 5
  • 45
  • 71
0

There are many ways to solve this problem, depending on exactly what you need each state instance to do. In this specific example, I would pass the field value of money into the AccountState rather than the entire Account object.

Here is an example using an enum, but obviously that could be two separate classes with an interface instead.

public class Account {
    private int balance = 0;
    private AccountState currentState = AccountState.ACTIVE;

    public int deposit(int amount) {
        balance = currentState.deposit(balance, amount);
        return balance;
    }

    public int withdraw(int amount) {
        balance = currentState.withdraw(balance, amount);
        return balance;
    }

    public AccountState activate() {
        this.currentState = AccountState.ACTIVE;
        return currentState;
    }

    public AccountState block() {
        this.currentState = AccountState.BLOCKED;
        return currentState;
    }

    enum AccountState {
        ACTIVE {
            @Override int deposit(int balance, int amount) {
                return balance + amount;
            }
            @Override int withdraw(int balance, int amount) {
                int newBalance = balance - amount;
                if (newBalance >= 0) {
                    return newBalance;
                }
                throw new IllegalArgumentException("Withdrawal amount is greater than balance.");
            }
        },
        BLOCKED {
            @Override int deposit(int balance, int amount) {
                throw new UnsupportedOperationException("Account is blocked.");
            }
            @Override int withdraw(int balance, int amount) {
                throw new UnsupportedOperationException("Account is blocked.");
            }
        };
        abstract int deposit(int balance, int amount);
        abstract int withdraw(int balance, int amount);
    }
}

One clue that the code in the OP will be difficult to apply OOP patterns to is that the business logic methods (deposit and withdraw) return void. It's difficult to do anything other than procedural programming with void methods. Make your methods return appropriate values and you will have an easier time composing classes that interact naturally.

jaco0646
  • 15,303
  • 7
  • 59
  • 83
  • It looks pretty good. You delegate only condition checks and calculations to state entity (enum in your example). But what if domain object is more complex and its state depends from more than one field? Pass them all in through method parameters? – Al Ryz Aug 24 '20 at 08:03
  • And how you can to perform state transition with this approach? In context class (`Account` in this example)? I think that power of state pattern is delegation all state-dependent logic (including state switching) to state class. – Al Ryz Aug 24 '20 at 08:19
  • There are decisions to make when implementing the State pattern. Should states know about their context? This example demonstrates they don't have to. The context can pass any object to the states rather than itself. Should states know about each other? This example demonstrates the context initiating state transitions. Conversely, it would be equally valid to pass the context into the states and link each state to the next. The pattern allows for all of these possibilities. You can choose which objects are appropriate to couple, depending on the use case. – jaco0646 Aug 24 '20 at 21:21
  • Sorry, I written a poor example. I meant that I want to delegate condition checking, context internal data mutation, and state switching into state class. In my example, it is excessively but it needed in cases where we have many states and complex transition conditions with not only state switching but with context internal data mutation. – Al Ryz Aug 26 '20 at 14:31
  • It sounds like you have a new question. I think the answer here satisfies the question that was asked. You should ask another that includes details of these additional requirements. – jaco0646 Aug 27 '20 at 13:31