2

Let's say I have an interface:

 public interface IAccountStoreManager
{
    bool IsUserMemberOfAny(string username, string[] groups, out string[] containingGroups);
}

The method signature of IsUserMemberOfAny was affected by my observation that the client class would check a user's membership in multiple groups. For example, it might be that to check if a user account is that of a supervisor, they would be a member of either one of Admins1 or Admins2 groups.

If I were to think of this method without considering that there would be more than one group to check membership of, I might have designed a method like this:

bool IsUserMemberOf(string username, string group);

and ended up with a loop containing a call to the method implementation. This loop could be costly if the implementation were to interact with an Account Store like Active Directory for example, where we would have something like:

using (PrincipalContext adPrincipalContext = new PrincipalContext(ContextType.Domain, ldapSettingsConfig.DomainController, ldapSettingsConfig.Username, ldapSettingsConfig.Password))
        {
            using (PrincipalSearcher ps = new PrincipalSearcher())
            {
               //code here
            }
        }

So my question is, what is recommended regarding the effect of implementing and client classes on the design of an interface? Is it recommended to let the implementation shape the abstraction (interface) in such a way?

1
  • 3
    I think you found out that any interface you build you have to be concerned about its intended audience.
    – Matthew
    Dec 22, 2016 at 13:51

5 Answers 5

6

Absolutely. An interface is designed to be used, so the expected usage patterns should definitely shape the interface. This is why API design is sometimes described as something of an art, and human factors and intended audience should be taken into consideration.

Interfaces should not expose implementation details, but constraints dictated by the underlying implementation should be expressed in the API in such a way that problematic uses is discouraged. Otherwise you create a leaky abstraction.

3

Broken abstractions are created all the time when they are created with no thought given to the implementation. Just look at ASP.NET web forms. Microsoft had a great idea for hiding a lot of the ugly web development details, but anyone who wanted to create decent sites quickly realized that you had to fully understand HTTP, caching, state management, javascript, etc, and you ended up fighting against the web forms abstraction, rather than utilizing it as designed.

I used to work with an architect who would always say, "that's an implementation detail" anytime we would question the design of something and provide an example of how it might not work. He just didn't care. His design was the most important thing and not how it was going to be used. This is an absolute fallacy to think this way and it can have detrimental, long-term consequences.

I have learned over the years to think in concrete terms first, and then work backward into a set of interfaces that abstract out the details. Think about the implementation of objects up front and not after the magical black box is created. And don't get caught up in addressing every eventuality and creating cumbersome components that were designed to do much more than what they were originally intended to do. Keep it simple and always think about how an object will be utilized.

1
  • Good example of an incomplete abstraction where the client has to reach to the underlying implementation to fulfill their needs.
    – Erik Eidt
    Dec 22, 2016 at 15:36
1

I agree with the answers by JacquesB and Telastyn, though I thought I'd add to the discusson:

The primary consideration for providing an interface should be the intended usage. Our job is to provide our clients (even if that is only us) with a usable and complete abstraction.

If the abstraction is not complete we find that our clients need to make up the difference; often this means doing reaching around the provided abstraction to a lower layer, doing things that rely on the specific implementation, and creating tighter coupling than we'd like to see.

While not necessarily key in this situation, the example loop you are showing does not inherently offer atomicity. With the asynchronous systems we have today, it is a given that information, once queried and transmitted, can be stale, but it is much better if the information provided is at least a single self-consistent snapshot in time rather than a series of answers potentially from different points in time.


I can't say I'm a fan of using strings to represent users and groups. Ideally these concepts would be first-class entities, e.g. in Java each their own class for type safety.


While we can't really tell from this small snippet, I would probably expect a more role-oriented enumeration rather than groups and containing groups. It looks like clients will still need to do work to determine if the user has special privileges.

1
  • 1
    Anecdotally, interfaces that are extracted from (at least three independent) existing clients seem to be better designed and more robust. My favorite personal anecdote is the "generic" source control provider interface for Eclipse, which was designed with CVS as its only client, and, as it turned out, supported every version control system, provided that system was CVS. A similar thing happened with Visual Studio. The "generic" source control provider had to be replaced because it didn't even support Microsoft's own successor to VSS. Dec 22, 2016 at 17:00
0

Is it recommended to let the implementation shape the abstraction (interface) in such a way?

No, the implementation shouldn't influence the interface - that is by definition a leaky abstraction. On the other hand, interfaces should be easy to use - they should provide what consumers want.

But that's the ideal side of things. On the pragmatic side of things, you do occasionally run into these sorts of things where providing the un-leaky abstraction or doing what consumers intuitively want means that you're forced to implement things in a way that makes the code unusable due to performance/scalability/robustness/security/etc reasons.

This is one of those common cases where you start with the guidelines, until reality forces you to compromise.

0

Is it recommended to let interface clients shape the interface?

Yes

According to the Interface segregation and Dependency inversion principles (the I and D in SOLID) the client (high-level module) should define the interfaces of its dependencies (low-level modules).

This means that you should create the client first together with the interfaces best suited for its needs. Then these interfaces should be implemented as external libraries.

Of course you should be pragmatic and if the "ideal" interface is too complex for implementation it should be changed. But you should clearly know that this is against the good practices and is done for a reason.

Your Answer

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

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