Skip to content

Instantly share code, notes, and snippets.

@Asenim
Created October 29, 2024 23:08
Show Gist options
  • Save Asenim/71dba352c6f0f94581fe086c04eacaa6 to your computer and use it in GitHub Desktop.
Save Asenim/71dba352c6f0f94581fe086c04eacaa6 to your computer and use it in GitHub Desktop.

https://github.com/Konfeton/Simulation
Ревью от Алексея @Raketa4000az [Dmitry Dulko]

Хорошая ООП архитектура.

Хорошо:

  1. Существа не хранят в себе координату.
  2. Существа не хранят в себе спрайт собственного изображения.
  3. Простой понятный алгоритм.

Замечания:

  1. Нейминг

-Название должно как можно лучше объяснять суть явления

/* class WorldMap */
private final int maxWorldX;
private final int maxWorldY;

//ЛУЧШЕ:
private final int height;
private final int width;

-Неочевидно, но
//ВМЕСТО
Class type

//ПРИНЯТО
Class clazz

//ПОТОМУ ЧТО НЕЛЬЗЯ
Class class

-MapConfigurator тут создает карту. Между тем, конфигурирование является действием, в котором вы изменяете настройки по умолчанию в различных частях продукта.
Т.е. конфигуратор должен проводить действие над уже созданным объектом. Если конфигуратор создает объект, это не конфигуратор, а создатель.

--
Oracle Java code conventions, part."Naming conventions"
Мартин, "Чистый код", гл.2
--
  1. Если в блоке if есть return(break, continue, exit), то else не пишется
if (currentNumber < numberToSpawn){
  return numberToSpawn - currentNumber;
}else {
  return 0;
}

//ПРАВИЛЬНО:
if (currentNumber < numberToSpawn){
  return numberToSpawn - currentNumber;
}
return 0;
  1. Не используй try-catch вместе с бизнес логикой, изолируй во вспомогательных методах
while (true) {
  try { 
    int maxSize = Integer.parseInt(input);
    //...
    return maxSize;    
  } catch (NumberFormatException e) {
     System.out.println("Введите число");
  }
}

//ПРАВИЛЬНО:
while (true) {
  if(isInteger(input)) {
    int maxSize = Integer.parseInt(input);
    //...
    return maxSize;    
  } else {
    System.out.println("Введите число");    
  }
}

private static boolean isInteger(String s) {
  try {
     Integer.parseInt(s);
     return true;
  } catch (NumberFormatException e) {
     return false;
  }
}
  1. class Coordinates +Все отлично.

  2. class WorldMap, карта

-Метод возращает Map. Зачем? Довольно неочевидный вариант возврата результата public Map<Coordinates, T> getEntitiesOfType(Class<? extends Entity> type) Этот метод используется для получения Set координат Set coordinates = map.getEntitiesOfType(Creature.class).keySet(); А дальше этот сет используется для получения существа по координате Creature creature = (Creature) map.getEntityByCoordinates(coordinate); Спрашивается, почему сразу не сделать метод, который будет возвращать список всех существ заданного типа public List getEntities(Class<? extends Entity> clazz)

-Нарушение SRP, методы чужих ответственностей. Карта должна только хранить существа и обеспечить базовые операции с ними- вставить, выдать одно существо и список всех хранимых существ, удалить. Если какой-то метод не нужен для обеспечения этого функционала, значит он не принадлежит к ответственности карты, а принадлежит к чужой ответственности. Метод подсчета площади(диагонали, периметра, стороны вписанного треугольника) не нужен для реализации единой ответственности карты public int getMapSize(){ return maxWorldX * maxWorldY; } Этот метод используется в одном классе- SpawnRates. Вот там он и должен находиться.

-Метод совершения хода- переносит существо из точки А в точку Б. А имеет ли карта право осуществлять перемещение существ? Не нарушит ли такое перемещение правил игры? public void makeMove(Coordinates from, Coordinates to) Что будет, если в точке "to" будет находиться какое-то существо, например гора? Что будет, если дадим команду перенести зайца из одного края карты в другой, без учета пешеходных возможностей зайца? Карта никак не отвечает на эти вопросы. Очевидно, что эти вопросы выходят за рамки компетенции карты. А значит, карта не должна содержать в себе метод перемещения существа внутри себя.

+В целом, в архитектуре Карты нет грубых нарушений.

  1. class MapConfigurator, ведет диалог с юзером и и по его результатам создает Карту

-Не является Картой, либо ее составной частью, как Координата. Поэтому не может находиться вместе с ними в папке map.

  1. abstract class HerbivoreFood и его наследник Carrot -Наследники должны инжектить int healingPower HerbivoreFood'у в конструктор, а не сетить его
public class Carrot extends HerbivoreFood {
  public Carrot() { healingPower = 2; }
}

//ПРАВИЛЬНО:
public class Carrot extends HerbivoreFood {
  private final static int HEALING_POWER = 2;

  public Carrot() { 
    super(HEALING_POWER);
  }
}

?. class Creature extends Entity и его наследники Заяц и Волк +Все хорошо, за исключением мелкого нюанса при использовании поиска, о чем ниже.

  1. interface PathFinder, интерфейс поиска пути

+Интерфейс поиска- это хорошо. Можно сделать разные реализации интерфейса и т.о. ввести в программу разные алгоритмы поиска пути. -Не понимаю, как работает поиск- он только принимает координату старта

public interface PathFinder {
  Path findPath(Coordinates start);
}

По идее, метод поиска пути должен принимать карту, координату старта и условия идентификации координаты конца, напр. класс существа, которое находится на этой координате.

  1. class BreadthFirstSearch implements PathFinder, алгоритм поска BFS -Теперь понятно- BreadthFirstSearch принимает карту и класс еды в конструктор public BreadthFirstSearch(Class<? extends Entity> target, WorldMap map) Если рассматривать ситуацию как сферического коня в вакууме, такая реализация хуже, чем если бы Поиск имел пустой конструктор, а необходимые для поиска аргументы принимал в метод. Тогда один и тот же экземпляр можно было бы задействовать для поиска пути всеми существами, а не персонально конкретным, как сейчас
//поиск принимает аргументы в конструктор
Морковка морковка = new Морковка();
Заяц заяц = new Заяц();
Волк волк = new Волк();
...
Поиск ищетЗаяц = new Поиск(Морковка.getClass(), map);  //one
Поиск ищетВолк = new Поиск(Заяц.getClass(), map);  //two

Путь путьВолка = ищетВолк.get(координатаВолка);
Путь путьЗайца = ищетЗаяц.get(координатаЗайца);
...

//VS поиск принимает аргументы в метод
Морковка морковка = new Морковка();
Заяц заяц = new Заяц();
Волк волк = new Волк();
...
Поиск универсальныйПоиск = new Поиск(); //one

Путь путьЗайца = универсальныйПоиск.get(map, координатаЗайца, Морковка.getClass());
Путь путьВолка = универсальныйПоиск.get(map, координатаВолка, Заяц.getClass());

-Экземпляр поиска создается каждый раз при вызове метода makeMove() в креатурах

public abstract class Creature extends Entity {
  //...
  public void makeMove(Coordinates from, WorldMap map) {
    PathFinder pathFinder = new BreadthFirstSearch(...);
    Path path = pathFinder.findPath(...);
    //...
  }
}

//ПРАВИЛЬНО:
public abstract class Creature extends Entity {
  private final PathFinder pathFinder = new BreadthFirstSearch(...);
  //...
  public void makeMove(Coordinates from, WorldMap map) {
    Path path = pathFinder.findPath(...);
    //...
  }
}
  1. Action'ы

+MakeMoveAction- хорошо. +EntitySpawnAction, помещает существ на карту. Универсальный класс(т.е. селит на карту существ любого класса), для этого принимает в аргументы Supplier, который описывает создание нужных существ- хорошо.

  1. interface Renderer +Интерфейс для распечатки это хорошо- можно делать разные рендереры.

  2. class ConsoleRenderer implements Renderer +Хранит в себе спрайты существ, хорошо. +Спрайты сделыны константами, хорошо. +В дефолте свич-кейса бросается исключение, если пришло неизвестное существо, хорошо.

  3. Simulation и SimulationController +Все ок

===== Вывод: получилось хорошо. Не зря столько времени продумывал архитектуру. Замечания есть, но несущественные.

#ревью #симуляция

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment