Vamos escrever um ensaio ! Mas essa é uma tarefa impossível. Porém, podemos criar um processo que, se executado para sempre, geraria um ensaio infinitamente longo. . infinitamente longo Perto o suficiente Agora, obviamente, você pode gerar um ensaio longo e repetitivo com uma única linha de código Python: >>> "This is water. " * 20 'This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. ' … vaias! Em vez disso, geraremos um ensaio muito mais interessante usando o padrão de projeto State neste artigo. Booawn Primeiro, entenderemos o que são máquinas de estado e como elas estão relacionadas ao padrão de design State. A seguir, criaremos uma máquina de estados que pode gerar um ensaio (razoavelmente) interessante e infinito. Em seguida, examinaremos brevemente o padrão de design do estado. Finalmente, traduziremos essa máquina de estados em código orientado a objetos usando o padrão de design de estados. Os padrões de projeto de software são formas eficazes de resolver problemas comuns. Quando aplicados adequadamente, os padrões de design de software, como o padrão de estado, podem ajudá-lo a escrever software melhor escalável, sustentável e testável. Máquina de Estado Em essência, o padrão de design State traduz uma máquina de estados em código orientado a objetos. Se você não está familiarizado com máquinas de estado, é um conceito bastante simples. Uma máquina de estados possui e . Os estados são certas propriedades do nosso sistema de interesse, e as transições de estado são ações que alteram essas propriedades e, portanto, também causam uma mudança de estado. estados transições Como tenho experiência em robótica (entre outras coisas) e como as máquinas de estado são amplamente utilizadas em robótica, usarei um exemplo simples de aspirador de pó robô para ilustrar como as máquinas de estado funcionam. O diagrama da máquina de estados mostra uma imagem intuitiva de como o robô opera, mesmo que você nunca tenha encontrado máquinas de estados. Vamos repassar esta operação passo a passo. O robô inicia no estado (o ponto preto indica o estado inicial). Ancorado Se o robô detectar que a bateria está fraca, ele começa a carregar (estado ) até que a bateria esteja cheia. Quando a bateria estiver cheia, ela retornará ao estado . de carregamento encaixado No estado , se o robô detectar que o chão está sujo (e a bateria não está fraca), ele começa a limpar o chão (estado ). encaixado de limpeza No estado , se o robô ficar com pouca bateria, ele irá carregar sozinho. E se o chão estiver limpo, o robô retorna ao seu encaixe (estado ). Limpeza encaixado Assim, nosso aspirador robô possui três estados — , e — e possui transições baseadas na detecção sensorial do chão e de sua bateria. Ancorado Limpeza Carregamento Máquina de estado simples para ensaio infinito Agora que entendemos as máquinas de estado em um nível básico, vamos criar uma máquina de estado capaz de escrever um ensaio infinito. Acima está uma máquina de estado que usa a gramática inglesa para gerar uma redação composta por frases curtas e simples. Prometo que chegaremos a uma versão mais interessante muito em breve, mas isto deve servir como um bom ponto de partida para a compreensão. Vamos ver como funciona. Começando no estado , geramos um substantivo escolhendo em alguma lista predefinida de substantivos. Digamos que nosso substantivo seja “O Mundo”. (frase até agora: “O Mundo”) Substantivo Então terminamos no estado , gerando um verbo a seguir (digamos, “latido”). (frase até agora: “O mundo late”) Verbo Geramos um adjetivo (digamos, “vermelho”) no estado . (frase até agora: “O mundo late vermelho”) Adjetivo Então, no estado , geramos um dos sinais de pontuação final, digamos “!”. (frase: “O mundo late vermelho!”) Endmark Finalmente, estamos de volta ao estado para gerar nossa próxima frase no ensaio. Substantivo Esta máquina de estado pode gerar um ensaio (absurdo) parecido com este. O mundo late em vermelho! Primo Harry chove muito? Os tigres brilham divertidos. … Máquina de estado não determinística para ensaio infinito Embora “não determinístico” pareça complicado, para nossos propósitos, significa apenas adicionar alguma aleatoriedade. Essencialmente, estamos adicionando uma espécie de sorteio antes de fazer a transição para alguns dos estados. Você verá o que quero dizer. A máquina de estado não determinística acima é muito semelhante à anterior. As únicas diferenças são: Negações são palavras como “não” ou “não” e conjunções são palavras como “e” e “mas”. No estado , geramos um verbo e depois jogamos uma moeda. Se sair cara (50% de probabilidade), passamos para o estado ; caso contrário, vamos para o estado . Verbo Negação Adjetivo Da mesma forma, no estado , geramos um adjetivo e depois jogamos uma moeda. Se der cara, vamos para o estado ; se der coroa, então vamos para o estado . Adjetivo de Conjunção Endmark Com a introdução da aleatoriedade, da negação e das conjunções, agora podemos gerar sentenças mais interessantes e de comprimento variável. Padrão de Design de Estado Agora, vamos entender como funciona o padrão de design de estado. Novamente, lembre-se de que estamos tentando traduzir uma máquina de estados em código orientado a objetos. Na máquina de estado de geração de ensaios, observe que cada estado precisa fazer duas coisas. Execute alguma ação. Neste caso, gerando uma palavra (substantivo, adjetivo, etc.). Transição para o próximo estado. Do ao e assim por diante. substantivo verbo Do ponto de vista de um determinado Estado, não há que ele precise saber ou fazer. Em vez de ficarmos atolados na complexidade de todo o sistema – todos os seus estados e transições – podemos concentrar-nos apenas num estado de cada vez. Na minha opinião, este tipo de e é o maior argumento de venda do padrão estatal. mais nada isolamento dissociação Abaixo, temos um diagrama para o padrão de design State. Existe algum contexto no qual cada um dos estados opera, ilustrado pela classe . O objeto de contexto possui um atributo de estado privado, que ele usa para chamar o estado atual para executar sua ação. Cada estado implementa uma interface com métodos para executar sua ação ou operação e retornar o próximo estado. UML Context State Se mapearmos isso no exemplo de geração de ensaio, o diagrama UML ficará assim. agora é uma classe abstrata (indicada em itálico) em vez de uma interface. Classes abstratas podem ter alguns métodos e atributos abstratos (não implementados), enquanto outros podem ser definidos. As interfaces são totalmente abstratas: todos os seus métodos são abstratos. Fiz essa alteração porque a implementação é a mesma em todos os estados e é bom evitar código duplicado. WordState generateWord Vamos detalhar cada um dos atributos e métodos acima. Na classe , temos: EssayContext : referência ao objeto atual. state WordState : Lista de todas as palavras geradas até agora. essayBody : Setter para alterar o atributo . setState() state : Método para adicionar a próxima palavra ao corpo do ensaio. addWord() : chamamos esse método para gerar nossa redação; paramos quando o tem comprimento maior que . generateEssay() essayBody length : Retorna uma string da redação gerada. printEssay() Na classe abstrata , temos: WordState : propriedade abstrata (indicada em itálico) para uma lista de palavras da qual escolhemos palavras para gerar. wordList : Método que adiciona a palavra gerada ao contexto da redação. generateWord() : método abstrato para retornar o próximo estado. nextState() Usaremos como exemplo representativo para todos os outros estados concretos herdados de . NounState WordState : uma lista de substantivos da qual escolhemos palavras para gerar. wordList : Retorna o próximo estado. nextState() Agora, temos tudo o que precisamos para realmente implementar isso no código. Vamos em frente e fazer exatamente isso! Código Python Vamos primeiro escrever a classe em um arquivo chamado . Vamos abandonar o caso do camelo e mudar para o caso da cobra porque, bem, Python é uma... cobra (desculpe). EssayContext essay_context.py from word_state import WordState class EssayContext: def __init__(self, state: WordState): self.state = state self.essay_body: list[str] = [] def set_state(self, state: WordState): self.state = state def add_word(self, word: str): self.essay_body.append(word) def generate_essay(self, length: int): while len(self.essay_body) < length: self.state.generate_word(self) def print_essay(self) -> str: return " ".join(self.essay_body) Então, vamos adicionar os estados em um arquivo chamado . word_state.py import abc import numpy as np class WordState(abc.ABC): word_list: list[str] @classmethod def generate_word(cls, context: "EssayContext"): word = np.random.choice(cls.word_list) context.add_word(word) context.set_state(cls.next_state()) @classmethod @abc.abstractmethod def next_state(cls) -> "WordState": pass class NounState(WordState): word_list: list[str] = ["everything", "nothing"] @classmethod def next_state(cls): return VerbState class VerbState(WordState): word_list: list[str] = ["is", "was", "will be"] @classmethod def next_state(cls): heads = np.random.rand() < 0.5 if heads: return NegationState return AdjectiveState class NegationState(WordState): word_list: list[str] = ["not"] @classmethod def next_state(cls): return AdjectiveState class AdjectiveState(WordState): word_list: list[str] = ["fantastic", "terrible"] @classmethod def next_state(cls): heads = np.random.rand() < 0.5 if heads: return ConjunctionState return EndmarkState class ConjunctionState(WordState): word_list: list[str] = ["and", "but"] @classmethod def next_state(cls): return NounState class EndmarkState(WordState): word_list: list[str] = [".", "!"] @classmethod def next_state(cls): return NounState Finalmente, vamos adicionar código para executar tudo em . main.py from essay_context import EssayContext from word_state import NounState if __name__ == '__main__': ctx = EssayContext(NounState) ctx.generate_essay(100) print(ctx.print_essay()) A execução nos fornece a seguinte saída (diferente a cada vez devido ao não determinismo): python main.py 'everything is not terrible and nothing was terrible ! everything will be not fantastic but everything is fantastic . everything will be fantastic . nothing will be fantastic and nothing will be terrible ! everything was not fantastic and everything will be not terrible . everything was terrible . nothing was terrible but nothing will be fantastic ! nothing is not terrible . nothing was not fantastic but everything was not fantastic ! everything will be not fantastic but everything will be terrible ! everything will be not fantastic . everything is fantastic but nothing will be not terrible ! everything will be not fantastic but nothing was not fantastic !' Nada mal para um sistema tão simples! Também podemos estender as várias listas de palavras ou adicionar mais estados para tornar a geração da redação mais sofisticada. Poderíamos até introduzir algumas APIs LLM para levar nossos ensaios ao próximo nível. Pensamentos finais As máquinas de estado e o padrão State são ideais para modelar e criar sistemas com uma noção bem definida de “estado”. Ou seja, existem comportamentos e propriedades específicas associadas a cada estado. O aspirador robô está a limpar, encaixado ou a carregar. Sua TV pode estar LIGADA ou DESLIGADA e os botões do controle remoto da TV funcionarão de maneira diferente com base no estado da TV. Também é uma boa opção para gerar ou identificar sequências com um padrão bem definido. Isso se aplica ao nosso exemplo de geração de ensaio. Finalmente, você pode perguntar: “Qual é o sentido de tudo isso?” Por que tivemos tantos problemas para definir os vários estados e classes para gerar este ensaio “infinito”? Poderíamos ter escrito 20 (ou menos) linhas de código Python para obter o mesmo comportamento. A resposta curta é para melhor . escalabilidade Imagine se, em vez de apenas três ou cinco estados, tivéssemos 50 ou 500 estados. Isso não é uma hipérbole; sistemas empresariais reais têm esse nível de complexidade. De repente, o padrão estatal parece muito mais atraente devido à sua dissociação e isolamento. Podemos concentrar-nos apenas num estado de cada vez, sem ter de manter todo o sistema nas nossas cabeças. É mais fácil introduzir mudanças porque um estado não afetará outros. Também permite testes de unidade mais fáceis, uma grande parte de um sistema escalável e de fácil manutenção. Em última análise, o padrão estatal não se trata apenas de gerir estados; como todos os padrões de projeto, é um modelo para a construção de sistemas que sejam tão escaláveis e de fácil manutenção quanto sofisticados.