Object Oriented Programming: Curiosity of access modifiers

In object oriented programming languages there are a number of different access levels available. These access levels can be applied to fields and methods. Access levels are there to ensure the features of an object are called only by those parts of the logic that are allowed to access them. They enforce encapsulation, separation of implementation logic.

In a class hierarchy-based OOP language there are typically 4 levels of access modifiers:

Access modifiers in practice

Now if we look at this from the perspective of implementation and usage logic, we find that there is something curious going on.

private, package-private and public all define an access level that is relevant to the usage of such an object: private features are restricted from being used by anything but implementation logic, package-private features can be used only if the logic resides within the same package, and public can be used by any (usage) logic.

Similarly, there is a scale that can be explained for implementation logic. private can be accessed only by features within the same class, protected can be accessed only by features within the same class hierarchy (i.e. derived from this class, thus extending on its internal state) and implementation logic, and public features are open to all, including any implementation.

A schematic representation of the access levels:

           <<usage>>  public
                         |
                         |
          <<usage>>  protected  <<implementation>>
                     /       \
                    /         \
 <<usage>>  pkg-private       ???  <<implementation>>
                    \         /
                     \       /
                      private  <<implementation>>

Note that if you make a feature accessible to the (derived) class hierarchy, i.e. other classes that derive from this class, that you are also making it accessible to usage logic within the package. This seems like a curious choice, since implementation logic exists for a different reason than usage logic. The level denoted by ??? is a blind spot. There exists no access modifier that sets the access level to that particular spot in the graph.

Effectively, this means that if you open up your implementation methods to classes that derive from your class, then you are implicitly opening up your otherwise hidden (encapsulated) implementation logic (remember, this is internal state management) to usage logic within the package.

One can argue that this is not a big issue, as we implement the package ourselves. However, my point is that you unnecessarily expose implementation logic to outside influences, making it available to be called by usage logic, even though it may be intended for implementation logic only. And, as we know, usage logic is not responsible for or interested in preserving internal state consistency.

I can only speak for myself, of course, but I have had more cases where the missing access level would be appropriate than I have had cases where the current protected level is appropriate. That is, where I have written a method that was required to be accessible for both implementation logic in derived classes and usage logic within the same package.

The right way to use ‘protected

We discussed how Java’s protected access modifier is not as useful as expected. Now we’ve seen earlier that there are different concerns when accessing for the purpose of using a method from accessing for inheriting from an object and extending the object with new implementation logic. Considering these distinct goals, only one of these two goals should be targeted when a method is implemented.

Given the above caveat, it would be best to use access modifier package-private to grant package-level usage access (this is actually in line with intentions) and to grant inheritance-level implementation (privileged) access with access modifier protected. The latter, protected, grants wider access than described here, but for the purpose of control and manageability we should refrain from leveraging protected access level for usage logic.

C#’s access modifiers

It turns out that C# defined the access modifiers differently, making them more suitable for the goals as described above. See Accessibility Levels (C# Reference). There, protected really does mean that only access from derived classes is allowed. It correctly separates the usage from the implementation levels. Quote from the ‘protected’ access level description:

protected Access is limited to the containing class or types derived from the containing class.

Access modifiers in Go

To give you an impression of how you could otherwise arrange access levels in programming languages, we’ll look at the access levels in Go. Go has a very limited set of access modifiers … actually it does not have any access modifiers. But it does have access levels. There are 2 variations: unexported and exported. The unexported level exists to limit accessibility to logic in the same package. Exported access does not impose this limit and makes these features freely accessible from the outside.

Go manages these access levels through the use of capitalization of the start of a method name. If the first letter is a capital, then a feature is exported. If it is not a capital, then it is limited to use inside the package.

Note that there is no further access level for making a feature private to only a single type, such that even other logic inside the package cannot access it. In Go, inside a package you can access everything. Within the same package, there is no real way to prevent logic that only uses an object from accessing certain features. There is, however, the option to use an interface definition to restrict the amount of methods that are exposed. As the specific type is not known, only the methods of the interface are provided.

As a side note

Technically one could argue that public is a valid implementation access level too. This would qualify if you consider global state, such as static variables spread over multiple classes, as state that should be managed. Since global state is not exactly best practice in object oriented programming, I do not take that into account here.


This post is part of the Object Oriented Programming series.
Other posts in this series: