13 Nov

Could sealing a class be a sign of a good design

Category:UncategorizedTag: , :

I recently attended this years Øredev conference and one of the things I had the good fortune of doing was to meet-up with a long time twitter friend, Philip Laureano. One of the days me and Philip started talking about a previous discussion he had with another attendee (whom shall remain nameless since I do not know the full details of his exact opinions). Anyway, the short version is that the person suggested that classes should be sealed by default, or at least have the developer explicitly state if the class should be sealed or closed.

My immediate reaction was that this was a terrible idea and that I had been struck down too many times by sealed classes before. But then I started thinking that maybe it was not such a bad idea after all. Maybe it even was a sign of a well designed class and that more developers would be better of by sealing their classes.

Now let me inform you that I am still on the ropes about this, but I would like your thoughts on it. In fact I am hoping that the most interesting part of this post will end up being the discussion in the comment section.

So when you take a moment to think about the S.O.L.I.D principles, most specifically the Open-Closed Principle and Dependency Inversion Principle, a long with the old design principle of ‘Favor object composition over class inheritance’ then maybe it is not such a bad thing after all. Throw in interfaces into the mix and program to an interface and not an implementation, and it will enables you to create different branches if needed. If your classes can flourish while being sealed, chances are that you have some pretty nice structures code in your hands.

There probably are some legit reasons to not seal classes at time, despite the reasoning above, so I am not going to be definitive and say that is never the case. Voice your thoughts in the comments and let us see where this ends up – who knows, I might be left standing as a fool!

As always, you can find me on twitter by the name of @thecodejunkie

16 thoughts on “Could sealing a class be a sign of a good design

  1. Interesting post. I wouldn’t argue against sealing classes, I would like to hear some arguments _for_ sealing them though. My belief is that you should seal a class ONLY when it buys you something, not as a default. However there are some good uses for it, the first one that comes to mind is the string-class in the framework.

  2. In a very low-level technical perspective, the compiler can emit slightly more efficient code on a sealed type (since methods cannot be overridden), but I would say that that is just a side effect, not a reason.

    One reason *for* sealing would be that by prohibiting inheritance, you also do not need to take potential effects of inheritance into account when implementing the type. It may also give you greater freedom in refactoring your implementation (as long as the external behavior is left intact), since there are no inherited types that might depend on implementation details.

    Simply put; creating a type that is well-designed with inheritance in mind is probably more difficult that creating a well-designed sealed type.

    That said, I rarely seal types (out of pure, bad laziness). Should probably change that.

  3. @Fredrik: Could you give an example of a potential side effect of inheritance. Any class that you are thinking about sealing would of course have no protected or virtual members (they would make no sense). Having no protected or virtual members protects you against the side effects as far as I can tell. Any inheritor can still only use the public interface (and this is of course exposed anyways) but has the benefit of being able to extend to add functionality or members.

    The calls are optimized by the compiler when they are non virtual, I can’t see that they can be further optimized because the type is sealed.

  4. I’ll correct my self on the optimization part. If the sealed class is itself a sub-class of a class that has virtual members these members can be optimized on the sealed type. Of course any type is a sub class of the object class so the ToString, Equals and GetHashCode methods can be optimized in these cases. This is micro optimization though and I think it should only come into play in a really performance dependent system.

  5. @Patrik Hägne
    Speaking about a simple tree of inheritance (maybe two members) does not allow you sealing, but if your class has a method called multiple times, I’d considered it.
    In majority of my projects designed with TDD and strong DI, the majority of dependencies are interfaces, hence ‘to seal or not to seal’ does not work for it (one can intercept an interface with a proxy). If I don’t want sth do derive from a class, but I want it to be derivable in a project, I simply mark a constructor of an abstract class as internal. How about it?

  6. @Fredrik
    ‘One reason *for* sealing would be that by prohibiting inheritance, you also do not need to take potential effects of inheritance into account when implementing the type.’
    I think a good design should limit this kind of behavior, because it violates the Liskov substitution principle. I see just only a few acceptable exceptions to it: maybe the String class is the most obvious, as @Patrik said.

  7. I seal classes by default. Designing for extensibility is extremely hard so I consciously broadcast “I haven’t designed this for extensibility so you can’t extend it”. Every book I’ve read around design states that composition is preferable to inheritance; sealing a class makes that choice for the consumer and helps them fall into the pit of success.

    There are times where inheritance is useful but they seem to be quite rare.

  8. The fact that a class is designed to be inherited or not IS or SHOULD BE part of the class contract – so enforcing the need to specify if a class is sealed is probably a good idea… at least in a perfect world 🙂

    Of course there are lots of real world cases where you might need to extend a class that was not explicitly declared to be used as a base class, but in a almost perfect world the language/framework would provide explicit ways of dealing with these situations – something like the unsafe keyword in c#.

  9. IMO, sealing a class is an interesting take on the Open-Closed principle, and if you design (or refactor) your classes properly, then all the concrete classes you have should be nothing more than glue code to combine one interface dependency with another one. In essence, this forces the client to use the interfaces as extension points rather than relying on inheritance to fill in any gaps.

    On one hand, this approach might be useful in most cases, but OTOH, it might not work in cases where you rely on inheritance to extend your model. It really forces the framework designer to do an “all or nothing” approach to composition and delegation, so it’s not something that I recommend for everyone unless you know exactly what you’re doing.

  10. Inheritance has the unfortunate side effect of coupling the derived class directly to implementation details of the base class. Those details may not be public and thus could change at a different rate than the public API; the base class must be changed carefully to avoid breaking derived classed which depend on existing behaviors. This tenuous relationship is known as the brittle-base-class problem.

    If your extension point is an interface, say IUserRepository, then sealing the implementations forces developers to add or modify behavior solely in terms of the interface. This neatly encapsulates *all* implementation details and keeps them tucked away from the consuming code. There is no brittle-base-class problem because all functionality is written in terms of public APIs.

    For example, say we have UserRepository and CachingUserRepository. An inheritance relationship directly ties the two classes together:

    public class CachingUserRepository : UserRepository
    // Override methods and serve out of cache if necessary

    However, a compositional relationship can accomplish the exact same semantics:

    public sealed class CachingUserRepository : IUserRepository
    public CachingUserRepository(IUserRepository inner)
    // …

    // Implement each method in terms of the inner repository

    The key difference, though, is that the inheritance approach only allows caching of the base implementation, while composition allows caching of *any* implementation (which might itself be using the decorator pattern). We don’t explicitly choose the implementation on which we depend.

    Sealing classes which favor composition over inheritance keeps things neatly decoupled and focuses effort on the true point of abstraction, the interface.

  11. @Bryan Watts

    I think the argument @Patrik made was that there are no real difference between a sealed class, and a non-sealed class that has no virtual/abstract/protected members .. in the latter case you would gain little from inheriting the class.

    That said I think a good argument for sealing such classes is that it makes it crystal clear that the intention of said class is not be be inherited, instead use the interface as the extension point

  12. I’ve even heard the recommendation that the compiler make everything sealed by default unless a keyword instructs the compiler NOT to do so.

    In thinking through this, I have come to the decision that it is still okay for me to hate the sealed keyword.

    1. Although composition is preferred, there are certainly cases where inheritance is the right solution. A sealed class must essentially be decorated to get similar behavior, which basically just means I must write more code to achieve the same thing. PITA.

    2. Who is to say that sealed classes should be sealed? The originator of the class? That assumes this class usage has been considered in all possible scenarios for consuming applications. That, of course, is not realistic.

    Sealing a class simply seems like a defensive mechanism in which developers can protect themselves against inheriting classes, or in other words: their customers. I don’t need to “protect myself” if my class is competent to maintain its own state, and who am I to assume I understand all the scenarios in which the class will be used?

    I fail to see how sealing a class provides value to anyone.

  13. Perhaps sealed classes are just an artifact of well designed code. A developer is telling you, I’ve thought about extensibility and this is not the place to do so. This would be great for well designed applications.

    For poorly designed code you’ve just put yourself into a position where you are extremely limited where you can change behavior with out major rewrites. I rather this be opt in.

  14. I used to be against sealing classes, seeing inheritance as the only device that would let me extend a given idea.

    But after doing a lot of Composition heavy development I know infinitely prefer that style. I now have *two* Mechanisms at my disposal for expressing Intent: Inheritance and Composition.

    Sometimes it’s really handy to say, in a Domain specific way, “A is-very-much-a B”. Inheritance is the Mechanic for that Intent. I find that works very well for, say, NHibernate backed Domain objects.

    But when it comes to the Infrastructure surrounding the Domain, I flip. Composition becomes my mechanic for the intent of some given object graph, that at its core is consuming, creating, or otherwise transforming a Domain object.

    So where does ‘sealed’ come in? ‘sealed’ let’s me say in my Domain “this is a leaf node concept, and is a very concrete Idea”. In my Infrastructure it forces *myself*, even before any other developers, to think in terms of Composing my answer.

    @David Starr, I think your first point is valid. “Favoring composition over inheritance”, can lead to some wordy, and verbose code. But we’re not writing code to optimize the number of bytes we lay down on disk or to save us from typing. If it is The Right Answer, then so be it, and let’s move on. However! That is not to say we can’t figure out better answers for *expressing* the right idea.

    I like to think about what would a language a look like if something like Autofac was baked in. If the compiler, runtime, and syntax could lift some of the burden of performing Composition, what would you end up with?

    Really great comments all around though!

  15. When I design my classes I usually know when I’m designing a class to be inherited or not. For those classes that I design to be inherited I made special considerations how they are going to be inherited and used. And then I write unit-test to validate and document those considerations.
    In .NET when a method can be overridden it must be marked as virtual in its base class per design. I think the same principle should be also be applied to a class.

  16. Microsoft introduced the sealed class modifier to fix a problem they kept running into when upgrading class libraries. Microsoft would deploy an update to some base classes and lots of previously working third party applications would fail. The reason behind this was because third party application developers would inherit a Microsoft base class then modify them in unpredictable ways. People would not fault the third party applications as much as they would fault Microsoft for the errors. So, Microsoft introduced the sealed keyword and promptly sealed all their classes.

    So, a case can be made for sealing classes. However, I am ultimately against sealed classes. Sealed classes make the architecture too rigid. I would rather have the ability to modify base classes and suffer the consequences on upgrades than have this choice taken away from me.

Comments are closed.