2011-01-10 19 views
5

Estoy trabajando en una aplicación web que necesito localizar e internacionalizar. Se me ocurrió que podía hacer esto usando un marco de inyección de dependencia. Digamos que declarar una interfaz ILocalResources (usando C# en este ejemplo, pero eso no es realmente importante):Localización utilizando un marco DI: ¿una buena idea?

interface ILocalResources { 
    public string OkString { get; } 
    public string CancelString { get; } 
    public string WrongPasswordString { get; } 
    ... 
} 

y crear implementaciones de esta interfaz, uno para cada idioma que necesito para apoyar. Luego configuraría mi marco DI para instanciar la implementación adecuada, ya sea estática o dinámicamente (por ejemplo, en función del idioma preferido de los navegadores solicitantes).

¿Hay alguna razón por la que no deba utilizar un marco DI para este tipo de cosas? La única objeción que pude encontrar es que podría ser un poco exagerado, pero si estoy usando un marco DI en mi aplicación web de todos modos, ¿podría usarlo también para la internacionalización?

Respuesta

2

Si no puede usar un marco de recursos existente (como el integrado en ASP.Net) y tendría que crear uno propio, asumiré que en algún momento deberá exponer los servicios que proporcionan recursos localizados.

marcos DI se utilizan para manejar la creación de instancias del servicio. Su marco de localización expondrá los servicios que proporcionan localización. ¿Por qué no debería ese servicio ser atendido por el marco?

No utilizar DI para este propósito es como decir: "Estoy compilando una aplicación CRM pero no puedo usar DI porque DI no está diseñado para la gestión de relaciones con los clientes".

Así que sí, si ya está usando DI en el resto de su aplicación, IMO sería incorrecto no usarlo para los servicios que manejan la localización.

2

La única desventaja que puedo ver es que para cualquier actualización de "recursos", debería volver a compilar el conjunto que contiene los recursos. Y dependiendo de su proyecto, esta desventaja puede ser un buen consejo para usar solo un marco DI para resolver un ResourceService de algún tipo, en lugar de los valores en sí.

6

Un marco DI está diseñado para hacer la inyección de dependencia y la localización podría ser solo uno de sus servicios, por lo que en ese caso no hay ninguna razón para no usar para usar un marco de trabajo IMO. Tal vez deberíamos comenzar a discutir la interfaz ILocalResources proporcionada. Si bien soy partidario de tener soporte de tiempo de compilación, no estoy seguro de que la interfaz suministrada lo ayude, ya que esa interfaz probablemente sea del tipo de su sistema que más cambiará. Y con esa interfaz el tipo/tipos que lo implementan. Tal vez deberías ir con un diseño diferente.

Cuando miramos la mayoría de los marcos/proveedores/fábricas de localización (o lo que sea), todos están basados ​​en cadenas. Debido a esto, pensar en el siguiente diseño:

public interface ILocalResources 
{ 
    string GetStringResource(string key); 
    string GetStringResource(string key, CultureInfo culture); 
} 

Esto permitirá agregar claves y culturas para el almacén de datos mensaje subyacente, sin cambiar la interfaz. Lo malo es, por supuesto, que nunca deberías cambiar una tecla, porque eso probablemente sea un infierno.

Otro enfoque podría ser un tipo de base abstracta:

public abstract class LocalResources 
{ 
    public string OkMessage { get { return this.GetString("OK"); } } 
    public string CancelMessage { get { return this.GetString("Cancel"); } } 
    ... 

    protected abstract string GetStringResource(string key, 
     CultureInfo culture); 

    private string GetString(string key) 
    { 
     Culture culture = CultureInfo.CurrentCulture; 

     string resource = GetStringResource(key, culture); 

     // When the resource is not found, fall back to the neutral culture. 
     while (resource == null && culture != CultureInfo.InvariantCulture) 
     { 
      culture = culture.Parent; 
      resource = this.GetStringResource(key, culture); 
     } 

     if (resource == null) throw new KeyNotFoundException(key); 

     return resource; 
    } 
} 

y la aplicación de este tipo podría tener este aspecto:

public sealed class SqlLocalResources : LocalResources 
{ 
    protected override string GetStringResource(string key, 
     CultureInfo culture) 
    { 
     using (var db = new LocalResourcesContext()) 
     { 
      return (
       from resource in db.StringResources 
       where resource.Culture == culture.Name 
       where resource.Key == key 
       select resource.Value).FirstOrDefault(); 
     } 
    } 
} 

Este enfoque tiene lo mejor de ambos mundos, ya que las claves ganaron' Estar disperso por la aplicación y agregar nuevas propiedades solo tiene que hacerse en un solo lugar. El uso de la biblioteca de favorite DI, se puede registrar una aplicación como esta:

container.RegisterSingleton<LocalResources>(new SqlLocalResources()); 

Y puesto que el tipo LocalResources tiene exactamente un método abstracto que hace todo el trabajo, es fácil crear un decorador que se suma el almacenamiento en caché para evitar que solicita los mismos datos de la base de datos:

public sealed class CachedLocalResources : LocalResources 
{ 
    private readonly Dictionary<CultureInfo, Dictionary<string, string>> cache = 
     new Dictionary<CultureInfo, Dictionary<string, string>>(); 
    private readonly LocalResources decoratee; 

    public CachedLocalResources(LocalResources decoratee) { this.decoratee = decoratee; } 

    protected override string GetStringResource(string key, CultureInfo culture) { 
     lock (this.cache) { 
      string res; 
      var cultureCache = this.GetCultureCache(culture); 
      if (!cultureCache.TryGetValue(key, out res)) { 
       cultureCache[key] = res= this.decoratee.GetStringResource(key, culture); 
      }     
      return res; 
     } 
    } 

    private Dictionary<string, string> GetCultureCache(CultureInfo culture) { 
     Dictionary<string, string> cultureCache; 
     if (!this.cache.TryGetValue(culture, out cultureCache)) { 
      this.cache[culture] = cultureCache = new Dictionary<string, string>(); 
     } 
     return cultureCache; 
    } 
} 

puede aplicar el decorador de la siguiente manera:

container.RegisterSingleton<LocalResources>(
    new CachedLocalResources(new SqlLocalResources())); 
no hacer

e que este decorador guarda en caché los recursos de cadena de forma indefinida, lo que puede causar pérdidas de memoria, por lo que desea ajustar las cadenas en instancias WeakReference o tener algún tipo de tiempo de espera de caducidad. Pero la idea es que puede aplicar el almacenamiento en caché sin tener que cambiar ninguna implementación existente.

Espero que esto ayude.

+0

Alternativamente, también podría implementar métodos de extensión para la interfaz 'ILocalResources', pero ese inconveniente es que tendrá métodos en lugar de propiedades. Pero eso probablemente no sería tan malo. – Steven

Cuestiones relacionadas