45

When reading various Stack Overflow questions and others' code the general consensus of how to design classes is closed. This means that by default in Java and C# everything is private, fields are final, some methods are final, and sometimes classes are even final.

The idea behind this is to hide implementation details, which is a very good reason. However with the existence of protected in most OOP languages and polymorphism, this doesn't work.

Every time I wish to add or change functionality to a class I'm usually hindered by private and final placed everywhere. Here implementation details matter: you're taking the implementation and extending it, knowing full well what the consequences are. However because I can't get access to private and final fields and methods, I have three options:

  • Don't extend the class, just work around the problem leading to code that's more complex
  • Copy and paste the whole class, killing code reusability
  • Fork the project

Those aren't good options. Why isn't protected used in projects written in languages that support it? Why do some projects explicitly prohibit inheriting from their classes?

8
  • 1
    I agree, I have had this problem with Java Swing components, it's really bad.
    – Jonas
    Commented Jul 12, 2011 at 18:08
  • 20
    Oblig.: steve-yegge.blogspot.com/2010/07/…
    – Shog9
    Commented Jul 12, 2011 at 19:16
  • 3
    There is a good chance that if you are having this problem the classes were designed poorly in the first place--or perhaps you are trying to use them incorrectly. You don't ask a class for information, you ask it to do something for you--therefore you should not generally need it's data. Although this isn't always true, if you find you need to access a classes data a lot chances are something has gone awry.
    – Bill K
    Commented Jul 12, 2011 at 21:15
  • 2
    "most OOP languages?" I know a lot more where classes can't be closed. You left out the fourth option: change languages. Commented Jul 12, 2011 at 22:18
  • @Jonas One of Swing's major problems is that it exposes way too much implementation. That really kills development. Commented Jul 13, 2011 at 11:16

10 Answers 10

59

Designing classes to work properly when extended, especially when the programmer doing the extending doesn't fully understand how the class is supposed to work, takes considerable extra effort. You can't just take everything that's private and make it public (or protected) and call that "open." If you allow someone else to change the value of a variable, you have to consider how all the possible values will affect the class. (What if they set the variable to null? An empty array? A negative number?) The same applies when allowing other people to call a method. It takes careful thought.

So it's not so much that classes shouldn't be open, but that sometimes it's not worth the effort to make them open.

Of course, it's also possible that the library authors were just being lazy. Depends on which library you're talking about. :-)

10
  • 14
    Yes, this is why classes are sealed by default. Because you cannot predict the many ways your clients may extend the class, it is safer to seal it than to commit to supporting the potentially limitless number of ways the class might be extended. Commented Jul 12, 2011 at 19:42
  • 3
    See also blogs.msdn.com/b/ericlippert/archive/2004/01/22/… Commented Jul 12, 2011 at 19:52
  • 18
    You don't have to predict how your class will be overridden, you just have to assume that people extending your class know what their doing. If they extend the class and set something to null when it shouldn't be null then its their fault, not yours. The only place that this argument makes sense is in super critical applications where something will go horribly wrong if a field is null.
    – TheLQ
    Commented Jul 12, 2011 at 19:58
  • 6
    @TheLQ: That's a pretty big assumption. Commented Jul 12, 2011 at 20:05
  • 10
    @TheLQ Whose fault it is is a highly subjective question. If they set a variable to a seemingly reasonable value, and you didn't document the fact that that wasn't allowed, and your code didn't throw an ArgumentException (or equivalent), but it causes your server to go offline or leads to a security exploit, then I would say it's as much your fault as theirs. And if you did document the valid values and put an argument check in place, then you have put forth exactly the kind of effort I'm talking about, and you have to ask yourself if that effort was really a good use of your time.
    – Aaron
    Commented Jul 12, 2011 at 20:14
26

Making everything private by default sounds harsh, but look at it from the other side: When everything is private by default, making something public (or protected, which is almost the same thing) is supposed to be a conscious choice; it is the class author's contract with you, the consumer, about how to use the class. This is a convenience for both of you: The author is free to modify the inner workings of the class, as long as the interface remains unchanged; and you know exactly which parts of the class you can rely on and which ones are subject to change.

The underlying idea is 'loose coupling' (also referred to as 'narrow interfaces'); its value lies in keeping complexity down. By reducing the number of ways in which components can interact, the amount of cross-dependency between them is also reduced; and cross-dependency is one of the worst kinds of complexity when it comes to maintenance and change management.

In well-designed libraries, classes that are worth extending through inheritance have protected and public members in just the right places, and hide everything else.

2
  • That does make some sense. There's still the issue though when you need something very similar but with 1 or 2 things changed in the implementation (inner workings of the class). I'm also struggling to understand that if your overriding the class, aren't you depending heavily on its implementation already?
    – TheLQ
    Commented Jul 12, 2011 at 20:09
  • 8
    +1 for the benefits of loose coupling. @TheLQ, it's the interface you should be depending heavily on, not the implementation. Commented Jul 12, 2011 at 21:16
21

Everything that is not private is more-or-less supposed to exist with unchanged behaviour in every future version of the class. In fact, it can be considered part of the API, documented or not. Therefore, exposing too many details is probably causing compatibility problems later.

Regarding "final" resp. "sealed" classes, one typical case are immutable classes. Many parts of the framework depend on strings being immutable. If the String class wasn't final, it would be easy (even tempting) to create a mutable String subclass that cause all kinds of bugs in many other classes when used instead of the immutable String class.

3
  • 5
    This is the real answer. Other answers touch on important things but the real relevant bit is this: everything that’s not final and/or private is locked in place and can never be changed. Commented Jul 13, 2011 at 11:23
  • 1
    @Konrad: Until the developers decide to revamp everything and be non-backwards-compatible.
    – JAB
    Commented Jul 13, 2011 at 15:15
  • That is true, but it's worthwhile to note that's a far weaker requirement than for public members; protected members form a contract only between the class which exposes them and its immediate derived classes; by contrast, public members for contracts with all consumers on behalf of all possible present and future inherited classes.
    – supercat
    Commented Nov 18, 2012 at 14:57
14

In OO there are two ways to add functionality to existing code.

The first one is by inheritance: you take a class and derive from it. However, inheritance should be used with care. You should use public inheritance mainly when you have a isA relationship between the base and the derived class (e.g. a Rectangle is a Shape). Instead, you should avoid public inheritance for reusing an existing implementation. Basically, public inheritance is used for making sure the derived class has the same interface as the base class (or a larger one), so that you can apply the Liskov's substitution principle.

The other method to add functionality is to use delegation or composition. You write your own class that uses the existing class as an internal object, and delegates part of the implementation work to it.

I am not sure what was the idea behind all those finals in the library you are trying to use. Probably the programmers wanted to make sure you were not going to inherit a new class from their ones, because that's not the best way to extend their code.

4
  • 5
    Great point with composition vs. inheritance. Commented Jul 13, 2011 at 7:12
  • +1, but I never liked the "is a shape" example for inheritance. A rectangle and a circle could be two very different things. I like more to use, a square is a rectangle that is constrained. Rectangles can be used like Shapes.
    – tylermac
    Commented Jul 13, 2011 at 13:59
  • @tylermac: indeed. The Square/Rectangle case is actually one of the counterexamples of the LSP. Probably, I should start thinking of a more effective example.
    – knulp
    Commented Jul 13, 2011 at 14:47
  • 3
    Square/Rectangle is only a counterexample if you allow modification.
    – starblue
    Commented Jul 13, 2011 at 19:55
11

Most of the answers have it right: Classes should be sealed (final, NotOverridable, etc) when the object is not designed or intended to be extended.

However, I would not consider simply closing everything proper code design. The "O" in SOLID is for "Open-Closed Principle", stating that a class should be "closed" to modification, but "open" to extension. The idea is, you have code in an object. It works just fine. Adding functionality should not require opening that code up and making surgical changes which may break previously working behavior. Instead, one should be able to use inheritance or dependency injection to "extend" the class to do additional things.

For instance, a class "ConsoleWriter" may get text and output it to the console. It does this well. However, in a certain case you ALSO need the same output written to a file. Opening up the code of ConsoleWriter, changing its external interface to add a parameter "WriteToFile" to the main functions, and placing the additional file-writing code next to the console-writing code would be generally considered a bad thing.

Instead, you could do one of two things: you could derive from ConsoleWriter to form ConsoleAndFileWriter, and extend the Write() method to first call the base implementation, and then also write to a file. Or, you could extract an interface IWriter from ConsoleWriter, and reimplement that interface to create two new classes; a FileWriter, and a "MultiWriter" which can be given other IWriters and will "broadcast" any call made to its methods to all the given writers. Which one you choose depends on what you're going to need; simple derivation is, well, simpler, but if you have a dim foresight of eventually needing to send the message to a network client, three files, two named pipes and the console, go ahead and take the trouble to extract the interface and create the "Y-adapter"; it'll save you time on the back end.

Now, if the Write() function had never been declared virtual (or it was sealed), now you're in trouble if you don't control the source code. Sometimes even if you do. This is generally a bad position to be in, and it frustrates users of closed-source or limited-source APIs to no end when it happens. But, there are legitimate reason why Write(), or the entire class ConsoleWriter, are sealed; there may be sensitive information in a protected field (overridden in turn from a base class to provide said information). There may be insufficient validation; when you write an api, you have to assume the programmers who consume your code are no smarter or benevolent than the average "end user" of the final system; that way you're never disappointed. You either take the time to validate the hell out of anything your consumer can do to extend your API, or you lock the API down like Fort Knox, so they can only do exactly what you expect.

1
  • Good point. Inheritance can be good or bad, so it is wrong to say that every class should be sealed. It really depends on the problem at hand.
    – knulp
    Commented Jul 13, 2011 at 12:48
2

I'd think it's probably from all the people using an oo language for c programming. I'm looking at you, java. If your hammer is shaped like an object, but you want a procedural system, and/or don't really understand classes, then your project will need to be locked down.

Basically, if your going to write in an oo language, your project needs to follow the oo paradigm, which includes extension. Of course, if someone wrote a library in a different paradigm, maybe your not supposed to extend it anyway.

5
  • 8
    BTW, OO and procedural are most emphatically NOT mutually exclusive. You can't have OO without procedures. Also, composition is just as much an OO concept as extension.
    – Michael K
    Commented Jul 12, 2011 at 19:17
  • @Michael of course not. I stated that you may want a procedural system, that is, one without OO concepts. I've seen people use Java to write purely procedural code, and it doesn't work if you try and interact with it in an OO fashion. Commented Jul 12, 2011 at 19:38
  • 1
    That could be solved, if Java had first class functions. Meh.
    – Michael K
    Commented Jul 12, 2011 at 20:11
  • It's difficult to teach Java or DOTNet Languages to new students. I usually teach Procedural with Procedural Pascal or "Plain C", and later, switch to O.O. with Object Pascal or "C++". And, leave Java for later. Teaching programming with a procedura programs look like a single (singleton) object works well.
    – umlcat
    Commented Jul 13, 2011 at 15:20
  • @Michael K: In OOP a first class function is just an object with exactly one method.
    – Giorgio
    Commented Jun 12, 2013 at 20:45
1

Because they don't know any better.

The original authors are probably clinging to a misunderstanding of SOLID principles which originated in a confused and complicated C++ world.

I hope you will notice that the ruby, python, and perl worlds don't have the problems that the answers here claim to be the reason for sealing. Note that its orthogonal to dynamic typing. Access modifiers are easy to work in most (all?) languages. C++ fields can be mucked with by casting to some other type (C++ is more weak). Java and C# can use reflection. Access modifiers make things just difficult enough to prevent you from doing it unless you REALLY want to.

Sealing classes and marking any members private explicitly violates the principle that simple things should be simple and hard things should be possible. Suddenly things that should be simple, aren't.

I'd encourage you to try to understand the viewpoint of the original authors. Much of it is from an academic idea of encapsulation that has never demonstrated absolute success in the real world. I've never seen a framework or library where some developer somewhere didn't wish it worked slightly differently and didn't have good reason to change it. There are two possibilities that may have plagued the original software developers which sealed and made members private.

  1. Arrogance - they really did believe they were open for extension and closed for modification
  2. Complacence - they knew there might be other use cases but decided not to write for those use cases

I think in the corporate framework wold, #2 is probably the case. Those C++, Java and .NET frameworks have to be "done" and they have to follow certain guidelines. Those guidelines usually mean sealed types unless the type was explicitly designed as part of a type hierarchy and private members for many things which might be useful for others use.. but since they don't directly relate to that class, they aren't exposed. Extracting a new type would be too expensive to support, document, etc...

The entire idea behind access modifiers is that programmers should be protected from themselves. "C programming is bad because it lets you shoot yourself in the foot." It is not a philosophy that I agree with as a programmer.

I much prefer python's name mangling approach. You can easily (much easier than reflection) replace privates if you need. A great writeup on it is available here: http://bytebaker.com/2009/03/31/python-properties-vs-java-access-modifiers/

Ruby's private modifier is actually more like protected in C# and doesn't have a private as C# modifier. Protected is a little different. There is great explaination here: http://www.ruby-lang.org/en/documentation/ruby-from-other-languages/

Remember, your static language doesn't have to conform to the antiquated styles of the code writte in that language past.

4
  • 5
    "encapsulation has never demonstrated absolute success". I would have thought that everyone would agree that lack of encapsulation has caused a lot of trouble.
    – Joh
    Commented Jul 13, 2011 at 11:10
  • 6
    -1. Fools rush in where angels fear to tread. Celebrating the ability to change private variables shows a serious flaw in your coding style.
    – riwalk
    Commented Jul 13, 2011 at 15:19
  • 3
    Apart from being debatable and probably wrong, this posting is also quite arrogant and insulting. Nicely done. Commented Jul 13, 2011 at 20:46
  • 1
    There are two schools of thoughts whose proponents don't seem to get along well. The static typing folk want it to be easy to reason about the software, the dynamic folk want it to be easy to add functionality. Which one is better basically depends on the expected life length of the project.
    – Joh
    Commented Jul 14, 2011 at 9:17
1

EDIT: I believe classes should be designed to be open. With some restrictions, but shouldn't be closed for inheritance.

Why: "Final classes" or "sealed classes" seems to me weird. I never have to mark one of my own classes as "final" ("sealed"), because I may have to inheret that classes, later.

I have buy / download third party libraries with classes, (most visual controls), and really grateful none of those libraries used "final", even if supported by the programming language, in which they where coded, because I ended extending those classes, even if I have compiled libraries without the source code.

Sometimes, I have to deal with some ocassional "sealed" classes, and ended making a new class "wrapper" containg the given class, that has similar members.

class MyBaseWrapper {
  protected FinalClass _FinalObject;

  public virtual DoSomething()
  {
    // these method cannot be overriden,
    // its from a "final" class
    // the wrapper method can  be open and virtual:
    FinalObject.DoSomething();
  }
} // class MyBaseWrapper


class MyBaseWrapper: MyBaseWrapper {

  public override DoSomething()
  {
    // the wrapper method allows "overriding",
    // a method from a "final" class:
    DoSomethingNewBefore();
    FinalObject.DoSomething();
    DoSomethingNewAfter();
  }
} // class MyBaseWrapper

Scope clasifiers allow to "seal" some features without restricting inheritance.

When I switched from Structured Progr. to O.O.P., I started using private class members, but, after many projects, I ended using protected, and, eventually, promote those properties or methods to public, if necessarily.

P.S. I don't like "final" as keyword in C#, I rather use "sealed" like Java, or "sterile", following the inheritance metaphor. Besides, "final" is an ambiguos word used in several contexts.

2
  • -1: You stated you like open designs, but this does not answers the question, which was why classes shouldn't be designed to be open.
    – Joh
    Commented Jul 13, 2011 at 9:19
  • @Joh Sorry, I didn't explain myself well, I tried to express the opposite opinion, there shouldn't be sealed. Thanks for describe why you disagree.
    – umlcat
    Commented Jul 13, 2011 at 15:07
1

I do what I get paid for. Making a class open is extra work. I’m not doing it unless you pay me for it. Or convince my boss to pay me for it.

If you decide to want the class to be open, and we work at the same place, you take the source code and change it. Now it is your responsibility. You better make sure it’s something your boss wants you to spend time on. And it’s not open until you actually used it that way. Created subclasses that you couldn’t create while it was closed. If you open it and don’t use it, then you just wasted time and money and probably reduced the quality of the existing code.

0

It's fairly easy to design a class in such a manner that some other programmer Bobby will be able to modify it to add features in such a way that Bobby's code will be able to work with the original and modified versions interchangeably, exploiting the additional features hwen they exist.

Such a class design would also likely have the useful trait that some other programmer Jo would be able to do likewise.

Allowing Bobby and Jo to extend the functionality of the existing class, however, rather than forking a copy and extending that, would imply that code which works with the original should work will all extended versions interchangeably, and thus Bobby's client code should be expected to work with Jo's enhanced version of the class, and Jo's client code should work with Bobby's. While it's possible to design a class in such a way that such interoperability could be expected, such a design would make things somewhat more difficult not just for the class designer, but also probably for Bobby and Jo, even if such interoperability was never used.

I would also suggest as a design principle that classes should generally be designed to either be instantiated or extended, but not both, and that programs should generally avoid using references of instantiable types except as private members within those types themselves or other non-inheritable types within the source code module defining them. Languages could facilitate this distinction by allowing inheritable types to designate instantiable types which should be constructed when client code needs an object of some general-purpose type that's compatible with the inheritable one. For example, an List<T> interface could designate that code which wants an instance of something that implements List<T> should construct an ArrayList<T>. Most code that uses a List<T>--even the code that needs to create an instance of a "general-purpose" implementation--shouldn't need to care about whether it's given a reference to an ArrayList<T> or something else.

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