Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Not all Interface Members will be Implemented

Tags:

c#

Is it right by saying it's common to have "some" interface members not implemented for certain classes simply because in certain circumstances they don't apply so you throw a non-implemented error in the method body?

For example lets say I create an interface IAPIAuthentication that servers as a contract for classes that will perform authentication requests to 3rd party APIs such as Facebook for example and others also that we'll be implementing later on.

So my IAPIAuthentication Interface would have the following properties possibly:

// The URI that the auth HTTP Request will go to (minus any querystring values, this is just the base)
AuthenticationURI (property)

// unique ID for your API account with whatever API you are using (Facebook, Picasa, whatever)
ClientID  (property)

// unique secret code also obtained when you sign up for an API account and used in auth calls
ClientSecret (property)

// a confirmation code sent back from the 
AuthenticationVerificationCodeID (method)

// a boolean property set to true if an AuthenticationVerificationID was received back after an Auth request
AuthenticationWasSuccessful (property)

// sends the actual HTTP Request to the specified Uri
SendRequest()

Ok so in many cases, other APIs require the same info during their auth process (such as PhotoBucket, etc.)

Ok so I created this interface that will be used for various implementations when I create wrappers for these APIs and the whole point of this is to create some good commonality in terms of structure and reuse when I start to create features using some of the wrappers I will build off these generic Interfaces here. These are the basic building blocks for most NVP APIs so what I put in these interfaces will usually all be used no matter what API I'm implementing or at least 90% of the values in any Interface that I'm creating here.

So I will create a class for example FacebookAuth that implements that Interface. All find and dandy. Ok, next time I work on a new project I say hey, I'm going to also implement that interface keeping with the team standard/pattern for creating third party wrapper projects) and I know that I'm going to need lets say all those properties but maybe minus one or two because that provider doesn't require that piece in information during their auth process. Lets say it's just one that it doesn't need.

So my question(s) are these:

  1. Is my approach making sense for the goal of reuse and consistency when I start to create all these 3rd party API wrapper projects later on?

  2. Ok in general with Interfaces as you know, you can dodge implementing a certain Interface method by just throwing at least in .NET a non implemented exception if someone tries to use that method in your subclass. I'm not sure about for properties...how you'd ignore any of them if you absolutely had to (rare one-offs). So is it "normal" to expect that your interface is never going to be "perfect" in terms that all members will always be used across the board 100%? I mean one could say only put in the elements that will be used 100%. Ok fine but it depends...because some APIs will use all in this case most of the time..just a few one-offs won't...so to me it still makes sense to include some that may not be used in others.

I just want some input based on experience out there...developers who are much more experienced with Interfaces than I at this point. I have not used interfaces much..I get what they are (contract, yada yada) but I'm trying to figure out the best use for them and I really think this is a good use. I'll also have Abstract classes...just a few so I know the difference between the two. I just want to know is it acceptable to have non-perfect Interfaces so to say. I guess that's why they version APIs right? But even in an existing version you're going to have some classes that do not fully implement ALL members of an interface but implements that interface for consistency and reuse regardless overall in your app or wrapper...

I hope I did not ramble too much. Let me know if I'm not clear. And keep in my ind my above example is not really complete but gets the point across.

like image 356
PositiveGuy Avatar asked Nov 28 '22 19:11

PositiveGuy


2 Answers

It is generally not desirable to have partially implemented interfaces.

My advice is to separate your concerns, i.e. separate your interface into several interfaces so that implementations can be complete and autonomous.

But, there are cases where the benefit of a unified interface outweighs separation of concerns, IMHO there are very few of these, but they certainly exist. One example is in the .NET BCL, where some ICollection classes are read-only and hence do not support Add, Remove, and Clear. To avoid using exceptions for control flow, ICollection provides an IsReadOnly property to indicate that these methods are not supported.

And if you still decide to keep a single interface for all your codes, throw NotSupportedException rather than NotImplementedException, making your choice of not implementing that operation explicit.

like image 77
Håvard S Avatar answered Dec 16 '22 14:12

Håvard S


From what you've described, you're creating an interface that will be implemented by multiple similar services. Since I don't know your requirements, constraints, or division of responsibility between interface author/implementor are, let me instead examine some of the options available to you. This is by no means an exhaustive list - just those that are common:

  1. Create a "superset" common interface which, for some implementations, it may not be possible to fully implement. Exceptions report which capabilities are unsupported.
  2. Create a "lowest-common-denominator" interface which only has members that all implementation can support. No unsupported exceptions thrown since all capabilities are required to be supported by everyone.
  3. Create completely separate interfaces for each implementing services (which effectively destroys the value of a common, unifying interface).
  4. As a variation of #1, add a property/method that can report which capabilities are supported (so you aren't using exceptions as a mean to check for supported functionality).
  5. As a variation of #2, create inherited interfaces that progressively add additional capabilities.

Option #1 can make it difficult to write reliable code. You always find yourself wrapping each method call in a try/catch block - which can become unreadable. Worse yet, if you forget to do this somewhere you can propagate unhandled exceptions through your call stack, which may itself be disruptive. However, this approach is the easiest for those implementing the interface to deal with - if a capability isn't supported just throw a NotSupportedException - easy.

Option #2 takes may require significant analysis to correctly identify what is a true common subset of supported functionality. This approach is further complicated by the fact that if you guess incorrectly about the ability for all implementaitons to support a feature, you may find yourself in situation #1 by default - requiring you to add all of the necessary logic to handle potential exceptions.

Option #3 is basically: "I give up. There's no common interface here". Sometimes this is an acceptable answer. Trying to wedge an interface where one doesn't naturally emerge can be worse than simply coding against separate (unrelated) interfaces or implementation classes. In this case, you would normally implement "strategies" for each known implementation and group functionality into more coarse-grained methods which can be reliably implemented for all cases.

Option #4 is helpful in that it allows you to first check for whether a capability is supported before invoking it. Implementors would still likely throw NotSupportedException if invoked, the difference is that you wouldn't invoke implementations that aren't supported to begin with. The problem here is that you need to design a mechanism that allows consumers to test which functionality is supported. This approach isn't very object-oriented or particularly extensible (if your inteface evolves), but it is simple.

Option #5 tries to improve on #2 by partitiong functionality - it may be that there is no meaningful "lowest-common-denominator" interface - instead, there may be a family of related interfaces which can be used together. This option also creates the potential for a proliferation of fragmentary interfaces, which may become a maintenance challenge. On the upside, this approach allows you to easily test for which features are supported by an implementation by attempting to cast to a particular interface. It is also more extensible than some kind of SupportedCapabilities enum. In this approach, each implementation would be expected to either support all of an interface or none of it. This isn't always possible to achieve, and runs afoul of the challenges described in option #2.

As the adage goes: "Any programming problem can be solved by adding another level of abstraction". However, it's really up to you to decide what level of abstraction is necessary or appropriate to the problems you are trying to solve.

like image 24
LBushkin Avatar answered Dec 16 '22 12:12

LBushkin