ПРОБЛЕМА ДИЗАЙНА: Как правильно использовать композицию - PullRequest
4 голосов
/ 28 октября 2019

Я работаю над проектом и сомневаюсь в его дизайне. Как лучше разработать следующую задачу (в JAVA):

Класс A со следующими атрибутами:

  • HashSet of Pixels, где каждый пиксель имеет x, координаты y и значение v между 0-1.
  • экземпляром класса B.

Классом B со следующей функцией:

  • функция, которая получает пиксель и возвращает его левого соседа.

Когда я в классе AI, я хочу использовать B.function для каждого пикселя в A и добавлять его в HashSet, только еслиэто еще не там. Проблема в том, что я не хочу отправлять HashSet в функцию, насколько плохо возвращать новый экземпляр Pixel из функции, если она уже существует (эта функция будет работать на многих пикселях и создаст много неиспользуемых экземпляровof Pixel).

Какие еще есть варианты?

Ответы [ 2 ]

0 голосов
/ 29 октября 2019

Я вижу здесь несколько проблем, касающихся объектно-ориентированного программирования.

  1. Нарушение инкапсуляции: когда вы вызываете функцию B из A, которая работает с данными A (чего вы избегаете, не отправляя HashMap),это нарушает инкапсуляцию (если есть причина, хотя это приемлемо). Можно ли переместить эту функцию (работающую на HashSet A) в A? Это защитит состояние А. от раскрытия.
  2. Распространение классов: Существует вероятность того, что будет большое количество объектов типа Point. Вы можете подумать об использовании шаблона проектирования Flyweight GOF, он выведет состояние каждой точки и сделает его многоразовым. и существенно уменьшит число.
  3. Передача большого набора точек методу в B: если вы можете переместить метод с B на A, эта точка будет решена. В любом случае java передаст эту коллекцию по ссылке. Но в этом случае он открыт для изменений из внешних классов (необходимо позаботиться об этом аспекте).
  4. Абстракция типа Point : если класс Point имеет только состояние и не имеет поведения, он будетпривести к нарушению инкапсуляции. Можете ли вы переместить метод getNeighbour () в пункт? как это сделает Point неизменным (что важно). Фактический алгоритм может быть делегирован другому классу (если его независимо изменяющаяся ответственность и иерархия алгоритмов, подумайте о паттерне стратегии GOF здесь).
  5. Уникальность точек в наборе: о чем ваш набор будет заботиться должным образомзаботиться о соответствующем хеше и логическом равенстве для класса Point.
0 голосов
/ 29 октября 2019

Поскольку вы используете Set<Pixel>, вы должны создать новый экземпляр Pixel, чтобы проверить, существует ли он в наборе или нет.

Если набор содержит N элементов после вызова B.function метод, вы будете создавать дополнительные N Pixel узлов. Если все элементы новые, вы просто добавляете их для установки, в противном случае Garbage Collection необходимо их развернуть. Один из недостатков заключается в том, что нам нужно создать m (тогда m <= N - количество Pixel -ов, которые уже существуют в наборе), а затем нам нужно собрать их по GC. Насколько велико соотношение m/N, зависит от вашего алгоритма и того, что вы на самом деле делаете.

Позволяет рассчитать, сколько памяти нам нужно использовать для набора N = 1_000_000 пикселей. Мы знаем, что int - это 4 bytes, а double - это 8 bytes, давайте добавим дополнительные 8 bytes для объекта и 8 bytes для ссылки. Это дает 32 bytes для каждого экземпляра Pixel объекта. Нам нужно создать N объектов, что дает 32MB. Давайте предположим, что наше соотношение составляет 50%, поэтому 16MB мы выделили просто для того, чтобы проверить, что оно не нужно.

Если это стоимость, которую вы не можете заплатить, вам необходимо разработать алгоритм, который позволит вам перебирать Set<Pixel> при заказе от left-to-right. Таким образом, левый сосед Pixel X находится перед X.

Предположим, что левый сосед Pixel X(x, y) равен пикселю X'(x - 1, y). Pixel B(0, y) не имеет соседа. Вам необходимо использовать TreeSet и реализовать интерфейс Comparable<Pixel> в классе Pixel. Простая реализация может выглядеть следующим образом:

@Override
public int compareTo(Pixel o) {
    return this.y == o.y ? this.x - o.x : this.y - o.y;
}

Это позволяет вам перебирать набор в порядке слева направо: (0, 0), (1, 0), ...., (x - 1, y), (x, y), (x + 1, y), ... , (maxX, maxY). Поэтому, когда вы выполняете итерацию, вы можете проверить, является ли предыдущий элемент левым соседом текущего Pixel. Пример реализации может выглядеть следующим образом:

void addNeighboursIfNeeded() {
    Set<Pixel> neighbours = new HashSet<>(pixels.size());
    Pixel last = null;
    for (Pixel p : pixels) {
        if (p.getX() == 0 || p.isLeftNeighbour(last)) {
            // a left border pixel
            // or last checked element is a left neighbour of current pixel.
            last = p;
            continue;
        }
        // last element was not our left-neighbour so we need to call b method
        Pixel left = b.getLeft(p);
        neighbours.add(left);
        last = p;
    }
    // add all new neigbours
    pixels.addAll(neighbours);
}

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

...