Alternativas a los métodos estáticos en las interfaces para hacer cumplir la coherencia


En Java, me gustaría poder definir interfaces de marcadores, que obligaron a las implementaciones a proporcionar métodos estáticos. Por ejemplo, para serialización/deserialización simple de texto me gustaría poder definir una interfaz que se viera algo como esto:

public interface TextTransformable<T>{

  public static T fromText(String text);

  public String toText();

Dado que las interfaces en Java no pueden contener métodos estáticos, sin embargo (como se indica en una serie de otros mensajes/hilos: aquí, aquí , y aquí este código no funciona.

Lo que estoy buscando sin embargo es un paradigma razonable para expresar la misma intención, a saber, métodos simétricos, uno de los cuales es estático, y aplicado por el compilador. En este momento, lo mejor que podemos encontrar es algún tipo de objeto de fábrica estático o fábrica genérica, ninguno de los cuales es realmente satisfactorio.

Nota: en nuestro caso nuestro caso de uso principal es que tenemos muchos, muchos tipos de "objeto-valor" - enums, u otros objetos que tienen un número limitado de valores, normalmente no llevan ningún estado más allá de su valor, y que parse / de-parse miles de veces por segundo, por lo que realmente se preocupan por reutilizar instancias (como Float, Integer, etc.).) y su impacto en el consumo de memoria/g.c.

¿Algún pensamiento?

EDIT1: Para aclarar alguna confusión - tenemos muchos objetos diferentes que encajan con este patrón-realmente estamos tratando de llegar a algo elegante para las personas que llaman con 2 semántica:

  • Interfaces como contratos - unidad de acceso (por ejemplo, TextTransformable como un capacidad)
  • Exigir la implementación por subclases/implementaciones (por ejemplo, obligarlos a implementar su propia transformación

En términos de nuestros pensamientos para Flyweight, Factories, ambas son opciones que hemos considerado, realmente estamos tratando de ver si podemos encontrar algo más elegante que confiar en JavaDoc diciendo "implementar una Fábrica y delegar llamadas a ella, o exponerla en XXX ubicación por convención"

Author: Community, 2010-05-11

7 answers

Esto es realmente apropiado para el optimización del rendimiento. Eso es básicamente lo que estás tratando de lograr con la estática. En términos de cómo servir el objeto de peso mosca para que no cree miles de ellos, aquí hay algunas ideas.

Una es la fábrica, que usted afirma que pensó y rechazó, aunque no declaró por qué (por lo que cualquier otra idea puede sufrir del mismo problema), por lo que no entraré en ella.

Otra es que el tipo de valor tenga un método que puede servir a su convertidor. Algo como esto:

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

Y luego úsalo así:

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

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

Ahora eso no impone que todos los ValueType proporcionen sus convertidores a través del mismo mecanismo, para eso creo que necesitas una fábrica de algún tipo.

Editar: Supongo que no se lo que encuentras poco elegante sobre una fábrica, pero creo que estás centrado en las personas que llaman, así que ¿cómo se siente esto para ti:

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

Lo anterior se puede hacer con una importación estática de la fábrica y un método que tiene una firma así:

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

El código para encontrar el transformador correcto no es necesariamente el más bonito, pero desde la perspectiva de las personas que llaman todo está muy bien presentado.

Edit 2: Pensando más en esto, lo que veo es que quieres controlar la construcción de objetos. No puedes hacer eso. En otras palabras, en Java no se puede forzar a un implementador a usar o no usar una fábrica para crear su objeto. Siempre pueden exponer a un constructor público. Creo que tu problema es que no están contentos con los mecanismos para hacer cumplir la construcción. Si ese entendimiento es correcto, entonces el siguiente patrón puede ser útil.

Crea un objeto con solo constructores privados que envuelven su tipo de valor. El objeto puede tener un parámetro de tipo genérico para saber qué tipo de valor envuelve. Este objeto se crea una instancia con un método de fábrica estático que toma una interfaz de fábrica para crear el objeto de valor "real". Todo código de framework que usa el objeto solo toma este objeto como un parámetro. No acepta el tipo de valor directamente, y ese objeto no puede ser instanciado sin una fábrica para el tipo de valor.

El problema con este enfoque es que es bastante restrictivo. Solo hay una forma de crear objetos (los soportados por la interfaz de fábrica) y hay una capacidad limitada para usar los objetos de valor, ya que el código que procesa estos elementos de texto tiene una interacción limitada solo a través de este objeto.

Supongo que dicen que no hay un problema de software eso no se puede resolver a través de una capa adicional de indirección, pero esto puede ser un puente demasiado lejos. Al menos es materia de reflexión.

 6
Author: Yishai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-10 23:13:49

Solo una idea a considerar: Puede separar la lógica de transformación de los propios objetos, y luego tiene un conjunto fijo de transformadores, implementando la siguiente interfaz:

public interface Transformer<T>{ 

  public T fromText(String text); 

  public String toText(T obj); 
}

Sus clases de datos reales pueden tener un método getTransformer() que devuelve el transformador correcto para ellas.

 4
Author: Eyal Schneider,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-10 20:20:54

Un enfoque totalmente diferente (y un truco feo, para el caso) es dejar que la interfaz tenga un método que devuelva un método.

public interface MyInterface{
    Method getConvertMethod();
}

Ahora su código de cliente puede hacer

yourInterface.getConvertMethod().invoke(objectToBeConverted);

Esto es extremadamente poderoso, pero muy mal diseño de API

Sean

 2
Author: Sean Patrick Floyd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-17 19:59:35

Si se está ejecutando en Java 5 o superior, puede usar el tipo enum - todos ellos son, por definición, singletons. Así que podrías algo como:

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

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

}

De esta manera el problema de memoria desaparece, y puede tener instancias individuales de comportamiento implementadas.


Edit: Si no tienes acceso a enums (1.4 o anteriores) o, por alguna otra razón, no funcionan para ti, recomendaría una implementación Flyweight pattern.

 1
Author: aperkins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-10 20:14:50

Solo una idea diferente. No aplicable para todos los casos, pero tal vez ayudar a alguien más.

Puedes usar un abstract class, como puente entre tu interface y tu concrete classes. Clases abstractas permite métodos estáticos, así como definiciones de métodos de contrato. Como ejemplo, puede consultar la API de colección, donde Sun implementó varias clases abstractas, en lugar de codificar por fuerza bruta desde cero todas las clases concretas.

En algunos casos, solo puede reemplazar las interfaces por clases abstractas.

 1
Author: Alex Byrth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-06 20:02:00

Parece que necesita separar la fábrica del objeto creado.

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

Puedes registrar las clases TextTransformer como quieras:

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

Si desea que FooTextTransformer sea un singleton, puede usar un contenedor como Spring para hacer cumplir eso. Google ha iniciado un proyecto para eliminar todos los singletons forzados manualmente de su base de código, pero si desea hacer un singleton anticuado, podría usar una clase de utilidad:

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

En su cliente código:

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

Este es solo un enfoque de la parte superior de mi cabeza.

 0
Author: les2,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-10 20:26:33

Como dijo @aperkins, deberías usar enumeraciones.

La clase base enum, Enum, proporciona un método valueOf que convertirá una cadena en una instancia.

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

Actualización: Para los que son enumeraciones, esto podría hacer lo que necesita. Utiliza reflexión, por lo que solo necesita implementar EnumHelper en enumeraciones donde tiene que lidiar con valores heredados.

/** 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();
    }
}
 0
Author: Devon_C_Miller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-10 23:01:12