Padrão State
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 100 de 104.
O padrão State permite que um objeto altere seu comportamento quando seu estado interno muda, fazendo com que pareça que o objeto mudou de classe. Em vez de usar instruções condicionais complexas para lidar com diferentes estados, você encapsula cada estado como uma classe separada.
O padrão consiste em um Context que mantém uma referência ao estado atual, uma interface State que define o comportamento específico do estado e Concrete States que implementam o comportamento para cada estado:
#include <iostream>
#include <memory>
class Document; // Declaração antecipada
// Interface de Estado
class DocumentState {
public:
virtual void publish(Document& doc) = 0;
virtual std::string getName() const = 0;
virtual ~DocumentState() = default;
};
// Contexto
class Document {
std::unique_ptr<DocumentState> state;
public:
Document();
void setState(std::unique_ptr<DocumentState> newState) {
state = std::move(newState);
}
void publish() { state->publish(*this); }
std::string getStateName() const { return state->getName(); }
};
// Estados Concretos
class Draft : public DocumentState {
public:
void publish(Document& doc) override;
std::string getName() const override { return "Draft"; }
};
class Review : public DocumentState {
public:
void publish(Document& doc) override;
std::string getName() const override { return "Review"; }
};
class Published : public DocumentState {
public:
void publish(Document& doc) override {
std::cout << "Already published\n";
}
std::string getName() const override { return "Published"; }
};
void Draft::publish(Document& doc) {
std::cout << "Moving to review\n";
doc.setState(std::make_unique<Review>());
}
void Review::publish(Document& doc) {
std::cout << "Publishing document\n";
doc.setState(std::make_unique<Published>());
}
Document::Document() : state(std::make_unique<Draft>()) {}Cada estado lida com a ação publish() de forma diferente e é responsável por transitar para o próximo estado. O Document não precisa conhecer a lógica de transição - ele simplesmente delega para o estado atual. Isso elimina grandes instruções switch e torna a adição de novos estados direta.
Use o State quando o comportamento de um objeto depende fortemente de seu estado e você tem muitas instruções condicionais que alternam com base no estado do objeto.
Desafio
FácilVamos construir um Controlador de Semáforo usando o padrão State. Você criará um sistema onde um semáforo alterna entre diferentes estados (Red, Yellow, Green), com cada estado determinando o que acontece quando a luz muda e qual mensagem é exibida. Este é um exemplo clássico do padrão State — o comportamento do semáforo depende inteiramente do seu estado atual.
Você organizará seu código em três arquivos:
TrafficLightState.h: Define a interface de estado e todas as classes de estado concretas.Crie uma classe abstrata
TrafficLightStatecom:- Um método virtual puro
change(TrafficLight& light)que lida com a transição para o próximo estado - Um método virtual puro
getColor()que retorna a cor atual como uma string - Um método virtual puro
getAction()que retorna o que os motoristas devem fazer (ex: "Stop", "Caution", "Go") - Um destrutor virtual
Implemente três estados concretos:
RedState— a cor é"Red", a ação é"Stop", muda para GreenYellowState— a cor é"Yellow", a ação é"Caution", muda para RedGreenState— a cor é"Green", a ação é"Go", muda para Yellow
Você precisará de uma declaração antecipada (forward declaration) para
TrafficLight, já que os estados fazem referência a ela.- Um método virtual puro
TrafficLight.h: Crie a classe de contexto que mantém o estado atual.Sua classe
TrafficLightdeve conter umstd::unique_ptr<TrafficLightState>e começar no estado Red por padrão. Implemente:setState(std::unique_ptr<TrafficLightState> newState)— altera o estado atualchange()— delega para o método change do estado atualdisplay()— imprime o status do semáforo no formato:[Color]: [Action]
main.cpp: Demonstre o semáforo alternando entre os estados.Leia uma entrada: o número de mudanças de estado a serem realizadas (inteiro).
Crie um
TrafficLighte exiba seu estado inicial. Em seguida, realize o número especificado de mudanças, exibindo o estado após cada mudança.
Por exemplo, com a entrada 3:
Red: Stop
Green: Go
Yellow: Caution
Red: StopCom a entrada 6:
Red: Stop
Green: Go
Yellow: Caution
Red: Stop
Green: Go
Yellow: Caution
Red: StopObserve como o semáforo alterna entre seus estados em um padrão previsível: Red → Green → Yellow → Red. Cada estado sabe qual estado vem a seguir e lida com sua própria transição. A classe TrafficLight não precisa de nenhuma lógica condicional para determinar o que acontece — ela simplesmente delega para qualquer estado em que esteja atualmente.
Folha de consulta
O padrão State permite que um objeto altere seu comportamento quando seu estado interno muda. Em vez de usar instruções condicionais complexas, cada estado é encapsulado como uma classe separada.
O padrão consiste em três componentes:
- Interface State: Define o comportamento específico do estado
- Estados Concretos: Implementam o comportamento para cada estado
- Contexto: Mantém uma referência ao estado atual
Estrutura básica:
#include <memory>
class Context; // Forward declaration
// State interface
class State {
public:
virtual void handleAction(Context& ctx) = 0;
virtual std::string getName() const = 0;
virtual ~State() = default;
};
// Context
class Context {
std::unique_ptr<State> state;
public:
void setState(std::unique_ptr<State> newState) {
state = std::move(newState);
}
void performAction() {
state->handleAction(*this);
}
std::string getStateName() const {
return state->getName();
}
};
// Concrete State
class ConcreteState : public State {
public:
void handleAction(Context& ctx) override {
// Handle action and transition to next state
ctx.setState(std::make_unique<AnotherState>());
}
std::string getName() const override {
return "ConcreteState";
}
};Cada estado manipula as ações de forma diferente e é responsável pela transição para o próximo estado. O Contexto delega o comportamento ao estado atual sem precisar conhecer a lógica de transição.
Use o State quando o comportamento de um objeto depende fortemente de seu estado e você deseja evitar grandes instruções condicionais.
Experimente você mesmo
#include <iostream>
#include "TrafficLight.h"
using namespace std;
int main() {
// Lê o número de mudanças de estado
int numChanges;
cin >> numChanges;
// TODO: Criar um objeto TrafficLight
// TODO: Exibir o estado inicial
// TODO: Realizar o número especificado de mudanças
// Após cada mudança, exiba o estado atual
return 0;
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas11Conceitos Avançados de POO
Composição vs HerançaMixins via CRTPPimpl IdiomType ErasureEnum Classes e Tipagem ForteTratamento de Exceções em POOHierarquias de Exceções Personalizadas14Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como um Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico