Давайте напишем бесконечно длинное сочинение! Но это невыполнимая задача. Однако мы можем создать процесс, который, если его запустить бесконечно, создаст бесконечно длинное эссе. Достаточно близко .
Теперь вы, очевидно, можете создать длинное и повторяющееся эссе с помощью одной строки кода 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. '
Зевать … ухать! Вместо этого в этой статье мы создадим гораздо более интересное эссе, используя шаблон проектирования «Состояние».
Во-первых, мы поймем, что такое конечные автоматы и как они связаны с шаблоном проектирования состояний. Далее мы создадим конечный автомат, который сможет генерировать (достаточно) интересное и бесконечное эссе. Затем мы кратко рассмотрим шаблон проектирования состояний. Наконец, мы переведем этот конечный автомат в объектно-ориентированный код, используя шаблон проектирования состояний.
Шаблоны проектирования программного обеспечения — это эффективные способы решения часто возникающих проблем. При правильном применении шаблоны проектирования программного обеспечения, такие как шаблон состояния, могут помочь вам написать более масштабируемое, поддерживаемое и тестируемое программное обеспечение.
По сути, шаблон проектирования State преобразует конечный автомат в объектно-ориентированный код.
Если вы не знакомы с конечными автоматами, это довольно простая концепция. Конечный автомат имеет состояния и переходы . Состояния — это определенные свойства интересующей нас системы, а переходы состояний — это действия, которые изменяют эти свойства и тем самым также вызывают изменение состояния.
Поскольку у меня есть опыт работы в робототехнике (помимо прочего) и поскольку в робототехнике широко используются конечные автоматы, я воспользуюсь простым примером робота-пылесоса, чтобы проиллюстрировать, как работают конечные автоматы.
Диаграмма конечного автомата рисует интуитивно понятную картину того, как работает робот, даже если вы никогда не сталкивались с конечными автоматами. Давайте разберем эту операцию шаг за шагом.
Таким образом, наш робот-пылесос имеет три состояния — «Пристыкован» , «Уборка » и «Зарядка » — и имеет переходы, основанные на сенсорном обнаружении пола и его аккумулятора.
Теперь, когда мы понимаем конечные автоматы на базовом уровне, давайте создадим конечный автомат, способный написать бесконечное эссе.
Выше представлен конечный автомат, который использует английскую грамматику для создания эссе, состоящего из коротких простых предложений. Обещаю, очень скоро мы доберемся до более интересной версии, но это должно послужить хорошей отправной точкой для понимания. Давайте рассмотрим, как это работает.
Этот конечный автомат может сгенерировать (бессмысленное) эссе, которое выглядит следующим образом.
Мир лает красным! Кузен Гарри нарушил правила? Тигры весело мерцают. …
Хотя «недетерминированный» звучит сложно, для наших целей это просто означает добавление некоторой случайности. По сути, мы добавляем своего рода подбрасывание монеты перед переходом в некоторые состояния. Вы поймете, что я имею в виду.
Недетерминированный конечный автомат, описанный выше, очень похож на предыдущий. Единственные различия:
Благодаря введению случайности, отрицания и союзов мы теперь можем генерировать более интересные предложения переменной длины.
Теперь давайте разберемся, как работает шаблон проектирования состояний. Опять же, помните, что мы пытаемся преобразовать конечный автомат в объектно-ориентированный код.
Обратите внимание, что в машине состояний генерации эссе каждое состояние должно сделать две вещи.
С точки зрения конкретного государства, ему больше ничего не нужно знать или делать. Вместо того, чтобы увязнуть в сложности всей системы — всех ее состояний и переходов — мы можем просто сосредоточиться на одном состоянии за раз. На мой взгляд, такого рода изоляция и разъединение является самым большим преимуществом модели государства.
Ниже у нас есть UML- диаграмма для шаблона проектирования «Состояние». Существует некоторый контекст, в котором действует каждое из состояний, иллюстрируемый классом Context
. Объект контекста имеет атрибут частного состояния, который он использует для вызова текущего состояния для выполнения своего действия. Каждое состояние реализует интерфейс State
с методами для выполнения его действия или операции и возврата следующего состояния.
Если мы сопоставим это с примером создания эссе, диаграмма UML будет выглядеть следующим образом.
WordState
теперь является абстрактным классом (обозначенным курсивом), а не интерфейсом. Абстрактные классы могут иметь некоторые абстрактные (не реализованные) методы и атрибуты, а другие могут быть определены. Интерфейсы полностью абстрактны: все их методы абстрактны. Я внес это изменение, потому что generateWord
одинакова во всех состояниях, и избегать дублирования кода полезно.
Давайте разберем каждый из атрибутов и методов, описанных выше. В классе EssayContext
у нас есть:
state
: ссылка на текущий объект WordState
.essayBody
: Список всех слов, сгенерированных на данный момент.setState()
: установщик для изменения атрибута state
.addWord()
: метод добавления следующего слова в текст эссе.generateEssay()
: мы вызываем этот метод для создания нашего эссе; мы останавливаемся, когда длина essayBody
превышает length
.printEssay()
: возвращает строку сгенерированного эссе.
В абстрактном классе WordState
у нас есть:
wordList
: Абстрактное свойство (обозначено курсивом) для списка слов, из которого мы выбираем слова для генерации.generateWord()
: метод, который добавляет сгенерированное слово в контекст эссе.nextState()
: абстрактный метод для возврата следующего состояния.
Мы будем использовать NounState
в качестве репрезентативного примера для всех других конкретных состояний, унаследованных от WordState
.
wordList
: список существительных, из которого мы выбираем слова для создания.nextState()
: возвращает следующее состояние.
Теперь у нас есть все необходимое, чтобы реализовать это в коде. Давайте продолжим и сделаем именно это!
Давайте сначала напишем класс 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)
Затем давайте добавим состояния в файл 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
Наконец, давайте добавим код для запуска всего в 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())
Запуск 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 !'
Неплохо для такой простой системы! Мы также можем расширить различные списки слов или добавить больше состояний, чтобы сделать создание эссе более сложным. Мы могли бы даже представить некоторые API-интерфейсы LLM, чтобы вывести наши эссе на новый уровень.
Конечные автоматы и шаблон State отлично подходят для моделирования и создания систем с четко определенным понятием «состояние». То есть с каждым состоянием связано определенное поведение и свойства. Робот-пылесос выполняет уборку, док-станцию или зарядку. Ваш телевизор может быть включен или выключен, а кнопки пульта дистанционного управления телевизора будут действовать по-разному в зависимости от состояния телевизора.
Он также хорошо подходит для создания или идентификации последовательностей с четко определенным шаблоном. Это относится к нашему примеру создания эссе.
Наконец, вы можете спросить: «Какой во всем этом смысл?» Почему мы потратили столько времени на определение различных состояний и классов, чтобы создать это «бесконечное» эссе? Мы могли бы написать 20 (или меньше) строк кода Python, чтобы добиться такого же поведения.
Короткий ответ: для лучшей масштабируемости .
Представьте себе, если бы вместо трех или пяти штатов у нас было бы 50 или 500 штатов. Это не гипербола; реальные корпоративные системы имеют такой уровень сложности. Внезапно модель «Государство» кажется гораздо более привлекательной из-за ее разъединенности и изоляции. Мы можем просто сосредоточиться на одном состоянии за раз, не держа в голове всю систему. Вносить изменения проще, поскольку одно состояние не влияет на другие. Это также упрощает модульное тестирование, что является важной частью масштабируемой и поддерживаемой системы.
В конечном счете, шаблон «Состояние» касается не только управления состояниями; Как и все шаблоны проектирования, это образец построения систем, которые являются настолько же масштабируемыми и удобными в обслуживании, насколько и сложными.