2

If we program to interfaces various parts of the implementation can be effectively hidden. We can define multiple interfaces for a single implementation and use them as needed, instead of 4 fixed levels (public, protected, package protected, private).

From this point of view Access modifiers seems redundant and limited. From my experience, they also tend to promote bad practices and bad design decisions compared to interface-managed access. The private access modifier seems especially contra-productive on methods because it limits their testability.

Is there some case for access modifiers that can't be handled better in a more systematic way using interfaces?

11
  • 2
    You still need private, to be able to hide implementation details from things that subclass you, unless you remove subclassing.
    – Caleth
    Oct 19, 2022 at 9:12
  • 3
    "The private access modifier seems especially contra-productive" private is the most important access modifier. You don't need to directly test private code. You test the reachable private code, as part of testing the public interface, and it's perfectly fine for any unreachable private code to have undefined behaviour.
    – Caleth
    Oct 19, 2022 at 9:43
  • 1
    But I suppose that anything not mentioned in an interface is implicitly private in your scheme
    – Caleth
    Oct 19, 2022 at 9:45
  • 4
    "Composition over inheritance" says to prefer composition, not to never do inheritance or consider subclassing an antipattern. Sure, subclassing shouldn't be overused as a way to model things (in the "if your only tool is a hammer, everything looks like a nail" fashion), but it can work in tandem with composition and interfaces. Oct 19, 2022 at 11:23
  • 2
    @OndrejBozek: subclassing is not an antipattern - where did you get this nonsense from? Subclassing in cases where composition could be used sensibly instead is an antipattern.
    – Doc Brown
    Oct 19, 2022 at 15:16

2 Answers 2

3

Access modifiers and interfaces serve different purposes in an object-oriented language. An interface is used in cases where you desire loose coupling and polymorphism. Access modifiers are used to promote data hiding and encapsulation at the compiler level.

The critical flaw with using interfaces for data hiding and encapsulation is revealed when down-casting an interface to a concrete class. Most OO languages allow you to do this:

public interface IFoo
{
    void Something();
}

public class Foo : IFoo
{
    public void Something() { }
    public void DoNotCallMe() { }
}

IFoo foo = new Foo();

foo.Something(); // <-- "allowed" by the interface

Foo bar = (Foo)foo; // <-- cast IFoo down to concrete Foo

bar.DoNotCallMe(); // <-- this compiles and executes at runtime

This allows code to circumvent restrictions provided by the interface if you are willing to risk a runtime exception. Most programmers, since it compiles, will ignore the runtime risk, because they got something to work.

Access modifiers prevent unauthorized access at compile time. If you mark a method or field private, even down-casting from an interface to a concrete class prevents careless or malicious programmers from using something inappropriately.


A word of caution about languages that support class reflection. You can still circumvent private access modifiers in many languages, but it is a much bigger pain than simply down-casting an object.

Solution: trust that a developer who is using class reflection has a valid use case for doing so (see Object-Relational Mappers). If you do not trust the developer, do not hire them.

Also, a word of caution when using compilers that allow you to turn off access modifiers. Access modifiers are not magic. They are enforced by the compiler, so if you tell the compiler to not enforce this restriction, then you can circumvent those restrictions.

Solution: never compile your application using a compiler you do not trust, and never have someone compile your application if you do not trust them.

3
  • Good point, but this type cast is antipattern and would shine bright in review. It could be prohibited by encapsulating Impl class in Proxy class which implements only interface methods and forwards them to Impl. Oct 19, 2022 at 14:59
  • @OndrejBozek: introducing a proxy class instead of properly using access modifiers would also be an anti-pattern. Oct 19, 2022 at 15:31
  • 1
    @OndrejBozek, in Swift such casts are safe and expected. You write “if let bar = foo as? Foo”. as? Means a cast that can fail and returns nil on failure, “if let” assigns an optional value to a non-optional that can only be used within the If statement. Very common pattern. Especially when you have a protocol (interface) “Any” which can be literally anything, so you must cast it.
    – gnasher729
    Oct 19, 2022 at 19:34
2

It really helps to understand what the keyword interface really is. It’s a hack. A retrofit. Created because they finally figured out how to support multiple inheritance but couldn’t make it work with existing classes. The venders wouldn’t allow a change that would require existing, working, tested Java byte code to be recompiled. So rather than classes that support multiple inheritance we got the keyword interface.

It started with Java and spread to every language that follows Java. And then it stopped. Because it’s not what you do if you have a choice.

If you have a choice you design your language to support multiple inheritance right in your classes. No need for the interface keyword at all.

With this in mind, understand that “program to an interface” simply means to access only what is meant to be publicly available. A keyword interface isn’t required. Just an understanding of what is meant to be public.

And since the understanding is the important thing here you don’t need access modifiers either. Just some way to signal what the public interface is.

And that brings me to Python. In Python you don’t have keyword interfaces. You don’t have access modifiers. You have a convention that says if it starts with an underscore it’s private. And by that we mean you’re on your own if you code against this. We might change it at any time. No whining if we do.

No keywords. No modifiers. Just a clear signal that you’re poking around in the employees only part of the store.

So yeah, access modifiers can be replaced. We can do without a lot of that stuff just with some clear communication.

12
  • Kudos for getting to the heart of the matter, and clearly stating what the saying actually means. Oct 20, 2022 at 20:36
  • A complementary view to Python, there's C++'s concepts, static checks which apply to a type at the point of use, rather than keyword interface applied at point of declaration.
    – Caleth
    Oct 21, 2022 at 10:18
  • @Caleth yes and C++ has multiple inheritance right in it’s classes. But C++ does have access modifiers. Easier to make the point with Python. Oct 21, 2022 at 14:11
  • 1
    So you are saying the "interface" concept in Java was a predecessor of multiple inheritance? Sorry, but I don't think that's historically correct, MI in C++ and Smalltalk is definitely older. And I think Java language designers choose intentionally to allow implementation inheritance only with a single class, and only "interface inheritance" with multiple interfaces, since the experiences of MI in C++ showed there were way too many possibilities to use MI wrongly.
    – Doc Brown
    Oct 22, 2022 at 22:02
  • 1
    @VisualMelon The point of my rant was to drive home the idea that the phrase "program to an interface" isn't about interface as in the keyword. It's about the predating concept of interface as in the API. Oct 23, 2022 at 10:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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