Подтвердить что ты не робот

Альтернативы статическим методам на интерфейсах для обеспечения согласованности

В Java я хотел бы иметь возможность определять интерфейсы маркеров, которые принудительно реализуют статические методы. Например, для простой сериализации/десериализации сериализации я хотел бы определить интерфейс, который выглядит примерно так:

public interface TextTransformable<T>{

  public static T fromText(String text);

  public String toText();

Так как интерфейсы в Java не могут содержать статические методы (как указано в ряде других сообщений/потоков: здесь, здесь, и здесь этот код не работает.

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

Примечание: в нашем случае наш основной случай использования состоит в том, что у нас есть много, много типов "value-object" - перечисления или другие объекты, которые имеют ограниченное число значений, как правило, не имеют состояния, превышающего их значение, и которое мы parse/de-parse тысячи раз в секунду, поэтому на самом деле заботиться о повторном использовании экземпляров (например, Float, Integer и т.д.) и его влиянии на потребление памяти /gc

Любые мысли?

EDIT1: Чтобы устранить некоторую путаницу - у нас есть много разных объектов, соответствующих этому шаблону - мы стараемся придумать что-то элегантное для звонящих с 2 семантикой:

  • Интерфейсы как контракты - единство доступа (например, TextTransformable как возможность)
  • Требуется реализация подклассами/реализациями (например, заставить их реализовать свое собственное преобразование

С точки зрения наших мыслей о Flyweight, Factories - они оба варианта, которые мы рассмотрели, действительно мы пытаемся понять, можем ли мы найти что-то более элегантное, чем полагаться на JavaDoc, говорящий "реализовать factory и делегировать на него делегат или выставлять его в местоположении XXX по соглашению"

4b9b3361

Ответ 1

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

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

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

 public class ValueType {
       public static final TextTransformable<ValueType> CONVERT = ....
 }

И затем используйте его следующим образом:

 ValueType value = ValueType.CONVERT.fromText(text);

 String text = ValueType.CONVERT.toText(value);

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

Изменить: я думаю, я не знаю, что вы находите inelegant о factory, но я думаю, что вы сосредоточены на звонящих, так как это вам кажется:

  ValueType value = getTransformer(ValueType.class).fromText(text);

Вышеупомянутое может быть сделано с помощью статического импорта factory и метода, который имеет такую ​​подпись:

   public static <T> TextTransformable<T> getTransformer(Class<T> type) {
         ...
   }

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

Изменить 2: Размышляя об этом дальше, я вижу, что вы хотите контролировать построение объекта. Вы не можете этого сделать. Другими словами, в Java вы не можете заставить исполнителя использовать или не использовать factory для создания своего объекта. Они всегда могут выставлять публичный конструктор. Я думаю, что ваша проблема в том, что вас не устраивают механизмы принудительного строительства. Если это понимание правильное, тогда может использоваться следующий шаблон.

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

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

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

Ответ 2

Просто подумайте: вы можете отделить логику преобразования от самих объектов, а затем у вас есть фиксированный набор трансформаторов, реализующий следующий интерфейс:

public interface Transformer<T>{ 

  public T fromText(String text); 

  public String toText(T obj); 
}

В ваших реальных классах данных может быть метод getTransformer(), который возвращает для них правильный трансформатор.

Ответ 3

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

public interface MyInterface{
    Method getConvertMethod();
}

теперь ваш код клиента может выполнять

yourInterface.getConvertMethod().invoke(objectToBeConverted);

Это чрезвычайно мощный, но очень плохой дизайн API

Шон

Ответ 4

Если вы работаете на Java 5 или выше, вы можете использовать тип перечисления - все это, по определению, одноточие. Таким образом, вы могли бы что-то вроде:

public enum MyEnumType {
    Type1,
    Type2,
    //....
    TypeN;

    //you can do whatever you want down here
    //including implementing interfaces that the enum conforms to.

}

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


Изменить: Если у вас нет доступа к перечислениям (1.4 или более ранним) или по какой-то другой причине они не работают для вас, я бы рекомендовал Вертикальный рисунок.

Ответ 5

Просто другая идея. Не применимо для всех случаев, но может помочь кому-то другому.

Вы можете использовать abstract class, как мост между вашим interface и вашим concrete classes. Абстрактные классы позволяют использовать статические методы, а также определения методов контрактов. Например, вы можете проверить API Collection, где Sun реализовало несколько абстрактных классов вместо кодировки грубой силы с нуля всех конкретных классов.

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

Ответ 6

Как сказал @aperkins, вы должны использовать перечисления.

Базовый класс enum, Enum, предоставляет метод valueOf, который преобразует строку в экземпляр.

enum MyEnum { A, B, C; }
// Here your toText
String strVal = MyEnum.A.getName();
// and here your fromText
MyEnum value = MyEnum.valueOf(MyEnum.class, strVal);

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

/** Enums can implement this to provide a method for resolving a string
  * to a proper enum name.
  */
public interface EnumHelp
{
    // Resolve s to a proper enum name that can be processed by Enum.valueOf
    String resolve(String s);
}

/** EnumParser provides methods for resolving symbolic names to enum instances.
  * If the enum in question implements EnumHelp, then the resolve method is
  * called to resolve the token to a proper enum name before calling valueOf.
  */
public class EnumParser
{
    public static <T extends Enum<T>> T fromText(Class<T> cl, String s) {
        if (EnumHelp.class.isAssignableFrom(cl)) {
            try {
                Method resolve = cl.getMethod("resolve", String.class);
                s = (String) resolve.invoke(null, s);
            }
            catch (NoSuchMethodException ex) {}
            catch (SecurityException ex) {}
            catch(IllegalAccessException ex) {}
            catch(IllegalArgumentException ex) {}
            catch(InvocationTargetException ex) {}
        }
        return T.valueOf(cl, s);
    }

    public <T extends Enum<T>> String toText(T value)
    {
        return value.name();
    }
}

Ответ 7

Кажется, вам нужно отделить factory от созданного объекта.

public interface TextTransformer<T> {
    public T fromText(String text);
    public String toText(T source);
}

Вы можете зарегистрировать классы TextTransformer, как вам нравится:

public class FooTextTransformer implements TextTransformer<Foo> {
    public Foo fromText(String text) { return ...; }
    public String toText(Foo source) { return ...; }
}

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

public class TextTransformers {
    public static final FooTextTransformer FOO = new FooTextTransformer();
    ...
    public static final BarTextTransformer BAR = new BarTextTransformer();
}

В вашем клиентском коде:

Foo foo = TextTransformers.FOO.fromText(...);
...
foo.setSomething(...);
...
String text = TextTransformers.FOO.toText(foo);

Это всего лишь один подход с моей головы.