lundi 5 décembre 2022

What design pattern should be used for multiple providers?

I'm trying to write a localization system that supports different providers. (File, Database and embedded file) These providers can be used at the same time. (e.g. both embedded files and databases records)

Everything is fine until it comes to the embedded files; embedded files in .NET are read-only. This means that the localization must be separated for both read-only and writable actions.

I write some abstract and concrete entities, but I got stuck.

ILocalizer is for read-only providers.

public interface ILocalizer
{
    LanguageInfo GetLanguage(string cultureName);
    IReadOnlyList<LanguageInfo> GetLanguages();

    string GetString(string name, string cultureName);
    IReadOnlyList<LanguageLocaleInfo> GetLocales(string cultureName);
}

public class Localizer : ILocalizer
{
    private readonly ILocalizerProvider _provider;

    public Localizer(ILocalizerProvider provider)
    {
        _provider = provider;
    }

    public LanguageInfo GetLanguage(string cultureName)
    {
        return _provider.GetLanguage(cultureName);
    }

    public IReadOnlyList<LanguageInfo> GetLanguages()
    {
        return _provider.GetLanguages();
    }

    public string GetString(string name, string cultureName)
    {
        return _provider.GetString(name, cultureName);
    }
}

With ILocalizerManager changes can be reflected and the data can be read.

public interface ILocalizerManager : ILocalizer
{
    void AddLanguage(LanguageInfo languageInfo);
    void RemoveLanguage(LanguageInfo languageInfo);
    void UpdateLanguage(LanguageInfo languageInfo);

    void AddLocale(LanguageLocaleInfo localeInfo);
    void UpdateLocale(LanguageLocaleInfo localeInfo);
}

public class LocalizerManager : Localizer, ILocalizerManager
{
    private readonly ILocalizerManagerProvider _localizerProvider;

    public LocalizerManager(ILocalizerManagerProvider localizationProvider, ILocalizerProvider localizerProvider) : base(localizerProvider)
    {
        _localizerProvider = localizationProvider;
    }

    public void AddLanguage(LanguageInfo languageInfo)
    {
        _localizerProvider.AddLanguage(languageInfo);
    }

    public void AddLocale(LanguageLocaleInfo localeInfo)
    {
        _localizerProvider.AddLocale(localeInfo);
    }

    public void RemoveLanguage(LanguageInfo languageInfo)
    {
        _localizerProvider.RemoveLanguage(languageInfo);
    }

    public void UpdateLanguage(LanguageInfo languageInfo)
    {
        _localizerProvider.UpdateLanguage(languageInfo);
    }

    public void UpdateLocale(LanguageLocaleInfo localeInfo)
    {
        _localizerProvider.UpdateLocale(localeInfo);
    }
}

These are the provider interfaces.

public interface ILocalizerProvider
{
    LanguageInfo GetLanguage(string cultureName);
    IReadOnlyList<LanguageInfo> GetLanguages();

    string GetString(string name, string cultureName);
}

public interface ILocalizerManagerProvider : ILocalizerProvider
{
    void AddLanguage(LanguageInfo languageInfo);
    void RemoveLanguage(LanguageInfo languageInfo);
    void UpdateLanguage(LanguageInfo languageInfo);

    void AddLocale(LanguageLocaleInfo localeInfo);
    void UpdateLocale(LanguageLocaleInfo localeInfo);
}

These are concrete providers.

public class DatabaseLocalizationProvider : ILocalizerManagerProvider
{
    private readonly IRepository<Language> _languageRepository;
    private readonly IRepository<LanguageLocale> _localeRepository;
    private readonly IUnitOfWork _unitOfWork;

    public DatabaseLocalizationProvider(IRepository<Language> languageRepository, IRepository<LanguageLocale> localeRepository, IUnitOfWork unitOfWork)
    {
        _languageRepository = languageRepository;
        _localeRepository = localeRepository;
        _unitOfWork = unitOfWork;
    }

    public void AddLanguage(LanguageInfo languageInfo)
    {
        _languageRepository.Add(languageInfo.ToEntity());
        _unitOfWork.Commit();
    }

    public void AddLocale(LanguageLocaleInfo localeInfo)
    {
        _localeRepository.Add(localeInfo.ToEntity());
        _unitOfWork.Commit();
    }

    public LanguageInfo GetLanguage(string cultureName)
    {
        var language = _languageRepository.Get(g => g.Name == cultureName);
        if (language is null) throw new ArgumentNullException(cultureName);
        return language.ToModel();
    }

    public IReadOnlyList<LanguageInfo> GetLanguages()
    {
        return _languageRepository.GetWhere(w => true).Select(s => s.ToModel()).ToImmutableList();
    }

    public string GetString(string name, string cultureName)
    {
        var locale = _localeRepository.Get(g => g.Culture == cultureName && g.Name == name);

        return locale?.Value ?? default!;
    }

    public void RemoveLanguage(LanguageInfo languageInfo)
    {
        var language = _languageRepository.Get(g => g.Name == languageInfo.Name);
        _languageRepository.Remove(language);
        _unitOfWork.Commit();
    }

    public void UpdateLanguage(LanguageInfo languageInfo)
    {
        var language = _languageRepository.Get(g => g.Name == languageInfo.Name);

        language.Name = languageInfo.Name;
        language.DisplayName = languageInfo.DisplayName;
        language.Icon = languageInfo.Icon;

        _languageRepository.Update(language);
        _unitOfWork.Commit();
    }

    public void UpdateLocale(LanguageLocaleInfo localeInfo)
    {
        var locale = _localeRepository.Get(g => g.Culture == localeInfo.CultureInfo.Name && g.Name == localeInfo.Name);

        locale.Value = localeInfo.Value;

        _localeRepository.Update(locale);
        _unitOfWork.Commit();
    }
}

public class ReadOnlyFileLocalizationProvider : ILocalizerProvider
{
    public LanguageInfo GetLanguage(string cultureName) { }

    public IReadOnlyList<LanguageInfo> GetLanguages() { }

    public string GetString(string name, string cultureName) { }
}

public class FileLocalizationProvider : ReadOnlyFileLocalizationProvider, ILocalizerManagerProvider
{
    public void AddLanguage(LanguageInfo languageInfo) { }

    public void AddLocale(LanguageLocaleInfo localeInfo) { }

    public void RemoveLanguage(LanguageInfo languageInfo) { }

    public void UpdateLanguage(LanguageInfo languageInfo) { }

    public void UpdateLocale(LanguageLocaleInfo localeInfo) { }
}

Reading and writing to files is easy, this is why method bodies are empty.

This is what I've done so far, I can't connect them all. I have to use different providers at the same time.

How can I achieve this?

Tried to check out different design patterns, and browsed related stack-overflow questions.

Aucun commentaire:

Enregistrer un commentaire