Non-public Interface members


#1

I believe protected and internal interface members is an underestimated feature modern languages have to support. And here I’ll explain why I believe so.

Let’s scrutinize some use cases:

  1. A general-use interface like Runnable. It’s widely used by a variety of frameworks and end-user code. It’s usually implemented by small objects with limited functionality and scope of usage. It’s the case when the statement “an interface is a public contract between the caller and a callee” is relevant. So the public access to its method run() is fairly justified. WHATEVER > INTERFACE > WHATEVER = Ok

  2. A framework-specific interface. In this situation, a framework defines an interface to interact with user code which implements this interface. And it’s not supposed to be accessed by a third party. In a simple scenario when only the framework and end-user code involved there’s no reason to conceal public interface method because its access is under end-user code control. FRAMEWORK > INTERFACE > END-USER = Moderate

  3. A scenario when a framework interface is implemented by some object of a middleman library, and later, this object is exposed to end-user code. In this case, this end-user code gains the ability to inadvertently or deliberately mend the library object’s functionality. Or at least an end-user sees in IDE’s code completion list those members of the interface which s/he shouldn’t see. The illustration below :slight_smile: FRAMEWORK > INTERFACE > IMPLEMENTER LIBRARY > LIBRARY USER = Bad.

  4. A scenario when a framework/library defines an interface with public functionality that contains some internal implementation of that functionality and its implementations have to be combined (so that it cannot be based on class). In this case, end-user is forced to see internal implementation members, which isn’t what s/he wants too. = Bad.

I presume the genuine reason the Interface is given only public members is related to a legacy issue with Java. Java doesn’t have the internal access modifier. And at some moment some guy formulated a concept of the Interface as “public contract between a caller and a callee”, maybe because it’s otherwise impossible for the interface defining framework to access interface protected members from outside of a package where the interface is implemented. However, the key feature to contract is the identities of both sides. For example, if I bought a car in installments, I received a document that the car is mine. But as it happens with a public interface in the formulation above, anyone can drive my car if s/he has a copy of my contract. Thus this concept has an inherent issue.

What is a suggested solution:
Since Kotlin has the internal access modifier it’s possible to have an interface with protected and internal members, which can be defined in the initial project and can be implemented in another project, and all members can be accessed from the initial project (protecetd can be accessed via internal ones). Thus eliminating the problem of accessing interface’s protected and internal members implemented in another project.
Those interface protected and internal modifiers can be specially annotated indicating, that they are still visible from Java code. For example, in the same manner, as the internal inline modifier is annotated with @PublishedAPI.

PROS:
it resolves complications related to the interface access limitations.
It’s relatively easy to implement in Kotlin, I hope. (As I see it it’s a matter of consulting the compiler to let annotated non-public modifiers, and IDE editor to use them for the purpose they serve).
If Java team ever decide to support this feature, there’s nothing to change in user code.
CONS:
There is some work for the Kotlin team to do.
There are three types of interoperability: 1. Kotlin > Kotlin, 2. Kotlin > Java and 3. Java > Kotlin.
For the 1 and 2, this is just a benefit. The 3-d group is the java users that want to use some cool Kotlin library, which is the case when a team of developers stuck on a legacy codebase. And this segment is decreasing each day as more projects start to use Kotlin as a primary language. This group will still see those non-public members as public, but they would see it anyway. So Win-win solution.

ifaces


Why does Kotlin prohibit exposing restricted-visibility types?
Why does Kotlin prohibit exposing restricted-visibility types?
#2

One thing, for the class should implement an interface with internal members (or the interface is internal altogether) the solution is normally to instead expose an Interface that only provides visibility to the public bits and implementation classes that actually have the hidden bits. You have to use a factory to create instances though - In Kotlin you can make those factories even look like they are constructor invocations.

It starts to get really interesting when interfaces have default methods where classes implement non-default methods that can then be used in the default methods, but the non-defaulted methods are not really for use externally.


#3

You’re right. We can use wrappers to conceal public stuff exposed by an interface… instead of having an option of defining an interface that conceals its stuff by itself. And that is called a workaround, which doesn’t add a value to the language.