Имитация статического абстрактного и динамического связывания при вызове статического метода в Java - PullRequest
23 голосов
/ 05 марта 2011

Введение

В качестве отказа от ответственности я прочитал Почему статические методы не могут быть абстрактными в Java и, даже если я с уважением не согласен с принятым ответом о "логическом противоречии"", Я не хочу никакого ответа о полезности static abstract просто ответа на мой вопрос;)

У меня есть иерархия классов, представляющая некоторые таблицы из базы данных.Каждый класс наследует класс Entity, который содержит множество служебных методов для доступа к базе данных, создания запросов, экранирования символов и т. Д.

Каждый экземпляр класса представляет собой строку из базы данных.

Проблема

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

Очевидным способом хранения этих данных являются статические поля, возвращаемые статическими методами в каждом классе.Проблема в том, что вы не можете заставить класс реализовать эти статические методы, и вы не можете выполнять динамическое связывание при вызове статических методов в Java.

Мои решения

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

Вопрос

Как вы справитесь с отсутствием abstract static и динамическими ссылками на абстрактный метод?

В идеальном мире данное решение должно генерировать ошибку компиляции, если информация для класса отсутствует и данные должны быть легко доступны из класса Entity.

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

Просто чтобы прояснить, у меня нет никаких требований, кроме простоты,Ничто не должно быть статичным.Я только хочу получить имя таблицы и столбца из Entity для построения запроса.

Некоторый код

class Entity {
    public static function afunction(Class clazz) { // this parameter is an option
        // here I need to have access to table name of any children of Entity
    }
}

class A extends Entity {
    static String table = "a";
}

class B extends Entity {
    static String table = "b";
}

Ответы [ 8 ]

21 голосов
/ 07 марта 2011

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

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

Если бы я использовал ваш пример, я бы пошел по этому пути:

@Target(ElementType.TYPE)
@Retention(RetentionType.SOURCE)
@interface MetaData {
  String table();
}

abstract class Entity {}

@MetaData(table="a")
class A extends Entity {}

@MetaData(table="b")
class B extends Entity {}

class EntityGetter {
  public <E extends Entity> E getEntity(Class<E> type) {
    MetaData metaData = type.getAnnotation(MetaData.class);
    if (metaData == null) {
      throw new Error("Should have been compiled with the preprocessor.");
      // Yes, do throw an Error. It's a compile-time error, not a simple exceptional condition.
    }
    String table = metaData.table();
    // do whatever you need.
  }
}

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

Полная документация доступна в документации к пакету javax.annotation.processing.

Кроме того, в Интернете доступно несколько учебных пособий, если вы ищете «обработка аннотаций Java».

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

3 голосов
/ 05 марта 2011

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

Вы также можете использовать инструмент обработки аннотаций времени компиляции, который поставляется с Java., который также может быть интегрирован в сборки IDE.Прочитайте и попробуйте.

1 голос
/ 13 марта 2011

Я знаю решение, предоставляющее все, что вы хотите, но это огромный взлом, которого я бы не хотел в своем собственном коде в наше время:

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

В противном случае создайте интерфейс с методами, обеспечивающими все ваши данные, подобные этому

public interface EntityMetaData{
    public String getTableName();
    ...
}

Все подклассы Entity должны были бы реализовать этохотя интерфейс.

Теперь ваша проблема - вызвать эти методы из вашего статического служебного метода, так как у вас там нет экземпляра.Так что вам нужно создать экземпляр.Использование Class.newInstance() нецелесообразно, так как вам потребуется нулевой конструктор, и может потребоваться дорогая инициализация или инициализация с побочными эффектами, происходящими в конструкторе, запускать не нужно.

ВзломЯ предлагаю использовать Objenesis для создания экземпляра вашего класса.Эта библиотека позволяет создавать любой класс, не вызывая конструктор.Нет нужды в нулевом конструкторе.Они делают это с некоторыми огромными внутренними взломами, которые адаптированы для всех основных JVM.

Таким образом, ваш код будет выглядеть следующим образом:

public static function afunction(Class clazz) {
    Objenesis objenesis = new ObjenesisStd();
    ObjectInstantiator instantiator = objenesis.getInstantiatorOf(clazz);
    Entity entity = (Entity)instantiator.newInstance();
    // use it
    String tableName = entity.getTableName();
    ...
}

Очевидно, вы должны кэшировать свои экземпляры, используя Map<Class,Entity>, что практически сводит на нет затраты времени выполнения (один поиск в вашей карте кэширования).

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

Но это не очень хороший дизайн!Я советую не использовать такой хак, даже если он пока работает, он может остановиться в следующей JVM.И тогда вам придется молиться об обновлении Objenesis ...

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

1 голос
/ 12 марта 2011

Идея Mi, состоит в том, чтобы пропустить материал таблиц и обратиться к "Нет абстрактных статических методов". Используйте "псевдо-абстрактно-статические" методы.

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

public class StaticAbstractCallException extends Exception {

  StaticAbstractCallException (String strMessage){
    super(strMessage);
   }

   public String toString(){
    return "StaticAbstractCallException";
   }  
} // class

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

abstract class MyDynamicDevice {
   public static void start() {
       throw new StaticAbstractCallException("MyDynamicDevice.start()"); 
   }

   public static void doSomething() {
       throw new StaticAbstractCallException("MyDynamicDevice.doSomething()"); 
   }

   public static void finish() {
       throw new StaticAbstractCallException("MyDynamicDevice.finish()"); 
   }

   // other "abstract" static methods
} // class

... И наконец, определите подклассы, которые переопределяют «псевдо-абстрактные» методы.

class myPrinterBrandDevice extends MyDynamicDevice {

   public static void start() {
       // override MyStaticLibrary.start()
   }

   /*
   // ops, we forgot to override this method !!!
   public static void doSomething() {
       // ...
   }
   */

   public static void finish() {
       // override MyStaticLibrary.finish()
   }

   // other abstract static methods
} // class

Когда вызывается статическая переменная myStringLibrary doSomething, генерируется исключение.

1 голос
/ 11 марта 2011

В Java наиболее похожим подходом к «статическим классам» являются статические перечисления.

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

enum может определять один или несколько частных конструкторов, принимая некоторые параметры инициализации (например, имя таблицы, набор столбцов и т. Д.).

Класс enum может определять abstract методы, которые должны быть реализованы конкретными элементами для компиляции.

public enum EntityMetadata {

    TABLE_A("TableA", new String[]{"ID", "DESC"}) {

        @Override
        public void doSomethingWeirdAndExclusive() {

            Logger.getLogger(getTableName()).info("I'm positively TableA Metadata");

        }
    },
    TABLE_B("TableB", new String[]{"ID", "AMOUNT", "CURRENCY"}) {

        @Override
        public void doSomethingWeirdAndExclusive() {

            Logger.getLogger(getTableName()).info("FOO BAR message, or whatever");

        }
    };  

    private String tableName;
    private String[] columnNames;

    private EntityMetadata(String aTableName, String[] someColumnNames) {
        tableName=aTableName;
        columnNames=someColumnNames;
    }

    public String getTableName() {
        return tableName;
    }

    public String[] getColumnNames() {
        return columnNames;
    }


    public abstract void doSomethingWeirdAndExclusive();

}

Тогда для доступа к метаданным конкретной сущности этого будет достаточно:

EntityMetadata.TABLE_B.doSomethingWeirdAndExclusive();

Вы также можете ссылаться на них из реализации Entity , заставляя каждый ссылаться на элемент EntityMetadata:

abstract class Entity {

    public abstract EntityMetadata getMetadata();

}

class A extends Entity {

   public EntityMetadata getMetadata() {
       return EntityMetadata.TABLE_A;
   }
}

class B extends Entity {

   public EntityMetadata getMetadata() {
       return EntityMetadata.TABLE_B;
   }
}

ИМО, такой подход будет быстрым и легким.

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

0 голосов
/ 09 марта 2011

Во-первых, позвольте мне сказать вам, что я согласен с вами. Я хотел бы, чтобы у вас был способ использовать статический метод в классах.
В качестве решения вы можете «продлить» время компиляции с помощью пользовательской задачи ANT, которая проверяет наличие таких методов, и получить ошибку во время компиляции. Конечно, это не поможет вам в вашей IDE, но вы можете использовать настраиваемый статический анализатор кода, такой как PMD, и создать собственное правило для проверки того же.
И вот вы java компилируете (ну, почти компилируете) и редактируете проверку ошибок времени.
Эмуляция динамической компоновки ... ну, это сложнее. Я не уверен, что понимаю, что вы имеете в виду. Можете ли вы написать пример того, что вы ожидаете?

0 голосов
/ 07 марта 2011

Я бы абстрагировал все метаданные для сущностей (имена таблиц, имена столбцов) в службу, неизвестную самим сущностям.Было бы намного чище, чем иметь эту информацию внутри сущностей

MetaData md = metadataProvider.GetMetaData<T>();
String tableName = md.getTableName();
0 голосов
/ 05 марта 2011

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

class Entity {
   private static final ConcurrentMap<Class, EntityMetadata> metadataMap = new ...;

   Entity(EntityMetadata entityMetadata) {
      metadataMap.putIfAbsent(getClass(), entityMetadata);
   }

   public static EntityMetadata getMetadata(Class clazz) {
      return metadataMap.get(clazz);
   }
}

Я бы хотел, чтобы больше было потрачено впустую ссылки, но динамически:

class Entity {
   protected final EntityMetadata entityMetadata;

   public Entity(EntityMetadata entityMetadata) {
      this.entityMetadata=entityMetadata;
   }
}

class A extends Entity {
  static {
     MetadataFactory.setMetadataFor(A.class, ...);
  }

  public A() {
     super(MetadataFactory.getMetadataFor(A.class));
  }
}

class MetadataFactory {
   public static EntityMetadata getMetadataFor(Class clazz) {
      return ...;
   }

   public static void setMetadataFor(Class clazz, EntityMetadata metadata) {
      ...;
   }
}

Вы можете полностью избавиться от EntityMetadata в Entity и оставить его только на заводе. Да, это не заставит предоставлять его для каждого класса во время компиляции, но вы можете легко применить это во время выполнения. Ошибки времени компиляции велики, но в конце концов они не являются святыми коровами, так как вы всегда получите ошибку сразу, если класс не предоставит соответствующую часть метаданных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...