2010-05-10 6 views
27

En Java, me gustaría poder definir interfaces de marcador, que forzaron las implementaciones para proporcionar métodos estáticos. Por ejemplo, para el texto simple-serialización/deserialización me gustaría ser capaz de definir una interfaz que parecía algo como esto:Alternativas a los métodos estáticos en las interfaces para imponer la coherencia

public interface TextTransformable<T>{ 

    public static T fromText(String text); 

    public String toText(); 

Desde interfaces en Java no pueden contener métodos estáticos, aunque (como se señaló en una número de otros mensajes/temas: here, here y here este código no funciona

lo que estoy buscando es sin embargo un poco de paradigma razonable para expresar la misma intención, es decir, métodos simétricos, uno de los cuales es estático. e implementado 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 de genéricos, ninguno de los cuales es realmente satisfac conservador.

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

¿Alguna idea?

EDIT1: Para aclarar cierta confusión - tenemos muchos, diferentes objetos se ajusten a este patrón -en realidad que estamos tratando de llegar a algo elegante para las personas que llaman con 2 semántica:

  • Interfaces como contratos - la unidad de acceso (por ejemplo TextTransformable como una capacidad)
  • que requieran una aplicación por las subclases/implementaciones (por ejemplo, los obligan a poner en práctica su propia transformación

En ter Muchos de nuestros pensamientos sobre Flyweight, Factories: ambas opciones hemos considerado, realmente estamos tratando de ver si podemos encontrar algo más elegante que confiar en JavaDoc diciendo "implementar una fábrica y delegarle llamadas, o exponerlo en la ubicación XXX por convención "

Respuesta

6

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

Una es la fábrica, que dice que pensó y rechazó, aunque no indicó por qué (por lo que otras ideas pueden sufrir el mismo problema), así que no entraré en ello.

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

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

y luego usarlo como esto:

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

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

Ahora que no hace cumplir que todos de ValueType proporcionar a sus convertidores a través del mismo mecanismo, por que creo que necesita una fábrica De algún tipo.

Editar: supongo que no sé lo que se encuentra poco elegante sobre una fábrica, pero creo que se centran en las personas que llaman, así que ¿cómo ¿Se siente a usted:

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 de este modo:

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

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

Editar 2: Pensando en esto más, lo que veo es que desea controlar la construcción de objetos. Realmente no puedes hacer eso.En otras palabras, en Java no puede obligar a un implementador a usar o no usar una fábrica para crear su objeto. Siempre pueden exponer un constructor público. Creo que su problema es que no está satisfecho con los mecanismos para hacer cumplir la construcción. Si esa comprensión es correcta, entonces el siguiente patrón puede ser útil.

Crea un objeto con solo constructores privados que envuelve 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 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 el código de marco que usa el objeto solo toma este objeto como un parámetro. No acepta el tipo de valor directamente, y ese objeto no se puede crear una instancia 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 (aquellos soportados por la interfaz de fábrica) y existe 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 que no se pueda resolver a través de una capa adicional de indirección, pero esto puede ser un puente demasiado lejos. Al menos es algo para pensar.

+0

Sí, voy a editar arriba con la nota que hemos hablado sobre flyweight, aunque creo que estamos enfocados en la unidad de acceso (por ejemplo, como una interfaz de marcador y aplicación de contrato en la subclase) - ninguno de los cuales una fábrica o transformador estático parece que nos reciben) La razón principal por la que creo que no estamos contentos con la idea de fábrica es se siente poco elegante, aunque admitimos que algunas veces soportamos la inelegancia cuando no podemos encontrar algo elegante ... – jayshao

+0

Sí, restringir irónicamente es lo que queremos;) Idealmente más como una mezcla, pero sin tener que usar un colaborador Objeto: aunque estoy de acuerdo, su resumen es similar a nuestro pensamiento actual y probablemente sea tan bueno como podamos presentarlo por el momento. – jayshao

1

Si está ejecutando en Java 5 o superior, puede usar el tipo de enumeración; todos ellos son, por definición, singletons. Por lo tanto, podría hacer 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 forma, el problema de memoria desaparece y puede implementar instancias únicas de comportamiento.


Editar: Si usted no tiene acceso a las enumeraciones (1.4 o anterior) o, por alguna otra razón, no funcionan para usted, recomendaría una implementación Flyweight pattern.

+0

Puede ver a continuación por qué estamos tratando de llegar a una convención que vaya más allá de las capacidades incorporadas en el soporte enum en Java 5+ – jayshao

+0

@jayshao Tuvimos un problema similar con el valor de, y lo solucionamos. Pero creo que un patrón flyweight, con un tipo de registro singleton, resolvería una gran parte de ese problema para usted, como lo explicó @Yishai – aperkins

+0

@aperkins en cualquier código de muestra. – jayshao

4

Sólo una idea a considerar: Se puede separar la lógica de transformación de los objetos mismos, y entonces usted tiene un conjunto fijo de transformadores, la aplicación de la siguiente interfaz:

public interface Transformer<T>{ 

    public T fromText(String text); 

    public String toText(T obj); 
} 

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

+1

madre bleeper! ¡me ganaste!:) – les2

+0

esperamos mantener el código en el mismo lugar: estos son básicamente objetos de valor, idealmente esto es una gran parte de lo que hacen – jayshao

0

Como @aperkins dijo, debes 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 la reflexión, por lo que solo necesita implementar EnumHelper en las enumeraciones en las que debe lidiar con los 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

Muchos de los objetos existentes ya son Enume, aunque desafortunadamente ya que Enum no permite anular valueOf() nuestra capacidad para producir buenas representaciones de texto (valores predeterminados, null-safe, versiones anteriores de soporte, codificaciones, etc.) es limitada, de ahí nuestro deseo de implementar métodos separados, pero con una semántica similar, más poder en nuestra capacidad de personalizar . – jayshao

+0

Nada le impide agregar un método de búsqueda personalizado a su enumeración si no le gusta el comportamiento de valueOf(). A menudo lo hará si necesita un método de búsqueda que devuelva nulo o un valor predeterminado si no se puede encontrar nada. Con valueOf() solo puedes hacer eso con try/catch, lo que hace que el manejo de excepciones esté a cargo del flujo de control –

0

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

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

Puede registrar clases TextTransformer como usted quiera:

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

Si desea FooTextTransformer sea un producto único, puede utilizar un recipiente como la primavera que lo imponen. Google ha puesto en marcha un proyecto para eliminar todos los embarazos únicos forzadas manualmente desde su base de código, pero si usted quiere hacer una anticuada Singleton, se podría utilizar una clase de utilidad:

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

En su código de cliente:

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

Esto es solo un enfoque fuera de mi cabeza.

2

Un enfoque totalmente diferente (y un hack 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 muy potente, pero muy mal diseño API

Sean

+0

eso es realmente interesante - en JS lo haría en un abrir y cerrar de ojos, o en algún otro idioma donde Functors eran comunes, en Java se siente un poco extraño – jayshao

+0

exactamente, ya que los métodos no son "ciudadanos de primera clase" en Java. Sin embargo, esto puede cambiar pronto en JDK1.7. Veremos cuánto cambia el estilo de programación ... –

1

Sólo una idea diferente. No aplicable para todos los casos, pero tal vez ayudar a otra persona.

Puede usar un abstract class, como puente entre su interface y su concrete classes. Clases abstractas permite métodos estáticos y también contratan definiciones de métodos. Como ejemplo, puede consultar Collection API, donde Sun implementó varias clases abstractas, en lugar de la codificación de fuerza bruta desde cero todas las clases concretas.

En algunos casos, puede reemplazar interfaces por clases abstractas.

Cuestiones relacionadas