28

I”m working on a C# library where the API provides several public interfaces and a single concrete factory class (itself an interface implementation). This factory provides implementations of the various interfaces. Other than the factory, none of the actual implementations are available to the user.

As is the nature of using interfaces, this decouples my users from being concerned with how I variously decide to refactor the implementations. I know that’s good practice in itself, but I do feel I’m maybe being a little restrictive not making the implementations public.

Is this common/accepted practice? What are the pros and cons of this approach?

12

23

While this may be an opinion-based question, and the real answer is the same as with many endeavors in software design and development (“it depends”), I’m going to say yes, do not expose these implementation details by adhering to the Principle of Least Privilege. If and only if you find your design has good reason to expose the implementation for extension — adherence to another design guidepost known as the Open/Closed Principle — should you make that available to other parties.

45

As is the nature of using interfaces, this decouples my users from being concerned with how I variously decide to refactor the implementations.

Let’s take a step back and make sure we understand what the value of an interface is. It’s not really what you’ve stated. Take the following example:

interface IFoo 
{
  void baz();
}

Now later you want to refactor this and realize the method shouldn’t be named baz but bar instead. Can you do that refactoring without the users of the interfaces being concerned about it? No. How about adding or changing parameter types? No. You can add new methods to the interface without breaking things but that doesn’t mean the user is not or should not be concerned. Nothing about this changes by using an interface or not defining one.

What about refactoring the implementation? You can (carefully) change the implementation of a public method on a concrete class without users knowing or having concerns about it. You can also change method’s implementation to be incompatible with previous versions. Putting an interface in front of that doesn’t really change anything with those two scenarios.

The only point of an interface is to define a contract which is independent of a specific concrete class. In other words, the client should not need to care which implementation it is dealing with. They are expected to be interchangeable with regard to the methods defined on the interface. It shouldn’t even matter to the client whether the contract is defined on an interface or on a concrete class. All an interface does is decouple a set of public method signatures from a concrete class definition. It is a formal way to allow for substitution of implementations.

I think the C# Hungarian wart standard of prefixing interfaces with ‘I’ is misguided (and misguiding.) Interfaces don’t do anything. There’s no reason developers need to be concerned with whether the type they are working with is an interface. A lot of people might think this is just a preference but it creates a real issue. This standard means that you can’t start with a class and then later decided it should be an interface without breaking everything that was using it. Therefore, C# APIs are so packed with interfaces that have a single implementation that I believe it has lead to a lot of developers who ‘grow up’ in C# not understanding the point of them.

In a nutshell, interfaces only support adding additional concrete implementations of a contract and especially allowing users to do so. And, naming standard aside, they can be used to do so without having to introduce them prematurely as long as you don’t allow clients to bind to anything other than public methods. If you follow the ‘I’ wart standard, you must create an interface for every class you might ever want to later provide more implementations of without breaking user code.

27

10

In addition to the points already mentioned by JimmyJames, there’s one additional drawback in exposing interfaces rather than classes: Your consumers can make their own classes implement them, e.g.

// consumer-side code
class MyFoo : IFoo
{
    void DoSomething() { ... }
}

Why is this a problem?

  1. Imagine you want to extend your IFoo interface with an additional DoSomethingElse method. You can’t, without breaking MyFoo‘s code.

    (Note that C# 8+ allows you to work around this issue by adding a default interface method, but if you did not intend your interfaces to be implemented by consumer-side code, why clutter them with code that does not belong there?)

  2. Consumers can replace your implementation with theirs. Imagine that your API exposes a method FrobnicateFoo(IFoo foo), which is meant to be used with a Foo created by your factory methods. Well, your consumer can just pass in their own MyFoo instance instead.

    The general recommendation in C# is to make classes “sealed” unless they were explicitly designed to support inheritance. You can’t “seal” your interfaces to consumer-side code.

What you can do is to only expose an abstract base class: If all its constructors are internal, your API users can’t inherit from it. However, this is not something you need to do a priori: You can start with a regular class Foo. Later, should the need arise to use a subclass (example), you can make your factory method return a (private) SubclassOfFoo instead without breaking backwards compatibility.

16

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *