#1 2020-02-13 12:18:28

sakura
Member
From: Germany
Registered: 2018-02-21
Posts: 239
Website

Multiple, data dependent, implementations of an interface

Hi,

is there a designed way, to implement an interface multiple times and have the resolver return an implementation based upon some information.

Simple sample:

type
  ITaxCalcService = interface
    ['{9B1FA8CB-9552-4CE7-B1C7-8D11D72291BC}']
    function CalcTaxes(const aNetto: Currency; out aTaxes: Currency; out aTaxPerc: Double): TCQRSResult;
  end;

  TTaxCalcServiceDE = class(TDDDRepositoryRestCommand, ITaxCalcService)
  protected
    function CalcTaxes(const aNetto: Currency; out aTaxes: Currency; out aTaxPerc: Double): TCQRSResult;
  end;

  TTaxCalcServiceAT = class(TDDDRepositoryRestCommand, ITaxCalcService)
  protected
    function CalcTaxes(const aNetto: Currency; out aTaxes: Currency; out aTaxPerc: Double): TCQRSResult;
  end;

Perfect would be Rest.Services.Resolve(ITaxCalcService, Srv, 'DE') - but this, I know, does not exist.

I could now create Specific interfaces like

type
  ITaxCalcService = interface
    ['{9B1FA8CB-9552-4CE7-B1C7-8D11D72291BC}']
    function CalcTaxes(const aNetto: Currency; out aTaxes: Currency; out aTaxPerc: Double): TCQRSResult;
  end;

  ITaxCalcServiceDE = interface(ITaxCalcService)
    ['{08C30244-CD53-42B0-9CB6-DEB043719CAE}']
  end;

  ITaxCalcServiceAT = interface(ITaxCalcService)
    ['{7D0CDA1E-C44B-448B-9E28-1364994D6054}']
  end;

  TTaxCalcServiceDE = class(TDDDRepositoryRestCommand, ITaxCalcServiceDE, ITaxCalcService)
  protected
    function CalcTaxes(const aNetto: Currency; out aTaxes: Currency; out aTaxPerc: Double): TCQRSResult;
  end;

  TTaxCalcServiceAT = class(TDDDRepositoryRestCommand, ITaxCalcServiceAT, ITaxCalcService)
  protected
    function CalcTaxes(const aNetto: Currency; out aTaxes: Currency; out aTaxPerc: Double): TCQRSResult;
  end;

and have an [GoF] Abstract Factory decide upon the country code which implementation to load. No big problem, however, what would you do? Maybe I am missing something obvious.

I simply want to ensure not to have to create some if-then-else-if... all the time ;-)

Kind regards,
Daniel

Offline

#2 2020-02-17 07:50:43

sakura
Member
From: Germany
Registered: 2018-02-21
Posts: 239
Website

Re: Multiple, data dependent, implementations of an interface

Any hints, anyone?

Offline

#3 2020-02-17 09:18:40

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,673
Website

Re: Multiple, data dependent, implementations of an interface

Your interfaces are clearly breaking the Liskov substitution principle: the same name is breaking the contract, since it would change the currency.

You just need to add state to your implementation class.
So switch from a sicShared to a sicClient kind of service, then first call a "SetCountry" method and use the other methods as you expect.

Offline

#4 2020-02-17 09:44:20

sakura
Member
From: Germany
Registered: 2018-02-21
Posts: 239
Website

Re: Multiple, data dependent, implementations of an interface

ab wrote:

Your interfaces are clearly breaking the Liskov substitution principle: the same name is breaking the contract, since it would change the currency.

I just used that as a simplified sample. The real world problem we have is way more complex and as we just have to "run" after federal changes every quarter of the year, and have to be backwards compatible with older requirements for at least a year, I was looking for something easier.

What we really need, are changed implementations, as the services of the other parties change every so often, however, I would like to keep the client unaware of that. The "tax sample" was really a totally over-simplified sample.
The client shall simply say, I need to generate the tally for a certain period and submit it. Depending on the period, we have to implement totally different algorithms. And this is only one of many areas that change quarterly, therefore an abstract factory, that is returning the correct implementation would be best.  Honestly, I can't see how that would break the Liskov substitution principle, quite contrary to that. It is using it. I just don't want the client t decide which derived implementations of each service to use, but let the server decide which child class would be the correct one.

Offline

#5 2020-02-17 10:45:51

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,673
Website

Re: Multiple, data dependent, implementations of an interface

LSP's contract is not about the parameters signature, but about the computation involved.
LSP induces that whatever the class implementing an interface is, it wouldn't change the result.
Whereas in your sample, the result changes, because the the context is not the same.

Of course, LSP is relative. I was speaking in "pure LSP" terms. In "pure LSP", if the service call is local or remote, it should always have the exact same behavior.
In your sample, if you abstract from the context (i.e. country), then your interface indeed follows LSP.

So for your case, I would define the service as sicClientDriven or sicPerUser, then create a small wrapper implementation class redirecting to the actual implementation class depending on the Country.
The tricky part is how to induce the country from which client is connected.
Perhaps you can't use sicClientDriven on the client side (i.e. can't add the session id to the input), or don't use mORMot authentication (so sicPerUser won't work)...
What you can do, e.g. from a regular JS client, is to set a cookie then use TServiceRunningContext (either from ServiceContext threadvar or inheriting from TInjectableObjectRest).

Perhaps I could add automatic cookie use for sicClientDriven instead of / in addition to the current session id in the input JSON.

Offline

Board footer

Powered by FluxBB