Многомерный хэш / матрица Java - PullRequest
3 голосов
/ 26 октября 2011

У меня есть n классов, которые либо складываются, либо не складываются друг на друга.Все эти классы расширяют один и тот же класс (CellObject).Я знаю, что в этот список будет добавлено больше классов, и я хочу создать какой-то способ, которым легко манипулировать «наращиваемостью» в одном месте.

Я думал о создании матрицы, гдеrow-index - это класс в нижней части стека, а индекс столбца - это класс в верхней части стека.Значение будет true (или 1), если вы можете сложить сверху вниз, false (0) в противном случае.

Однако, мой коллега предлагает создать n + 1 метод canStack.Один общий метод canStack включит оператор instanceof, который направит его в один из n подметодов.Каждый из подметодов просто отвечает на вопрос, может ли верхний объект укладываться на нижний объект сам по себе.

Я думаю, что мое решение более элегантно / чисто.Это правда?Если так, как бы я это реализовал?

Я изменил объекты на классы

Ответы [ 3 ]

5 голосов
/ 26 октября 2011

Ваше решение будет короче. Но у него есть недостаток: если вы добавите подкласс CellObject, вы можете забыть изменить свой массив. Даже если вы знаете, что это должно произойти, кто-то может когда-нибудь поработать над кодом. Опять же, его решение имеет ту же проблему.

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

Примерно так:

@interface Stackable {
    Class<? extends CellObject>[] stackables(); //Classes that may stack on the annotated one
    Class<? extends Cellobject>[] pillars(); //Classes this one can stack on
}

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

Если вы используете процессор аннотаций, то, возможно, было бы безопаснее использовать массивы String с каноническими именами классов, поскольку объекты Class могут быть недоступны еще во время компиляции. Его выполнимость также будет зависеть от того, все ли классы CellObject всегда находятся в одном прогоне компиляции или нет.

Использование отражения (возможно, когда в аннотации указан RetentionType RUNTIME), представляется здесь приемлемым вариантом. Проверьте массив; если соответствующий элемент имеет значение NULL (это можно сделать, используя Boolean вместо Boolean), выполните рефлексию и заполните этот элемент. В следующий раз вы можете избежать отражения, лениво заполняя массив по мере необходимости.

РЕДАКТИРОВАТЬ: забыл упомянуть, мое решение не обязывает вас поддерживать актуальность метаданных. Кроме того, сложность может быть уменьшена, если наращиваемость является транзитивной. То есть, A может складываться на B, а B может складываться на C, подразумевает, что A может складываться на C.

1 голос
/ 26 октября 2011

Я не думаю, что ваша концепция матрицы будет хорошим способом для достижения вашей цели.Вы получите огромную матрицу, которая содержит все возможности.Очевидно, что извлечение информации, которую вы хотите из матрицы, будет довольно легким, но поддержание ее в долгосрочной перспективе может оказаться болезненным, так как добавляется больше CellObject подклассов.То же самое относится к n + 1 методам, предложенным вашим коллегой.

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

Одна вещь, которую вы могли бы сделать, это иметь карту в вашем CellObject суперклассе, которая будет хранить вашу информацию о «наращиваемости», и предоставлять методы для заполнения этой карты и получения возможности размещения члена класса A на членекласс B. Примерно так:

public abstract class CellObject
    {
    private static Map<Class<? extends CellObject>, Map<Class<? extends CellObject>, Boolean>> fullStackabilityMap = 
            new HashMap<Class<? extends CellObject>, Map<Class<? extends CellObject>, Boolean>> ();

    protected static void addStackableOnObjectInformation (Class<? extends CellObject> baseObjectClass, Class<? extends CellObject> objectToStack, boolean canStackOnObject)
        {
        Map<Class<? extends CellObject>, Boolean> stackableMapForObject = fullStackabilityMap.get (baseObjectClass);

        if (stackableMapForObject == null)
            {
            stackableMapForObject = new HashMap<Class<? extends CellObject>, Boolean> ();
            fullStackabilityMap.put (baseObjectClass, stackableMapForObject);
            }

        stackableMapForObject.put (objectToStack, canStackOnObject);
        }

    protected boolean isStackableOnObject (CellObject baseObject)
        {
        Map<Class<? extends CellObject>, Boolean> stackableMapForObject = CellObject.fullStackabilityMap.get (baseObject.getClass ());

        if (stackableMapForObject == null)
            {
            return false;
            }

        Boolean canStackOnObject = stackableMapForObject.get (this.getClass ());
        return canStackOnObject != null ? canStackOnObject : false;  //Assume that the object cannot be stacked if it was not specified
        }
    }

public class CellObjectA extends CellObject
    {
    }

public class CellObjectB extends CellObject
    {
    static
        {
        addStackableOnObjectInformation (CellObjectB.class, CellObjectA.class, true);
        }
    }

public class CellObjectC extends CellObject
    {
    static 
        {
        addStackableOnObjectInformation (CellObjectC.class, CellObjectA.class, true);
        addStackableOnObjectInformation (CellObjectC.class, CellObjectB.class, true);
        }
    }

Создание fullStackabilityMap в CellObject кажется сложным из-за отсутствия в Java оператора Diamond в Java 6, но его можно упростить, если вы напишите утилитуметод, который создает карты, или использовать Гуава.

Таким образом, в этом примере экземпляры CellObjectC не могут быть наращиваемыми по видам объектов;CellObjectB экземпляры могут быть сложены только на CellObjectC объектах, а CellObjectA могут быть сложены на CellObjectB или CellObjectC объектах.

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

  • Вам нужно только указать, какой тип объекта может быть уложен на какой тип объекта.Нет необходимости полностью инициализировать матрицу со всеми возможностями.
  • Вы можете напрямую спросить объект, может ли он быть сложен на объекте любого типа, вместо того, чтобы выполнять статический опрос внешнего класса, что для меня прощеподдерживать и генерировать более чистый код.
  • Вам не нужно обслуживать n + 1 методов, которые сообщают вам, что объект A может быть сложен на объекте B, что будет полным кошмаром, если вы в итоге получите значительныйколичество CellObject подклассов.
1 голос
/ 26 октября 2011

Матричный подход будет масштабироваться как O (n 2 ) . Напротив, другой подход будет масштабироваться как O (n) , но его было бы рискованно поддерживать.

В качестве альтернативы рассмотрите возможность предоставления abstract CellObject подходящего интерфейса Stackable, но отложите реализацию до конкретного подкласса n . компилятор немедленно идентифицирует отсутствующие реализации. См. Также Когда абстрактный класс реализует интерфейс .

interface Stackable {

    boolean canStack(Stackable other);
}

abstract class CellObject implements Stackable {}

class Cell01 extends CellObject {

    @Override
    public boolean canStack(Stackable other) {
        return true; // TODO
    }
}

class Cell02 extends CellObject {

    @Override
    public boolean canStack(Stackable other) {
        return true; // TODO
    }
}
...
...