Заполнение объекта zoo объектами animal с помощью перечисления в Java

будучи привыкшим к процедурному программированию старой школы C, я теперь изучаю Java и пришел к, возможно, очевидному пониманию того, что жесткий бит конструкция, а не синтаксис.

я потратил часы, отбрасывая идею за идеей о том, как заполнить мой зоопарк:

у меня есть class Zoo и абстрактный class Animal. Существует несколько не абстрактных подклассов Animal под названием Lion, Giraffe, Zebra, Penguin и так далее. А

5 ответов


требования

читая ваш вопрос я нашел следующие требования зоопарк:

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

1. Поиск животных по их названию вида

как вы упомянули, существует нежелательное разделение между строкой "LION" и класс Lion. В эффективной Java, пункт 50, следующей строки:

строки являются плохими заменителями других типов значений. Когда часть данных поступает в программу из файла, из сети или с клавиатуры, она часто находится в Строковой форме. Существует естественная тенденция оставлять это так, но эта тенденция оправдана только в том случае, если данные действительно являются текстовыми по своей природе. Если он числовой, его следует перевести в соответствующий числовой тип, например int, float, или BigInteger. Если это ответ на вопрос " да " или "нет", его следует перевести в boolean. В более общем плане, если есть соответствующий тип значения, будь то примитив или ссылка на объект, вы должны использовать его; если нет, вы должны написать его. Хотя этот совет может показаться очевидным, он часто нарушается.

поэтому, вместо того, чтобы искать животных по их видам имя вы должны искать животных по их видам экземпляр.

2. Определение вида

чтобы определить порядок видов, вам нужно использовать коллекцию с предсказуемым порядком итераций, например, упомянутый LinkedHashMap.

3. Легко добавлять животных в будущем

добавление животных в настоящее время состоит из трех шагов, которые вы упомянули. Побочным эффектом использования перечисления является то, что только человек с доступом к исходному коду имеет возможность добавлять новые виды (как the Species перечисление должно быть расширено).

Теперь рассмотрим, Species.LION, это вид Lion класса. Обратите внимание, что это отношение семантически такое же, как отношение между классом и его экземпляров. Поэтому гораздо более элегантным решением было бы использовать Lion.class как вида Lion. Это также уменьшает количество шагов для добавления животного, поскольку вы получаете вид бесплатно.

анализ других частей код

зоопарк

в вашем предложении, зоопарк несет ответственность за создание животных. Следствием этого является то, что каждый зоопарк когда-либо созданный должен использовать все определенные животные (из-за Species перечисление) и используйте указанный заказ, не будет никаких изменений среди зоопарка. Лучше отделить создание животных от зоопарка, чтобы обеспечить большую гибкость как животных, так и зоопарк.

животные

из-за их гибкости интерфейсы должны быть предпочтительнее абстрактных классов. Как объясняется в эффективной Java, пункт 18:

пункт 18. Предпочитайте интерфейсы абстрактным классам

язык программирования Java предоставляет два механизма для определения типа, который позволяет использовать несколько реализаций: интерфейсы и абстрактные классы. Наиболее очевидным различием между этими двумя механизмами является что абстрактные классы могут содержать реализации для некоторых методов, а интерфейсы-нет. Более важным отличием является то, что для реализации типа, определенного абстрактным классом, класс должен быть подклассом абстрактного класса. Любой класс, который определяет все необходимые методы и подчиняется общему контракту, может реализовать интерфейс, независимо от того, где класс находится в иерархии классов. Поскольку Java разрешает только одно наследование, это ограничение на абстрактные классы сильно ограничивают их использование в качестве определений типов.

циклические ссылки

в вашем вопросе вы упоминаете, что животное также должно иметь ссылку на зоопарк, в котором оно находится. Это вводит круговую ссылку, которой следует избегать как можно больше.

одним из многих недостатков круговых ссылок является:

круговые ссылки класса создают высокое соединение; оба занятия должны быть перекомпилированы каждый раз, когда любой из них меняется.

пример реализации

public interface Animal {}

public class Zoo {
  private final SetMultimap<Class<? extends Animal>, Animal> animals;

  public Zoo() {
    animals = LinkedHashMultimap.create();
  }

  public void addAnimal(Animal animal) {
    animals.put(animal.getClass(), animal);
  }

  @SuppressWarnings("unchecked") // the cast is safe
  public <T extends Animal> Set<T> getAnimals(Class<T> species) {
    return (Set<T>) animals.get(species);
  }
}

использование

static class Lion implements Animal {}

static class Zebra implements Animal {}

final Zoo zoo = new Zoo();
zoo.addAnimal(new Lion());
zoo.addAnimal(new Zebra());
zoo.addAnimal(new Lion());

zoo.getAnimals(Lion.class); // returns two lion instances

zoo.getSpeciesOrdering(); // returns [Lion.class, Zebra.class]

Обсуждение

реализация выше имеет поддержку нескольких экземпляров животных для каждого вида, так как это, кажется, имеет больше смысла для зоопарка. Если требуется только одно животное, подумайте об использовании гуава по ClassToInstanceMap вместо SetMultimap.

создание животных не рассматривался как часть проблемы проектирования. Если нужно построить более сложных животных, подумайте об использовании шаблон построителя.

добавление животного теперь так же просто, как создание нового класса, который реализует Animal интерфейс и добавление его в зоопарк.


хотя, возможно, незначительная вещь, следует позаботиться о том, чтобы избежать ненужных круговых ссылок/зависимостей. Я не хочу ссылаться на догматическую точку зрения, что все формы круговых зависимостей плохи, но в зависимости от того, как вы хотите использовать свою программу, вам может быть лучше не иметь животного, зависящего от зоопарка, а зоопарк зависит от нескольких животных одновременно.

в вашем примере класс Zoo и животное, возможно, более тесно связаны, чем они должны быть. Если вы хотите переместить животное в другой зоопарк, вы должны обновить как экземпляр зоопарка, так и экземпляр животного. Если вы хотите поддерживать двунаправленную навигацию, вы можете использовать, например, двунаправленную карту в зоопарке, поскольку зоопарк будет нести ответственность за агрегирование животных (что также делает зоопарк в реальном мире, подумайте об этом), но зоопарк также будет единственным местом, где поддерживаются эти отношения. На мой взгляд, это чище.

еще одно обсуждение точку циркулярных ссылок можно найти здесь: https://softwareengineering.stackexchange.com/questions/11856/whats-wrong-with-circular-references


Да, ваш подход кажется правильным.

однако, если у вас есть только одно животное по видам, возможно, вы можете непосредственно создать его экземпляр в статическом поле без необходимости конкретного метода.

вы также можете добавить фабрика класс такой:

interface AnimalFactory<T extends Animal> {
    T make();
}

и реализует фабрику для каждого вида животных:

class LionFactory implements AnimalFactory<Lion> {
     public Lion make() {
         return new Lion(); 
     }
}

и вы можете также добавить эту фабрику сразу в вашем перечислении:

enum Species { 
    LION ( new LionFactory() ), 
    // As AnimalFactory is a functional interface, you can use lambda expressions
    GIRAFFE ( () -> new Giraffe() );

    private AnimalFactory _factory;

    Species( AnimalFactory factory ) {
        this._factory = factory;
    }

    AnimalFactory getFactory() {
        return _factory;
    }
}

и затем, если вы хотите жираф, тебе надо только позвонить:

Animal giraffe = Species.GIRAFFE.getFactory().make();

в целом ваш дизайн выглядит хорошо, я рассмотрю несколько моментов, которые вы подняли:

  1. Я предлагаю животное интерфейса, которое унаследует другой конкретный класс животных , чтобы вы могли получить доступ ко всем животным одинаково.
  2. определить порядок-вот вариант:

    List<Animal> animals = new ArrayList<Animal>(); // List
    animals.Add(new Lion()); // Order 0
    animals.Add(new Giraffe()); // Order 1
    animals.Add(new Zebra()); // Order 2
    

    вы можете заменить новый ConcreteAnimal () методом, создающим экземпляр. Что-то вроде:

    Animal CreateAnimal(AnimalType animalType);
    

    ListArrayList) является последовательным списком. Таким образом, порядок вставки и извлечения гарантируется одинаковым. Кроме того, вы можете добавить / удалить тип животных очень легко.


шаблон дизайна, который вы использовали очень похож на Шаблон Проектирования Фабрика. которое приходит под категорию Creational картины конструкции. Такого рода шаблоны дизайна имеют дело с творениями объектов.

метод, который создает объекты для животных makeAnimal(Species species) можно поместить в отдельный класс, который по существу можно назвать чем-то в порядке "AnimalFactory" или что-то в этом роде.

кроме этого, я думаю, ваш дизайн звук.