1

Assume I have a simple interface for translating text (sample code in C#):

public interface ITranslationService
{
    string GetTranslation(string key, CultureInfo targetLanguage);
    // some other methods...
}

A first simple implementation of this interface already exists and simply goes to the database for every method call. Assuming a UI that is being translated at start up this results in one database call per control.

To improve this, I want to add the following behavior:

As soon as a request for one language comes in, fetch all translations from this language and cache them. All translation requests are served from the cache.

I thought about implementing this new behavior as a decorator, because all other methods of that interface implemented by the decorater would simple delegate to the decorated instance.
However, the implementation of GetTranslation wouldn't use GetTranslation of the decorated instance at all to get all translations of a certain language. It would fire its own query against the database.
This breaks the decorator pattern, because every functionality provided by the decorated instance is simply skipped. This becomes a real problem if there are other decorators involved.

My understanding is that a Decorator should be additive. In this case however, the decorator is replacing the behavior of the decorated instance.

I can't really think of a nice solution for this - how would you solve it? Everything is allowed, even a complete re-design of ITranslationService itself.

2
  • Is this a decorator? It seems like a repository to me. In my opinion the easiest solution would be a simple greedy cache (memory or otherwise) in the implementation of the translation service. When the first request comes through it checks the cache, if the language it not cached it fetches the whole thing, and then returns the single value from said cache.
    – Mike
    Commented Jun 25, 2013 at 16:18
  • @Mike: I agree, it's not a decorator. That's why I asked for a different design :-) Your comment is similar to JustinC's answer Commented Jun 25, 2013 at 16:19

2 Answers 2

1

Keep the ITranslationService, but instead of implementing calls directly against to the cache or the database, send to an object IRepository that uses a list of preferred IProviders which provide access to the underlying persistence mechanisms.

The first, or generally "preferred provider", is a CacheProvider. The second, is your primary LocalDatabaseTranslationProvider. Conceivable, there could be a third or more providers. The last provider in the list is least preferred, but a reliable fallback provider. For example one might take the form of a 3rd party translation service. It would be implemented as an object proxy service like RemotePartnerTranslationProvider.

Each provider will implement a common interface that allows the strategy to differ, so no matter which provider is chosen and no matter how each provides for reads and writes, the repository acts on each and receives from each in the same way. The repository is responsible for providing coordination and conflict resolution amongst the providers when necessary.

Among other concerns, you might look at an observable interface for each provider too, for when you need to propagate/push state/signal amongst all/most providers from the repository, for instance to properly close or dispose of them when cleaning up/shutting down.

2
  • As far as I understand your answer, each Provider is a separate Strategy. Is this correct? Commented Jun 25, 2013 at 13:39
  • 1
    Essentially yes. However a provider 'is a' special kind of bridge, and 'has a' unique strategy for x, y, z. The are a fundamentally a bridge because a database context is typically far different than a cache context vs a 3rd party remote service.
    – JustinC
    Commented Jun 25, 2013 at 16:28
1

The feasibility of a lot of the possible options depends on how closed the current implementation of the ITranslationService interface is.

  • If the current implementation is completely closed (final class, no modifications will be accepted), then your only real options are

    • Create a separate, independent implementation of the interface
    • use your current 'decorator' approach. This can be extended to call the GetTranslation method of the decorated object if the translation could not be found in the cache.
  • If the current implementation allows sub-classes, you could provide your cached version as a derived class

  • If the current implementation is still open for modification, you could change the GetTranslation implementation to use the Strategy pattern, where one strategy always goes to the database and the other uses caching.
2
  • The current implementation is open for modification as it has just been developed and the iteration is not yet finished, so it hasn't been deployed yet. Commented Jun 25, 2013 at 11:44
  • Thanks for the suggestion of Strategy. That sounds like a good idea. I will have to evaluate it in the light of other requirements I have. Commented Jun 25, 2013 at 11:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.