I've a situation where I need to call a third party service to fetch some information. Those service could be different for different clients. I've a authenticate function in my Interface as follows.
interface IServiceProvider {
bool Authenticate(string username, string password);
}
class ABCServiceProvider : IserviceProvider
{
bool Authenticate(string username, string password) { // implementation}
}
class EFGServiceProvider : IserviceProvider
{
bool Authenticate(string username, string password) { // implementation}
}
and so on... now I've came across a service provider (let's say XYZServiceProvider) that needs some additional information (agentid) for authentication. something like this...
class XYZServiceProvider
{
bool Authenticate(string username, string password, int agentid) { // implementation}
}
Now if I provide another function for Authenticate in my interface with 3 parameters, and throw not implemented exception in all the classes except for XYZServiceProvider, wouldn't it violate Interface segregation principle? I've similar situation in some of my other part of the code aswell. Can anyone please tell me whats the best way to implement this type of scenrio? I would be really very thankful.
The best way to solve this would probably be to require agentId in the interface, and to simply ignore it in the cases of ABC and DEF where they don't need it. That way, the consuming class still wouldn't know the difference.
Actually it's the Liskov Substitution Principle that is most important if the ABC, DEF and XYZ providers are to be used interchangeably; "Given a class A that is depended upon by class X, X should be able to use a class B derived from A without knowing the difference".
The Interface Segregation Principle basically says that an interface should not contain members that any of its consumers do not need, because if the definition of those members were to change, classes that don't even use that method would have to be recompiled because the interface they depended on has changed. While this is relevant (you do have to recompile all consumers of IServiceProvider if you add an overload), you will have to do that anyway if you change Authenticate()'s signature, and of more pressing concern from a maintenance standpoint is that if you added an overload of Authenticate(), your consumers now have to know which overload they need to use. That requires your consuming classes to know the difference between implementations of a common interface, violating LSP. It's never a problem providing more information than a particular provider needs, but there would be a problem using XYZ from a usage that only provides two inputs. To avoid those problems, you would always use the three-parameter overload, so why have the two-parameter one at all?
Now, if current usages of IServiceProvider are in areas that don't have and don't care about agentId, and therefore it would be difficult to begin providing it, then I would recommend an Adapter that the concrete XYZ provider plugs into, that implements your current IServiceProvider, and makes the new provider work like the old ones by providing the agentId through some other means:
public class XYZAdapter: IServiceProvider
{
private readonly XYZServiceProvider xyzProvider;
public XYZAdapter(XYZServiceProvider provider)
{
xyzProvider = provider;
}
public void Authenticate(string username, string password)
{
xyzProvider.Authenticate(username, password, GetAgentId());
}
public int GetAgentId()
{
//Retrieve the proper agent Id. It can be provided from the class creator,
//retrieved from a known constant data source, or pulled from some factory
//method provided from this class's creator. Any way you slice it, consumers
//of this class cannot know that this information is needed.
}
}
If this is feasible, it meets both LSP and ISP; the interface doesn't have to change to support LSP, therefore preventing the scenario (recompiling and redistributing dependencies) that ISP generally tries to avoid. However it increases class count, and forces new functionality in the Adapter to correctly get the needed agentId without its dependent having to provide anything it wouldn't know about through the IServiceProvider interface.