Локализация с использованием DI-фреймворка - хорошая идея? - PullRequest
5 голосов
/ 10 января 2011

Я работаю над веб-приложением, которое мне нужно локализовать и интернационализировать. Мне пришло в голову, что я могу сделать это с помощью инфраструктуры внедрения зависимостей. Допустим, я объявляю интерфейс ILocalResources (используя C # для этого примера, но это не очень важно):

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

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

Есть ли причина, по которой я не должен использовать DI-фреймворк для такого рода вещей? Единственное возражение, которое я могу найти, это то, что это может быть немного излишним, но если я все равно использую инфраструктуру DI в своем веб-приложении, я мог бы также использовать ее для интернационализации?

Ответы [ 3 ]

6 голосов
/ 10 января 2011

Структура DI создана для внедрения зависимостей, и локализация может быть просто одной из ваших услуг, поэтому в этом случае нет никакой причины , а не , использовать IMO структуры DI.Возможно, нам следует начать обсуждение предоставленного интерфейса ILocalResources.Хотя я поддерживаю поддержку времени компиляции, я не уверен, что предоставленный интерфейс поможет вам, потому что этот интерфейс, вероятно, будет типом в вашей системе, который изменится наиболее.И с этим интерфейсом тип / типы, которые его реализуют.Возможно, вам следует использовать другой дизайн.

Когда мы смотрим на большинство структур / провайдеров / фабрик локализации (или чего-либо еще), они все основаны на строках.Из-за этого подумайте о следующем дизайне:

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

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

Другим подходом может быть абстрактный базовый тип:

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

И реализация этого типа можетвыглядит следующим образом:

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

Этот подход использует лучшее из обоих миров, потому что ключи не будут разбросаны по приложению, и добавление новых свойств просто должно быть сделано в одном месте.Используя вашу любимую библиотеку DI, вы можете зарегистрировать реализацию, подобную этой:

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

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

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

Вы можете применить декоратор следующим образом:

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

Обратите внимание, что этот декоратор кэшируетстроковые ресурсы на неопределенный срок, что может привести к утечкам памяти, поэтому вы хотите обернуть строки в WeakReference экземплярах или иметь какое-то время ожидания истечения.Но идея в том, что вы можете применять кеширование, не меняя существующую реализацию.

Надеюсь, это поможет.

2 голосов
/ 10 января 2011

Единственный недостаток, который я вижу, состоит в том, что для любого обновления «ресурсов» вам придется перекомпилировать сборку, содержащую ресурсы. И в зависимости от вашего проекта этот недостаток может быть хорошим советом, чтобы использовать только DI-фреймворк для разрешения какого-либо ResourceService, а не сами значения.

2 голосов
/ 10 января 2011

Если вы не можете использовать существующую структуру ресурсов (например, встроенную в ASP.Net) и должны будете создать свою собственную, я предполагаю, что в какой-то момент вам потребуется предоставить службы, предоставляющие локализованные ресурсы.

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

Не использовать DI для своих целей здесь все равно, что сказать: «Я создаю приложение CRM, но не могу использовать DI, потому что DI не создан для управления взаимоотношениями с клиентами».

Так что да, если вы уже используете DI в остальной части своего приложения, IMO было бы неправильно не использовать его для служб, обрабатывающих локализацию.

...