State

Моделът на проектиране на състоянието позволява промяна на поведението на обект при промяна на неговото вътрешно състояние. Обикновено различните състояния са обособени в отделни класове и поведението се делегира на текущото състояние.

State елиминира условната логика чрез полиморфизъм. Неговото използване спестява писането на множество условни конструкции, силно разклонена логика и нарушаването на OCP.

За реализация на шаблона State са необходими контекст (обект, чието поведение се променя), състояние (описвано чрез интерфейс, дефиниращ поведението на контекста) и конкретни състояния (представляващи реализации на дадено състояние).

Основни характеристики на State:

  • поведението на обекта варира според състоянието

  • преходите между различните състояния могат да бъдат контролирани или от самите състояния, или от контекста (съществуват различни имплементации)

  • използва композиция и полиморфизъм

  • намалява присъствието на условна логика.

Нека разгледаме пример за билет, който може да бъде резервиран, закупен и отказан. Реализацията по-долу е без използване на шаблона State.


public class Ticket {

    private String ticketState;
    private int ticketId;

    public Ticket(int ticketId) {
        this.ticketState = "available";
        this.ticketId = ticketId;
    }

    public String book() {
        if (ticketState.equalsIgnoreCase("available")) {
            ticketState = "booked";
            return "Ticket with id = " + this.ticketId + " has been booked";
        } else if (ticketState.equalsIgnoreCase("booked")) {
            return "Ticket with id = " + this.ticketId + " has been already booked";
        } else if (ticketState.equalsIgnoreCase("sold")) {
            return "Ticket with id = " + this.ticketId + " has been already sold";
        }
        return "Unrecognized state";
    }

    public String buy() {
        if (ticketState.equalsIgnoreCase("available") ||
				ticketState.equalsIgnoreCase("booked")) {
            ticketState = "sold";
            return "Ticket with id = " + this.ticketId + " has been sold";
        } else if (ticketState.equalsIgnoreCase("sold")) {
            return "Ticket with id = " + this.ticketId + " has been already sold";
        }
        return "Unrecognized state";
    }

    public String cancel() {
        if (ticketState.equalsIgnoreCase("available")) {
            return "Available ticket cannot be cancelled";
        } else if (ticketState.equalsIgnoreCase("booked")) {
            ticketState = "available";
            return "Ticket with id = " + this.ticketId + " has been cancelled";
        } else if (ticketState.equalsIgnoreCase("sold")) {
            return "Sold ticket cannot be cancelled";
        }
        return "Unrecognized state";
    }

}

public class Application {

    public static void main(String[] args) {
        Ticket ticket = new Ticket(123);
        System.out.println(ticket.book());
        System.out.println(ticket.cancel());
        System.out.println(ticket.buy());
        System.out.println(ticket.cancel());

    }
}

Полученият резултат от изпълнението на горния пример е:

Ticket with id = 123 has been booked
Ticket with id = 123 has been cancelled
Ticket with id = 123 has been sold
Sold ticket cannot be cancelled

По-долу е показан същия пример, но реализиран с използване на шаблона State:

public interface TicketState {

    String bookTicket(Ticket ticket);

    String buyTicket(Ticket ticket);

    String cancelTicket(Ticket ticket);
}

Интерфейсът описва възможните състояния на контекста (Ticket).

Всяко състояние представлява отделен клас.

public class AvailableTicket implements TicketState {

    @Override
    public String bookTicket(Ticket ticket) {
        ticket.setTicketState(new BookedTicket());
        return "Ticket with id = " + ticket.getTicketId() + " has been booked";
    }

    @Override
    public String buyTicket(Ticket ticket) {
        ticket.setTicketState(new SoldTicket());
        return "Ticket with id = " + ticket.getTicketId() + " has been sold";
    }

    @Override
    public String cancelTicket(Ticket ticket) {
        return "No ticket to be cancelled";
    }
}
public class BookedTicket implements TicketState {
    @Override
    public String bookTicket(Ticket ticket) {
        return "Ticket with id = " + ticket.getTicketId() + " is already booked";
    }

    @Override
    public String buyTicket(Ticket ticket) {
        ticket.setTicketState(new SoldTicket());
        return "Ticket with id = " + ticket.getTicketId() + " has been sold";
    }

    @Override
    public String cancelTicket(Ticket ticket) {
        ticket.setTicketState(new AvailableTicket());
        return "Ticket with id = " + ticket.getTicketId() + " has been cancelled";
    }
}
public class SoldTicket implements TicketState {
    @Override
    public String bookTicket(Ticket ticket) {
        return "Sold ticket cannot be booked";
    }

    @Override
    public String buyTicket(Ticket ticket) {
        return "Sold ticket cannot be sold";
    }

    @Override
    public String cancelTicket(Ticket ticket) {
        return "Sold ticket cannot be cancelled";
    }
}

Контекстният обект ще изглежда така:

public class Ticket {

    private TicketState ticketState = new AvailableTicket();
    private int ticketId;

    public Ticket(int ticketId) {
        this.ticketId = ticketId;
    }

    public int getTicketId() {
        return ticketId;
    }

    public void setTicketState(TicketState ticketState) {
        this.ticketState = ticketState;
    }

    public String book() {
        return ticketState.bookTicket(this);
    }

    public String buy() {
        return ticketState.buyTicket(this);
    }

    public String cancel() {
        return ticketState.cancelTicket(this);
    }
}

Тестването и резултата от реализирания код изглеждат по напълно идентичен начин.

Реализираният със State пример представя ясно разделение на ролите и при него състоянието не пази вътрешни данни.

Важно е да се отбележи, че State не е универсално решение за премахване на всякаква условна логика.

В заключение, използването на шаблона State е подходящо при решаване на задачи, при които:

  • има ясно разграничени състояния

  • поведението на обекта се променя

  • наблюдават се преходи между различни състояния

  • не желаем условна логика в контекста.


This site uses Just the Docs, a documentation theme for Jekyll.