Post: Validação de Dados com o Padrão State

Alessandro Medeiros

Fala ai Radizeiros e Radizeiras, tudo bom com vocês?

Quem nunca precisou tratar os estados de um venda, saber se seu caixa está aberto ou fechado, se aquele item está ativo ou não, e ai você acaba distribuído objetos para tudo que é lado, polui seu banco de dados com esses estados que alteram a cada momento, isso pode acarretar problemas posteriormente, se já não está acontecendo com você.

Quando trabalhamos orientado a objetos, usando os padrões de projetos de forma correta, tudo fica mais fácil de trabalhar.

Sabemos que trabalhar orientado a objeto é trabalhar com estados e comportamentos dos objetos, o padrão State, que estarei mostrando neste post, fornece meios mais simples de controlar esses estados.

Neste post irei lhe mostrar como validar dados usando o padrão de projeto state.

Olhando o diagrama você pode não compreender como esse padrão poderá lhe ajudar, mas como eu gosta de mostrar tudo usando exemplos reais.

Nesse exemplo irei vender itens usando o padrão de projeto State.

No diagrama ele pede uma interface, essa interface é apenas para os estados dos itens, para as operações que irão variar de acordo com o estado.

Por exemplo:

Possuo um item, e quais são os estados que o meu item pode ter?

O meu item pode estar ativo, inativo, vendido, e já que o item está vendido no cupom ele pode ter um comportamento diferente, se o item esta inativo, ele terá outro comportamento, se o item está ativo, ele irá se comportar de outra forma.

Visto isso iremos ter esses 3 estados, Ativo, Inativo, Vendido.

Agora vamos ao código

Primeiro devo criar minhas interfaces que terão o item e as operações.


iItem = interface
    ['{F7FD84E6-35D6-4609-A40C-E7FF93B3372E}']
    function State : iState<iItemOperacoes>;
    function Operacoes : iItemOperacoes;
end;

iItemOperacoes = interface
    ['{621F9457-EDFD-4251-B9BE-7E29FA3ACF4D}']
    function Vender : iItemOperacoes;
    function Cancelar : iItemOperacoes;
    function Desconto : iItemOperacoes;
    function Devolver : iItemOperacoes;
end;

A interface iItemOperacoes compartilha as operações de cada estado, quando o item está inativo ele irá ter todas essas operações, porém cada uma com o comportamento diferente.

Neste ponto iremos criar a classe que implementa a interface iItem:


type
    TModelItem = class(TInterfacedObject, iItem, iItemOperacoes, iState<iItemOperacoes>)
    private
        FState : iItemOperacoes;
    public
        constructor Create;
        destructor Destroy; override;
        class function New: iItem;
        function Vender: iItemOperacoes;
        function Cancelar: iItemOperacoes;
        function Desconto: iItemOperacoes;
        function Devolver: iItemOperacoes;
        function SetState(Value : iItemOperacoes) : iItemOperacoes;
        function State : iState<iItemOperacoes>;
        function Operacoes : iItemOperacoes;
    end;

implementation

{ TModelItem }

function TModelItem.Cancelar: iItemOperacoes;
begin
    FState.Cancelar;
    //implementa o codigo de cancelamento
    FState := TModelItemAtivo.New;//Após realizar todos as operações de cancelamento retorna para ativo
    Result := Self;
end;

constructor TModelItem.Create;
begin
    FState := TModelItemAtivo.New;
end;

function TModelItem.Desconto: iItemOperacoes;
begin
    FState.Desconto;
    //implementa o codigo de cancelamento
    Result := Self;
end;

destructor TModelItem.Destroy;
begin

inherited;
end;

function TModelItem.Devolver: iItemOperacoes;
begin
    FState.Devolver;
    //implementa o codigo de cancelamento
    FState := TModelItemAtivo.New;//Após realizar todos as operações de devolução retorna para ativo
    Result := Self;
end;

class function TModelItem.New: iItem;
begin
    Result := Self.Create;
end;

function TModelItem.Operacoes: iItemOperacoes;
begin
    Result := self;
end;

function TModelItem.SetState(Value: iItemOperacoes): iItemOperacoes;
begin
    Result := Self;
    FState := Value;
end;

function TModelItem.State: iState<iItemOperacoes>;
begin
    Result := Self;
end;

function TModelItem.Vender: iItemOperacoes;
begin
    FState.Vender;
    //implementa o codigo de cancelamento
    FState := TModelItemVendido.New;//Após realizar todos as operações de venda recebe o estado de vendido
    Result := Self;
end;

Implementamos os métodos e tratamos os métodos referente aos estados.

Mas você deve esta se perguntando, afinal de contas como irei armazenar os estados do item?

Calma radizeiro, para que possamos implementar o estado iremos precisar de uma interface, neste caso iremos criar uma interface genérica, onde todas as classes do nosso código que quisermos que elas implementem o estado ela irá implementar essa interface.


...

iState<T> = interface
    ['{FAF29342-D2DE-4C63-84B0-F96AC498518A}']
    function SetState(Value : T) : T;
end;

...

Viu o que foi feito?

Nós criamos o item(interface = iItem), separamos as operações do item em uma outra interface(inerface = iItemOpecacoes), e criamos uma interface de estado(interface = iState).

Uma pequena analise

Na nossa classe de item você pode ver que colocamos a interface iState<iItemOperacoes>, pois as operações que são responsaveis por tratar os estados.


...

type
    TModelItem = class(TInterfacedObject, iItem, iItemOperacoes, iState<iItemOperacoes>)
    private
    FState : iItemOperacoes;

...

Nossa classe item implementa os métodos de nossa interface de estados, para que possamos trabalhar esses métodos iremos precisar de um objeto, o qual chamamos de FState do tipo iItemOperacoes.


type

...

private

    FState : iItemOperacoes;

...

    function Vender: iItemOperacoes;
    function Cancelar: iItemOperacoes;
    function Desconto: iItemOperacoes;
    function Devolver: iItemOperacoes;

...

Olha o que foi feito até agora

Criei um item, separei as funções que dependem de um estado, lembra o que disse, se for um ativo irá ter um comportamento, inativo outro, e por ai vai, cada ação que depende de um estado do objeto nós separamos em uma interface diferente, tenho meu item, suas ações separadas, e por ultimo possuo uma interface genérica que irá trabalhar com qualquer estado de qualquer objeto, em nosso caso ela esta implementando o iItemOperacoes


...

function TModelItem.Cancelar: iItemOperacoes;
begin
    FState.Cancelar;
    //implementa o codigo de cancelamento
    FState := TModelItemAtivo.New;//Após realizar todos as operações de cancelamento retorna para ativo
    Result := Self;
end;

function TModelItem.Desconto: iItemOperacoes;
begin
    FState.Desconto;
    //implementa o codigo de cancelamento
Result := Self;
end;

function TModelItem.Devolver: iItemOperacoes;
begin
    FState.Devolver;
    //implementa o codigo de cancelamento
    FState := TModelItemAtivo.New;//Após realizar todos as operações de devolução retorna para ativo
    Result := Self;
end;

function TModelItem.Vender: iItemOperacoes;
begin
    FState.Vender;
    //implementa o codigo de cancelamento
    FState := TModelItemVendido.New;//Após realizar todos as operações de venda recebe o estado de vendido
    Result := Self;
end;

...

Você pode observar que dentro de cada método da operação estou chamando sua ação dentro do seu estado, esse processo é feito porque todas ações que são compartilhadas pela interface de operações, vender, cancelar, desconto e devolver, antes delas fazerem qualquer coisa nós iremos chamar essa ação dentro do seu estado, porque ela irá ter o comportamento de acordo com o estado que estiver setado.

Você deve estar pensado, nossa por que tanto código para isso, lembre que programar de forma funcional você evita de ter problemas futuros em suas manutenções e futuras implementações,  depois que você ver esse padrão funcionando você irá querer usar em tudo que é lugar…


...

type
    TModelItemAtivo = class(TInterfacedObject,iItemOperacoes)
    private
    public
        constructor Create;
        destructor Destroy; override;
        class function New : iItemOperacoes;
        function Vender : iItemOperacoes;
        function Cancelar : iItemOperacoes;
        function Desconto : iItemOperacoes;
        function Devolver : iItemOperacoes;
    end;

implementation

uses
    System.SysUtils;

{ TModelItemAtivo }

function TModelItemAtivo.Cancelar: iItemOperacoes;
begin
    Result := self;
    raise Exception.Create('Este item ainda não foi vendido');
end;

constructor TModelItemAtivo.Create;
begin

end;

function TModelItemAtivo.Desconto: iItemOperacoes;
begin
    Result := self;
    raise Exception.Create('Este item ainda não foi vendido');
end;

destructor TModelItemAtivo.Destroy;
begin

inherited;
end;

function TModelItemAtivo.Devolver: iItemOperacoes;
begin
    Result := self;
    raise Exception.Create('Este item ainda não foi vendido');
end;

class function TModelItemAtivo.New: iItemOperacoes;
begin
    Result := Self.Create;
end;

function TModelItemAtivo.Vender: iItemOperacoes;
begin
    Result := Self;
end;

Em nossa classe de item ativo implementa a interface de operações, e tratamos seus comportamentos.

Você pode observar que nos métodos cancelar,desconto e devolver não podem ser implementados pois o item ainda não foi vendido.

Agora eu deleguei para uma outra classe, a classe de estado toda responsabilidade caso o item esteja ativo.

Veja as demais classe que implantem os estados.

Inativo:


type
    TModelItemInativo = class(TInterfacedObject,iItemOperacoes)
    private
    public
        constructor Create;
        destructor Destroy; override;
        class function New : iItemOperacoes;
        function Vender : iItemOperacoes;
        function Cancelar : iItemOperacoes;
        function Desconto : iItemOperacoes;
        function Devolver : iItemOperacoes;
    end;

implementation

uses
    System.SysUtils;

{ TModelItemInativo }

function TModelItemInativo.Cancelar: iItemOperacoes;
begin
    Result := self;
    raise Exception.Create('Este item está inativo');
end;

constructor TModelItemInativo.Create;
begin

end;

function TModelItemInativo.Desconto: iItemOperacoes;
begin
    Result := self;
    raise Exception.Create('Este item está inativo');
end;

destructor TModelItemInativo.Destroy;
begin

inherited;
end;

function TModelItemInativo.Devolver: iItemOperacoes;
begin
    Result := self;
    raise Exception.Create('Este item está inativo');
end;

class function TModelItemInativo.New: iItemOperacoes;
begin
    Result := Self.Create;
end;

function TModelItemInativo.Vender: iItemOperacoes;
begin
    Result := Self;
    raise Exception.Create('Este item está inativo');
end;

Vendido:


type
    TModelItemVendido = class(TInterfacedObject,iItemOperacoes)
    private
    public
        constructor Create;
        destructor Destroy; override;
        class function New : iItemOperacoes;
        function Vender : iItemOperacoes;
        function Cancelar : iItemOperacoes;
        function Desconto : iItemOperacoes;
        function Devolver : iItemOperacoes;
    end;

implementation

uses
    System.SysUtils;

{ TModelItemVendido }

function TModelItemVendido.Cancelar: iItemOperacoes;
begin
    Result := self;
end;

constructor TModelItemVendido.Create;
begin

end;

function TModelItemVendido.Desconto: iItemOperacoes;
begin
    Result := self;
end;

destructor TModelItemVendido.Destroy;
begin

inherited;
end;

function TModelItemVendido.Devolver: iItemOperacoes;
begin
    Result := self;
end;

class function TModelItemVendido.New: iItemOperacoes;
begin
    Result := Self.Create;
end;

function TModelItemVendido.Vender: iItemOperacoes;
begin
    Result := Self;
    {Foi colocado somente para fins didádicos}
    raise Exception.Create('Este item já foi vendido');
end;

Criamos nossas classes que implementa os estados, e agora vamos ver esses estados funcionando.

A magica acontecendo…

Em nossa view possuímos os botões que iremos tratar os exemplos de comportamento.

Em nossa classe de item você pode ver que de acordo com a ação ele irá verificar se pode fazer algo e irá alterar seu estado.


...
function TModelItem.Cancelar: iItemOperacoes;
begin
    FState.Cancelar;
    //implementa o codigo de cancelamento
    FState := TModelItemAtivo.New;//Após realizar todos as operações de cancelamento retorna para ativo
    Result := Self;
end;

function TModelItem.Desconto: iItemOperacoes;
begin
    FState.Desconto;
    //implementa o codigo de cancelamento
    Result := Self;
end;

function TModelItem.Devolver: iItemOperacoes;
begin
    FState.Devolver;
    //implementa o codigo de cancelamento
    FState := TModelItemAtivo.New;//Após realizar todos as operações de devolução retorna para ativo
    Result := Self;
end;

function TModelItem.Vender: iItemOperacoes;
begin
  FState.Vender;
  //implementa o codigo de cancelamento
  FState := TModelItemVendido.New;//Após realizar todos as operações de venda recebe o estado de vendido
  Result := Self;
end;

...

Em nossa view essas operações de Ativar, Vendido e Inativo está sendo feito para fins didático, mas em seu software esse processo pode ser feito automático.


...

private
    { Private declarations }
    FItem : iItem;

...

procedure TForm2.Button1Click(Sender: TObject);
begin
    //Ativar
    FItem.State.SetState(TModelItemAtivo.New);
end;

procedure TForm2.Button2Click(Sender: TObject);
begin
    //Vendido
    FItem.State.SetState(TModelItemVendido.New);
end;

procedure TForm2.Button3Click(Sender: TObject);
begin
    //Inativo
    FItem.State.SetState(TModelItemInativo.New);
end;

...

Veja como fica o código que tratar as operações de vender, cancelar, desconto, devolver.


...

procedure TForm2.Button4Click(Sender: TObject);
begin
    FItem.Operacoes.Vender;
end;

procedure TForm2.Button5Click(Sender: TObject);
begin
    FItem.Operacoes.Cancelar;
end;

procedure TForm2.Button6Click(Sender: TObject);
begin
    FItem.Operacoes.Desconto;
end;

procedure TForm2.Button7Click(Sender: TObject);
begin
    FItem.Operacoes.Devolver;
end;

...

Ao tentar dar desconto em um item já ativo, olha a mensagem que é retornada:

Viu como é legal esse padrão?

Você vendo funcionando é outra coisa né? RSRS

Nós criamos estados para comportamentos das classes, no caso do item.

Como ele se comporta quando está ativo? Vamos só na classe de ativo e vejo como é o comportamento dele.

E assim para todos os seus estados, vendo suas regras de validações.

Desta forma você poderá tratar cada comportamento das operações como melhor se aplica ao seu senário, seja para pegar um estado de um caixa, ou da venda, não importa, esse padrão se aplica a tudo objeto que precisa ser guardado seu estado.

Você pode baixar no link abaixo todos os fontes desse artigo:

BAIXAR OS FONTES DO ARTIGO

POR QUE FAZER ESTE TREINAMENTO?

Por mais que achamos que estamos construindo o melhor produto do planeta, sempre existe espaço para melhorar, ou alguém que já está fazendo algo melhor, se você ainda não utiliza Design Patterns (Padrões de Projeto) no seu software, você está anos luz atrás de muitos de seus concorrentes.

Nesse treinamento você irá aprender de forma prática como utilizar e para que serve cada um dos 23 padrões do GOF, com exemplos reais de aplicação das técnicas para resolver problemas do dia a dia, Como:

* Padrões para Fluxo de Nota Fiscal e Venda.
* Construção de Factorys para criação de classes e redução de acoplamento.
* Padrões para repetição e clonagem de itens em vendas e documentos fiscais.
* Padrões para redução de consumo de memória.
* Padrões para criação de cache e aumento de performance da aplicação.
* Padrões para agregação de funcionalidades em tempo de execução.
* Padrões para geração de arquivos fiscais (SPED, Sintegra) de forma prática.
* Padrões para validação de permissão a nível de usuário
* Padrões para Estratégias a nível de tomada de decisão por parâmetro.
* Padrões para comunicação de múltiplas camadas do software.
* e muito mais…

Dê um salto de qualidade na sua carreira e no seu produto!

CLIQUE AQUI PARA SABER MAIS SOBRE A CERTIFICAÇÃO ESPECIALISTA EM PADRÕES DE PROJETO EM DELPHI





Faça sua busca

CATEGORIAS

POSTS RECENTES

E caso você tem interesse de conhecer mais sobre Validação de Dados com o Padrão State, acesse o nosso portal do CLUBE DE PROGRAMADORES EM DELPHI
Você não terá só conteúdos relacionados ao Validação de Dados com o Padrão State, mas uma quantidade enorme de conteúdos que poderá lhe ajudar muito no seu dia a dia, é uma verdadeira NETFLIX para os programadores Delphi.
Gostou?
Compartilhe:

Embarque no foguete com milhares de devs para aprender desenvolvimento, evoluir de forma contínua e se manter relevante no mercado.

Sobre
Dúvidas
Cadastre-se em nossa lista

Para ter acesso em primeira mão, a tudo que acontece na Academia do Código, basta se cadastrar em nossa lista

Grupo Thulio Bittencourt | Academia do Código

#FaçaPartedaHistória

Copyright © 2022 – Todos os direitos reservados