Skip to content

Задание 1 Контейнер классов

Dmitry Arkhangelskiy edited this page Mar 15, 2016 · 6 revisions

Сроки сдачи. Оценка

Оценивается из 18 баллов. Сдача до 25 марта 20-00 - полный балл Сдача после - штраф 50%

Описание

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

Классы содержат поля, которые могут быть примитивными типами или ссылками на объекты других классов (зависимости). Пусть есть xml конфигурация, описывающая все объекты, нужные в приложении и их зависимости. Проанализировав зависимости, можно установить порядок инициализации и проверить, что не существует циклических зависимостей. Далее, мы можем создать экземпляры классов в правильном порядке и использовать в своем приложении.

Формат конфига

Для проекта с классами

class Car {
  private Engine engine;
  private Gear gear;
}

class Gear {
  private int count;
}

class Engine {
  private int power;
}

конфиг может выглядеть так

<root>

    <bean id="carBean" class="arhangel.dim.ioc.beans.Car">
        <property name="gear" ref="gearBean"/>
        <property name="engine" ref="engineBean"/>
    </bean>

    <bean id="gearBean" class="arhangel.dim.ioc.beans.Gear">
        <property name="count" val="6"/>
    </bean>

    <bean id="engineBean" class="arhangel.dim.ioc.beans.Engine">
        <property name="power" val="200"/>
    </bean>

</root>
  • root - корневой элемент конфига

  • bean - описание экземпляра класса (его полей)

    • id - уникальное имя экземпляра
    • class - определяет класс объекта
  • property - описание конкретного поля

    • name - имя свойства, должно совпадать с именем поля класса
    • val - примитивное значение, или
    • ref - поле ссылается на другой объект

Таким, образом, конфиг эквивалентен такому коду

Gear gear = new Gear();
gear.setCount(6);

Engine engine = new Engine();
engine.setPower(200);

Car car = new Car();
car.setEngine(engine);
car.setPower(power);

Задание

  1. (5 баллов) Даны классы Bean, Property, ValueType с комментариями. Написать класс XmlReader, который прочитает заданный xml файл, распарсит его и создаст список beans, соответствующий конфигу.
class BeanXmlReader {

    private static final String TAG_BEAN = "bean";
    private static final String TAG_PROPERTY = "property";
    private static final String ATTR_NAME = "name";
    private static final String ATTR_VALUE = "val";
    private static final String ATTR_REF = "ref";
    private static final String ATTR_BEAN_ID = "id";
    private static final String ATTR_BEAN_CLASS = "class";

    public List<Bean> parseBeans(String pathToFile) {
      .. 
    }
}
  1. (5 баллов) Теперь у вас есть список beans, у некоторых из них есть ref-зависимости, то есть ссылки на другие объекты. Нужно определить порядок инициализации. Будем считать, что bean - это вершина графа, а зависимости - это ребра между ними, тогда для определения порядка на графе достаточно отсортировать его. Поиск в глубину в графе - http://e-maxx.ru/algo/dfs. Топологическая сортировка - http://e-maxx.ru/algo/topological_sort и https://habrahabr.ru/post/100953/

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

// в каждой вершине храним связанный бин
class Vertex {
  Bean bean;
} 

class Graph {
  Map<Vertex, List<Vertex>> vertices = new HashMap<>();

  public Vertex addVertex(Bean value) {... }
  public void addEdge(Vertex from, Vertex to) {... }
  public boolean isConnected(Vertex v1, Vertex v2) {... }
  public List<Vertex> getLinked(Vertex vertex) {... }
  public List<Vertex> sort(){... }

}

2*) (2 балла) Встроить проверку, что нет циклических зависимостей

  1. (5 баллов) Инстанцирование бинов После чтения полного конфига с помощью механизма reflection нужно инстанцировать наши объекты.

Ключевой класс - Container

package arhangel.dim.container;

public class Container {
    private List<Bean> beans;

    /**
     * Если не получается считать конфиг, то бросьте исключение
     * @throws InvalidConfigurationException - неверный конфиг
     */
    public Container(String pathToConfig) throws InvalidConfigurationException {
    }

    /**
     *  Вернуть объект по имени бина из конфига
     *  Например, Car car = (Car) container.getByName("carBean")
     */
    public Object getByName(String name) {
        return null;
    }

    /**
     * Вернуть объект по имени класса
     * Например, Car car = (Car) container.getByClass("arhangel.dim.container.Car")
     */
    public Object getByClass(String className) {
        return null;
    }

}

Пример использования в коде

Container container = new Container("config.xml");
Car car = (Car) container.getByClass("arhangel.dim.container.beans.Car");

Договоримся, что у классов, которые создаются таким образом есть пустой конструктор, его поля - приватные, но имеют методы get/set (обратите внимание на именование метода, это важно):

public class Gear {
    private int count;

    public Gear() {
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}

То есть алгоритм такой

  1. Получить список бинов

  2. Отсортировать в правильно порядке (или с помощью алгоритма - это доп балл или задать правильный порядок в xml)

  3. Последовательно проходить по списку и для каждого бина

    a. Вызывать дефолтный конструктор

    b. Проходить по Property и устанавливать в поля соответствующие значения

    c. Созданный объект сохранять в Map<String, Object> objByName (маппинг ИмяБина->Объект) и в Map<String, Object> objByClassName (маппинг ИмяКласса -> Объект )

4*) (1 балл) В пункте 3b вместо установки значения в поле вызывать метод set<ИмяПоля>