6

According to Why is Clean Code suggesting avoiding protected variables?, I know there are tons of reasons to avoid protected variables.

However, there is a reason at the currently highest voted answer : https://softwareengineering.stackexchange.com/a/162657, which I don't understand : why do "protected variables" tend to violate open closed principle?

Because I think it is quite opposite, protected variables should tend to be more following open closed principle : when you want to extend the current class, if the new function doesn't require the parent class instance variables to participate, then regardless of if the instance variables are private or protected, you don't need to modify the source code of the base class. However, if the new function requires the parent class instance variables to participate, if the instance variables is private, I need to modify the source code of the base class to change the visibility from "private" "protected" first, or add the getter. In other words, if modifying the base class is not allowed, the "protected" one has more ways to add new functions (modify the base class behaviour) than the "private" one, which should be more following open closed principle.

Also, I don't really understand what is the meaning of "make assumptions about the protected member", I guess it is about the preconditions or invariants of the protected member. But even if preconditions or invariants are changed due to requirement changed, I think it is not a kind of "extension", but "modification", modifying the base class because of preconditions or invariants are changed instead of because of adding new features seems doesn't violate open closed principle.

And if the reason is "it may tend to add instance variables in order to add a new feature", I still disagree that it tends to violate open closed principle. While it may be an actual problem, I think it doesn't mean it is violating "open closed principle" : if there are some protected instance variables that is not necessary afterwards, deleting them from the base class should be a kind of "modification", not "extension". Also if your new requirement actually needs a new instance variable, regardless of it is private+getter or protected, adding it to a base class still requires modifying the base class.

While I agree there are other reasons to avoid protected variables, "tend to violate open closed principle" seems not one of them, and I think it is quite opposite : it tends to be more following open closed principle because there are more chance to add new features but not required to modify the base class (change the visibility from private to protected or add getter). So my question is, why would the answer suggest "protected variables" tend to violate open closed principle? Or I misunderstood the answer?

2
  • 1
    There can be no assumptions about exposed writable property and it is fine - assume it may be anything and there is no invariant. Commented Apr 1 at 0:11
  • 1
    "Assume it may be anything" is good advice that people are unlikely to follow consistently. Commented Apr 1 at 18:19

7 Answers 7

7

Original answerer here. Thanks for reading, and thank you for following up with this question.

First, the Open Closed Principle.

Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.

I know that wikipedia, and some of my fellow answers focus on source code, but I do not. The SOLID principles, like most engineering guidelines, are there to help people make better software with fewer bugs. They're years and years of engineering experience distilled into a witty quip so that you'll remember them. Following rules for the rules' sake is a waste of time.

Changing source code isn't what you should be focusing on. Almost all of programming is changing source code.

What the OCP tries to prevent is a modification of behavior, while allowing you to extend the behavior.

So your point:

modifying the base class because of preconditions or invariants are changed instead of because of adding new features seems doesn't violate open closed principle.

It absolutely does.

Think of it this way: You are responsible for a base class. It's used by thousands and thousands of programs around the world. Some inherit from the base class. Some compose it with other classes. Some merely consume it. It works great.

Now you change the invariants and post-conditions of that base class.

How the hell are you going to verify that it still works great for all of your users?

That's the sort of thing that the OCP exists to deal with. If all you do is extend the base class, while maintaining invariants and postconditions then you are safe.


Protected variables are more dangerous here because of scope.

If the variables were private, you could be certain (or reasonably certain in some languages) what their invariants were. They could only be modified in the base class and every consumer depended on that exact behavior. But for protected variables, they can be written by any subclass, anywhere. Better than global variables and better than public variables, but that same sort of danger. Code you don't know about is doing stuff to variables you're trying to use.

It's practically inviting bugs into the system.


The gotcha here of course is that many of the invariants and post-conditions of protected variables are implicit. There's no type constraint on them, or any linter enforcing the behaviors.

Yes, people can use protected variables without breaking the implied contracts, but my years and years of engineering experience (including the twelve and a half years since my original answer) is that they substantially increase the risk of breaking things.

1
  • I'd try to avoid "protected" variables - unless I intentionally create a set of classes that all use the same base class and nobody else uses the base class, for example because it is hidden. But I don't see a violation of the open/closed principle. Commented Apr 2 at 16:32
4

The original OCP paper addresses this directly. Under the section "Make all Member Variables Private", we find:

This is one of the most commonly held of all the conventions of OOD. Member variables of classes should be known only to the methods of the class that defines them. Member variables should never be known to any other class, including derived classes. Thus they should be declared private, rather than public or protected.

That's seems like an 'open-and-closed' case as to whether protected members violate the principle, at least according to 'Uncle Bob'. As to why, he explains:

When the member variables of a class change, every function that depends upon those variables must be changed. Thus, no function that depends upon a variable can be closed with respect to that variable.

... we do expect that any other class, including sub-classes are closed against changes to those variables. We have a name for this expectation, we call it: encapsulation.

You should probably read the original for the rest of his argument, but I'll add one more excerpt from the end of the section which shows that he explains that it's not strictly the case that protected variables violate the principle:

Thus, in those rare cases where the open-closed principle is not violated, the proscription of public and protected variables depends more upon style than on substance.

4

TL;DR The answer you linked makes a few too many assumption, in my opinion, about a cherrypicked scenario where something is already going wrong. Your interpretation, based on the question you posted here, is mainly focused on scenarios that are polar opposites of the scenarios that the original answer focused on, which is why you're not seeing a lot of agreement between their stance and yours.

Realistically, it's contextual. It depends on what kind of scenario you find yourself in. But for the purpose of defining a generalized good practice guideline (as opposed to a specific targeted feedback to a specific group of devs), I align myself with your stance more than that of the previous answer's.

Part of this is a clash of definitions.

If I see a protected variable, my inference is that the designer of the base class exposed it there for me to use (if I need to). But the answer's first bullet point:

  1. They tend to lead to YAGNI issues. Unless you have a descendant class that actually does stuff with the protected member, make it private.

... interprets it differently. They're arguing a scenario where exposing the variable (by making it protected) was done without consideration that a derived class would make use of it.

The issue here is one of context. If the base class has not been designed to account for how any derived classes intend to alter it, yeah then the base class should not be exposing variables which allow derived classes to alter how it works.
At the same time, if a base class has already considered (as part of its design) how to allow derived classes to alter it, then it invariably needs to expose this access via protected members (fields/properties/method as is appropriate).

Which brings us to the second bullet point in the answer:

  1. They tend to lead to LSP issues. Protected variables generally have some intrinsic invariance associated with them (or else they'd be public). Inheritors then need to maintain those properties, which people can screw up or willfully violate.

... and again, the interpretation made by the answerer is that things were made protected before/without considering if and how they should be used by deriving classes; which in my opinion makes little sense.

However, that's not to say that "don't expose what you're not ready to handle yet" doesn't make sense. I've worked in teams where the vast majority were inexperienced juniors and they needed to be told this information.

But as far as mid-level (or above) developers are concerned, this advice seems trivial, and the second bullet point seems to operate from an assumption that the preceding developer exposed things that they were not able to have altered by outside sources, which amounts to pointing out a rookie mistake, not providing an advanced development guideline for non-rookies.

Third bullet point:

  1. They tend to violate OCP. If the base class makes too many assumptions about the protected member, or the inheritor is too flexible with the behavior of the class, it can lead to the base class' behavior being modified by that extension.

... is in my opinion irrelevant. I don't mean that the opinion is wrong, I mean that it's just on another topic entirely. Whether or not a member should be made protected is a completely different question from judging how a base class could potentially have implemented any logic surrounding that member in a less-than-ideal way.

Not making members protected is not going to stop an OCP-violation-creating developer from creating OCP violations in their code. They're just two completely unrelated considerations.

Fourth bullet point:

  1. They tend to lead to inheritance for extension rather than composition. This tends to lead to tighter coupling, more violations of SRP, more difficult testing, and a slew of other things that fall within the 'favor composition over inheritance' discussion.

... is not very wrong in the sense that relying on protected access modifier inherently means relying on inheritance, and there is a general shift towards composition over inheritance.

However, we again strike on that same kind of different interpretation. Yes, it's correct to say that a developer should consider composition over inheritance. No, that does not mean that you should never use inheritance. And no, therefore it also does not mean that there is never a scenario to use the protected access modifier.

On the premise that inheritance is the right call in a scenario, there is nothing wrong with then using protected members where appropriate. The fourth bullet point's assumption that protected access modifiers are a gateway drug to overuse of inheritance is again, in my opinion, nonsensical.

But it would be unfair for me to omit the closing sentiment of that answer:

But as you see, all of these are 'tend to'. Sometimes a protected member is the most elegant solution. And protected functions tend to have fewer of these issues. But there are a number of things that cause them to be treated with care. With anything that requires that sort of care, people will make mistakes and in the programming world that means bugs and design problems.

... because they are right here, things are contextual and there are cases where it is the right approach, and cases where it is the wrong approach.

The part of the answer I disagree with is the interpretation that the use of the protected access modifier was executed before considering that it was a good call to give derived classes access to this member and considering if inheritance was even right in the first place.

In my opinion, that is a nonsensical interpretation in most cases. This may be good advice for specific developers who are making that specific mistake of exposing things before evaluating their design; but I don't think it's a productive basis for a broader good practice guideline, which is what that answer is intending to establish.

1
  • I feel i need a comfy armchair, roaring fire and a fine wine before sitting down to read your answers :) Commented Apr 1 at 18:51
4

I agree to JaquesB's answer that introducing protected member variables does not literally violate the OCP - protected members do not make a class technically less closed against modification or less extensible.

What I don't think is that the linked answer is wrong in full about the OCP. Protected member variables (tend to) make it harder to follow one of the goals of the OCP - which is IMHO not just to make a class or component extensible, but also to make it easy that users of a class extend it correctly. Reusage of a component by extension should not be errorprone, and a component which is designed for reusage should not make it easy for a user to introduce subtile errors without noticing. That is specifically an issue when the component is a compiled black box, where one cannot see in detail how a protected member variable is used inside, or which invariants the component designer had in mind.

In this sense, a class which contains protected members tends to be "too open" for extensions, more open than it should be to prevent errors.

4
  • 1
    Agree with your main points about the perils of protected variables. I'm just saying this has nothing to do with the OCP. If you modify the implementation of the base class (which the OCP disallows), protected variables also risk causing undesired changes in the behavior of inheritors. So the problem is not in particular related to the OCP but is more general about encapsulation and interface design. Commented Apr 4 at 16:46
  • @JacquesB: first, let me say, as you already know, I am convinced the OCP does not disallow modifications to a component, it makes them unnecessary . To may understanding, it is a principle which is applied first and foremost at design time of a class beforehand. Still, for my answer here, this distinction is irrelevant. ... Commented Apr 5 at 10:00
  • Assume a closed component which has protected member variables. Does this affect the black-box extensibility? Technically not - that's where we both agree. It looks like the opposite is true: less encapsulation allows more kind of extensions. But does this affect the ability of a user of the component to reuse the component correctly in a negative way? That can be the case, and I think you agree to this as well. Now has this still anything to do with the OCP ? ... Commented Apr 5 at 10:03
  • ... well, definitely not literally. But I guess a designer of a component who tries to create an "OCP-compliant" component, one which can be reused in a black-box fashion, has - usually - also the goal to create a component which can be easily extended without producing extensions with subtile, unforeseen errors. And that's where protected member variables are a weak instrument. So, I think this has at least something to do with the OCP, not directly and literally, but at least when it comes to aligned goals. Commented Apr 5 at 10:09
4

The linked answer is just mistaken about the Open-Closed Principle. The answer talks about causing changes in the behavior of a base class, but the Open-Closed Principle is about avoiding changes in the source code of the base class.

Altering behavior through extensions is the entire point of the Open-Closed principle.

The liked answer list the principles YAGNY, OCP etc. as it is a checklist and you want to conform to them all. This is an (unfortunately common) mistake. The principles should be used tools, not dogmas. In reality OCP and YAGNI are largely contradictory principles, and you have to select which principle is appropriate for a project.

OCP is appropriate when the code is published in the form of a library and consumed and extended by third parties in code-bases you do not control. Any change to a base class will have a high risk of breaking some existing code. You have to think about the ways consumers might want to extend the components and carefully design the base classes and interfaces for extensibility.

YAGNI is appropriate when you have full control over the source code and you have automated tests which detect if a change introduce bugs. You don't need to complicate the code by designing for potential future extensions which might not happen. Instead you extend or alter the code when you need it.

Unfortunately, much advice does not distinguish between these very different scenarios.

A downside to protected (and public) variables is you can't change the implementation later. For example adding validation or change the underlying representation in the base class would be a breaking change for the client. Therefore it is recommended to use getter/setter instead of just exposing a private variable. In theory this criticism should not apply if you follow the OCP, since you are not supposed to modify the base class anyway. But the original version of the OCP acknowledged that it is OK to change base classes to fix outright bugs. So you should still follow good practices of encapsulation.

9
  • 1
    (and it's not just a theory, I broke my nose on stuff like that in real projects. In particularly bad cases modifying behavior in subclasses has been literally choked by expectations buried in parents) Commented Mar 31 at 15:43
  • 2
    I think you might be right in terms of the original meaning of "closed", "protected/public var violate the OCP" is a bit of a stretch, really they are questioning if you can "close" a module if it still exposes those things, is it stable enough to release? But the problem would be better stated in terms of encapsulation or some other principle like the LSP Commented Mar 31 at 20:28
  • 2
    @JacquesB: OCP violations are not inherently tied to inheritance; you can violate OCP the exact same way using interfaces as a base type. The only thing OCP violations require you to do is write base-type-oriented logic that forcibly has to consider certain concrete derived/implemented types (of that base type) to keep it all working. Inheritance is one scenario to violate OCP, but OCP itself does not encourage (nor does it force) you to use inheritance. OCP does encourage polymorphism, not specifically inheritance. Commented Apr 1 at 1:04
  • 1
    Though I think this answer is literally correct, I guess the original post meant that protected member variables (tend to) make it harder to extend a component correctly (because it makes them "more open for extensions than they should be"). Yes, protected members are a way to design a OCP-compliant base class, but there are often ways which are less error-prone. Commented Apr 1 at 4:54
  • 1
    Also, just saying they are wrong, doesn't explain why they think they are right Commented Apr 1 at 18:49
2

I guess the argument is that by changing the state of a protected variable you modify the functionality of the base class.

eg

public class Base { protected bool isOpen; public Task DoThing1() { isOpen = getConnectionState(); .. } public Task DoThing2() { if(isOpen) {... //more optional stuff } 

Now if I inherit and have some new method

public class Child : Base { public Task DoSomethingElse() { isOpen = false } 

I've modified the behaviour of the base in the same way as if I had been able to change a private variable. Calling :

x.DoThing1() x.DoThing2() 

Is not the same as :

x.DoThing1() x.DoSomethingElse() x.DoThing2() 

The change is a modification rather than an extension because we have no control over the side effect. A new version of Base might break the combined functionality. If it wants to be sure not to, it has to treat protected like public.

So the question is really, "What's so special about an inherited class that it should be allowed to change stuff that other classes can't?"

5
  • 1
    But you did not modify the behavior. The behavior was - read the property do stuff. And it sayed the same. Base class has exposed the property, so no assumptions about its state should be made. Commented Apr 1 at 0:12
  • "so no assumptions about its state should be made" this is the argument, that using protected implies that its private to some extent. If you treat all your protecteds as if they were public, then you avoid the issue, but no one does. Commented Apr 1 at 7:53
  • They are private to final child! Base class can make no assumptions, but child can. Commented Apr 1 at 8:49
  • only if its sealed/final, but the base worrying about child class interference is the problem here Commented Apr 1 at 18:43
  • My problem with your comment is, even if you are right it doesn't matter. my answer is explaining the reason why people say protected violates OCP. You are arguing they are wrong, not that my explanation of their argument is wrong Commented Apr 1 at 18:45
1

After reading through the comments to my and other answers I will take another crack at it.

There seem to be two key points that are being made:

#1 - All mutable member variables should be private - simply because any protected or public members could be mutated at any point / hence you don't have control over when such mutations occur / as a result you can't include that variable in the invariants.

#2 - The other argument is that methods shouldn't be marked protected either, the logic being that a subclass shouldn't be granted any special access - it should use the same public methods that anyone else can use.

I disagree with the second point if the parent class is Abstract - I can see the need to force an implementation of a protected method.

More generally I can see the need for protected methods if there is a possibility that a child class may override the functionality / override an empty method to receive an event.

However I am tempted to agree that for methods that are not overridden its difficult to come up with a use case where only a subclass would need access to call a method (such methods could/should be public in such cases).

1
  • 1
    No, you are not relying on child, you just exclude that part of state from your invariants. Commented Apr 1 at 0:17

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.