Пишу клон agar.io. В последнее время я видел много предложений по ограничению использования записей (например, здесь), поэтому я пытаюсь выполнить весь проект только с использованием базовых карт.*
В итоге я создал конструкторы для разных «типов» бактерий, таких как
(defn new-bacterium [starting-position]
{:mass 0,
:position starting-position})
(defn new-directed-bacterium [starting-position starting-directions]
(-> (new-bacterium starting-position)
(assoc :direction starting-directions)))
К «направленной бактерии» добавлена новая запись. Запись :direction
будет использоваться для запоминания направления движения.
Вот проблема: я хочу иметь одну функцию take-turn
, которая принимает бактерию и текущее состояние мира и возвращает вектор [x, y]
, указывающий смещение от текущей позиции чтобы переместить бактерию. Я хочу иметь одну функцию, которая вызывается, потому что прямо сейчас я могу думать о по крайней мере трех видах бактерий, которые мне нужны, и я хотел бы иметь возможность добавлять новые типы позже, чем каждый определяет свой собственный take-turn
.
Протокол Can-Take-Turn
не подходит, так как я использую простые карты.
Сначала казалось, что мультиметод take-turn
будет работать, но потом я понял, что у меня не будет значений диспетчеризации для использования в моей текущей настройке, которые можно было бы расширять. Я мог бы использовать :direction
в качестве функции отправки, а затем отправить nil
, чтобы использовать take-turn
"направленной бактерии", или по умолчанию, чтобы получить базовое бесцельное поведение, но это не дает мне возможности даже иметь третьего "игрока". типа "бактерия".
Единственное решение, которое я могу придумать, это потребовать, чтобы у всех бактерий было поле :type
, и отправить его, например:
(defn new-bacterium [starting-position]
{:type :aimless
:mass 0,
:position starting-position})
(defn new-directed-bacterium [starting-position starting-directions]
(-> (new-bacterium starting-position)
(assoc :type :directed,
:direction starting-directions)))
(defmulti take-turn (fn [b _] (:type b)))
(defmethod take-turn :aimless [this world]
(println "Aimless turn!"))
(defmethod take-turn :directed [this world]
(println "Directed turn!"))
(take-turn (new-bacterium [0 0]) nil)
Aimless turn!
=> nil
(take-turn (new-directed-bacterium [0 0] nil) nil)
Directed turn!
=> nil
Но теперь я вернулся к диспетчеризации по типу, используя более медленный метод, чем протоколы. Является ли это законным случаем использования записей и протоколов, или я что-то упускаю из-за мультиметодов? У меня мало практики с ними.
*
Я также решил попробовать это, потому что я был в ситуации, когда у меня была запись Bacterium
и я хотел создать новую "ориентированную" версию записи, к которой было добавлено одно поле direction
(в основном наследование). Однако исходная запись реализовывала протоколы, и я не хотел делать что-то вроде вложения исходной записи в новую и маршрутизации всего поведения во вложенный экземпляр. Каждый раз, когда я создавал новый тип или менял протокол, мне приходилось менять всю маршрутизацию, что требовало много работы.
:type
на данный момент, и, кажется, все идет хорошо. Я до сих пор не знаю, является ли это лучшей практикой, но я думаю, что она будет работать нормально. - person Carcigenicate   schedule 16.11.2018