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

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

public interface TextTransformable<T>{

  public static T fromText(String text);

  public String toText();

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

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

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

Есть мысли?

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

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

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

Ответы [ 7 ]

7 голосов
/ 11 мая 2010

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

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

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

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

А затем используйте это так:

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

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

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

Редактировать: Я думаю, я не знаю, что вы считаете неуместным в отношении фабрики, но я думаю, что вы сосредоточены на вызывающих абонентах, так что вы чувствуете по этому поводу:

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

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

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

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

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

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

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

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

4 голосов
/ 11 мая 2010

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

public interface Transformer<T>{ 

  public T fromText(String text); 

  public String toText(T obj); 
}

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

2 голосов
/ 17 мая 2010

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

public interface MyInterface{
    Method getConvertMethod();
}

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

yourInterface.getConvertMethod().invoke(objectToBeConverted);

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

Sean

1 голос
/ 06 марта 2016

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

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

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

1 голос
/ 11 мая 2010

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

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

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

}

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


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

0 голосов
/ 11 мая 2010

Похоже, вам нужно отделить фабрику от созданного объекта.

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);

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

0 голосов
/ 11 мая 2010

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

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

enum MyEnum { A, B, C; }
// Here's your toText
String strVal = MyEnum.A.getName();
// and here's 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();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...