One of the most memorable eye-opening moments in my (admittedly fractured) education was when when my software engineering professor listed the 3 fundamental tenets of OO—inheritance, encapsulation, and polymorphism—and then asked us which one was optional.
Ever since then I've been on an anti-inheritance kick, although for the last several years it's polymorphed into an anti-OO kick.
Anyway, I suspect most readers here have heard the idea that inheritance should be used in limited circumstances (or not at all), but I wonder what proportion of college students get that message today.
I agree with "anti-inheritance" as a general guideline, but its adherents have to recognize the significant practical upsides of inheritance in many situations.
Imagine you have an interface with 20+ methods, such as the List interface. And you want to create a new implementation of this interface, which is almost identical to an existing implementation, except for one tiny difference. Example, you want an CustomArrayList implementation that doesn't allow the number 666 to be populated into the list.
With inheritance, you can accomplish this in just a few lines of code. Extend the existing implementation, and override just the methods that need to be overridden.
Whereas with composition, you now need to manually implement all 20+ methods in the interface, just to make the pass-through call. This adds a tremendous amount of boilerplate to the code.
Depending on how many methods are in the interface and how invasive the changes are, this boilerplate is sometimes worth it and sometimes not worth it. Ideally, languages will add built-in support to facilitate this pattern without the associated verbosity. But until then, people will continue to use inheritance just because of how much less verbose it is.
>Whereas with composition, you now need to manually implement all 20+ methods in the interface, just to make the pass-through call. This adds a tremendous amount of boilerplate to the code.
Inheritance is not necessary for this. The traditional way of achieving this in functional languages is to use a closure to return an object/record. Instead of brittle inheritance, just pass this object to another function that returns an object modified however you want it. Objects can be combined too, achieving the same goal as multiple inheritance, but less dangerous. Simple, easy, succinct, composable.
I agree that inheritance is not necessary for this, if languages provided better built-in support for the composition pattern. I was referring to the downsides of using composition in today's "enterprise" languages like Java.
Ideally, languages will add built-in support to facilitate this pattern without the associated verbosity. But until then, people will continue to use inheritance just because of how much less verbose it is.
Well, you can somehow achieve a part of it in Python with implementing magic __getattribute__ method:
class CustomArrayList (object):
def __init__(self):
self.list = ArrayList()
def add(self, item):
if item == 666:
raise
self.list.add(item)
def __getattribute__(self, key):
"""For rest of the methods use them as they are.
"""
try:
attr = object.__getattribute__(self, key)
except AttributeError:
attr = object.__getattribute__(self.list, key)
return attr
However, most probably no one will like you if you use this method :)
I have used inheritance in a way to make accessing code more seamless and easier for development. But without inheritance, I end up with a lot of boilerplate code, imports, etc...
Doesn't duplication go against the DRY principle? I know you are saying "wrong inheritance", but some people are saying all inheritance is wrong...
My little rule of thumb is to inherit if the new implementation is a 'pure' over the inherited class.
That is, I'm not introducing any _new_ changes to the state of the class.
Code generation tends to result in bloated binaries and excessive runtime memory consumption (unless your compiler is especially clever about optimizing out the redundancies).
Exactly. I learned pretty early that inheritance relations can be trivially refactored into a delegation based design and that there's a difference between type inheritance (aka. interfaces) and implementation inheritance.
IMHO inheritance and especially deep inheritance hierarchies are almost always the wrong thing to do. I know of few exceptions and have ended up regretting breaking that rule on the few occasions I allowed that to happen. These days I just don't use it at all on principle. I'm very weary of using frameworks that depend on or over use inheritance. E.g. much of Spring looks problematic and convoluted to me. I use it but I tend to avoid the stuff that is too hard to understand. Five or more layers of classes is just insanely misguided IMHO.
Most of those layers are just working around the previous (wrong) decision to use inheritance. It's like the broken windows theory. Inheritance leads to more inheritance. It causes all sorts of problems down the line and you can't change it once you have users extending your classes.
I love that modern languages disable this. E.g. Kotlin has closed classes by default (the opposite of Java) and does not allow inheritance between data classes at all. This is great and communicates clearly that you should not be littering your code base with 'open'. This encourages good design that does not depend on scattering functionality all over the inheritance hierarchy or the endless levels of indirection. They even provide a compiler plugin to deal with spring that re-opens classes at compile team so that Spring and other legacy Java framworks can do it's dirty business of reflectively extending classes without requiring the code to be compromised.
I find it ironic that e.g. the Javascript community has worked hard to get classes and inheritance and are essentially repeating all the same mistakes people were making with Java 20 years ago by overusing inheritance. Most of 'modern' javascript to me looks like a blast from the past.
> I find it ironic that e.g. the Javascript community has worked hard to get classes and inheritance and are essentially repeating all the same mistakes people were making with Java 20 years ago by overusing inheritance. Most of 'modern' javascript to me looks like a blast from the past.
IME JS classes are less about inheritance (which remains compositional anyway, though classes do make these easier what with not having to maintain the prototype chain or hand-roll super calls) and more about the various syntactical convenience of class declarations, namely that you don't have to deal with the Function / Function.prototype crap, the hand-rolling of `new` mandates (a class ctor is always called through new), the requirement of explicit strict mode (class declarations are always strict), method shorthands (they do exist on object literals, they don't exist when adding to a prototype property by property), …
I just don’t get the class/prototype approach, honestly. It really seems to me that you get almost all of the benefits and more by just returning an object from a closure, which has the extra benefits of encapsulation and using only simple language features that a JS user pretty much has to use at some point:
JavaScript classes are pure syntactic sugar for object constructor functions. If the syntactic sugar does not make your code cleaner then don’t use it.
Methods with arrow syntax offer implicit function binding and TypeScript adds abstract classes as well as abstract, private, protected and readonly fields. New language features aren’t supposed to replace simple logic that has always worked. They are there to provide new options for programming patterns.
In my experience, It's pretty rare to see inheritance used in anger in modern JavaScript. Frameworks may have you extend base classes as their primary API, but they don't tend to encourage that you implement your own deep inheritance tree.
I think the last time I personally abused inheritance was when using Backbone.js more than 5 years ago, and that experience was what finally tipped me over the edge to start learning more about functional programming.
> I learned pretty early that inheritance relations can be trivially refactored into a delegation based design
You can go much farther and discover that OO state mutations across multiple methods can be replaced with higher order functions that let you keep state mutations local, within context of each function. Suddenly OO becomes this weird very unproductive and error prone way of writing and reading code. An entire OO concept is just bad, always was, nothing can fix it.
At least with Javascript higher order programming was always possible.
That is one subjective question, OO is not that well defined beyond being oriented around objects. Heck, the Treaty of Orlando from 1988 says a lot more about the open definition of OO.
Sandi Metz does a far better job explaining the proper use of OOP in this video: [0]
I highly encourage anyone with doubts about OOP to actually complete that Kata.
In short, inheritance should be confined to removing conditionals in problems with shallow-wide [1] inheritance graphs, where things truly "is-a" thing, not "have-a" thing [2]
Real life examples I've encountered:
* buttons in a toolbar implementing a command interface
* database drivers extending a base implementation
* web-server endpoints extending a base implementation
Some of the most complex software in the world is currently modeled using OOP. If one is ever in doubt, look into creating plugins for IntelliJ. The whole program is a massive exercise in OOP modelling, and it works pretty well (if not a little slow some times) [3]
- objects that have conversations with other objects through messages to achieve a cohesive purpose
inheritence is essentially a method of sharing code without going through a message, so not really OO, you can use any sharing mechanisim you like to make cohesive objects.
that's too vague. Most OO PLs don't actually use messages in any way under the hood (except maaaybe ruby? I hear things but haven't peeked at the MRI code) and use messages as a "conceptual framework" but let's be honest - is calling a method owned by object A.do_something(B::target_type, C::parameter_type)... What sort of a message is that?
moreover, plenty of FPs (I'm looking at you erlang) use message passing all over the place, as, arguably the most important feature of the language, with things that vaguely look like objects (gen_servers) but I would not really call erlang an OO language, except maybe to joke to an OO programmer that an FP is truer to the original intent.
Inheritance / encapsulation / polymorphisim are kind of vague too.
actually, Alan Kay said Erlang was most like his concept of OO.
Ruby has some nice hooks for when an object doesn't have a method, at which point you can dynamically resolve it. Much advice in the OO world and its principles are really just to do with message routing, and a lot of Design Patterns are about how to do it when the type system fights you
but even if you are stuck with your C++/Java/C# type languages, then I think it is still useful. I've often seen in the first 10-15 minutes with "traditional" advice that set off down a path of design that ultimately causes a lot of struggle between OO and the typing system, most often because of naming things too early warping the mental rather than recognizing traits of interactions.
I think Ruby qualifies as true OO (in the Alan Kay sense) because messages are first class entities that can be explicitly manipulated. This makes it trivial to implement lots of OO patterns that require a significant amount of boilerplate in other languages. For example, a proxy.
Erlang makes messages an even more central element, as the whole language is built around the concept of inboxes and asynchronous message passing (synchronous message passing being a special case).
Better type systems are in vogue now, for really good reasons. But I wish we don't ignore lots of good ideas from dynamic languages like Erlang or Mozart/Oz. Every time I open CTM or SICP I find great insights I had already forgotten.
Interfaces or bases classes where all methods are pure or empty are often used to implement messages in awkward ways in mainstream OOP languages as the languages lack proper union types.
Polymorphism, no; I mean, how can we substitute different kinds of objects into the same situations dynamically without polymorphism. OOP is almost synonymous with polymorphism.
But we do not have to bundle code and data representation together; there are other approaches to OOP, like generic functions.
I don't think we can dispense with encapsulation and still have OO, though. Sure you can program without it. You can even make good programs without it. You may even be able to make better programs without it :-) However, it won't be OO.
The idea behind OO is to move away from data-centric programming. If I have a system dealing with geometry, I can have a Point object that has an x and y coordinate. However, once I have a Point, I should be trying to make my system so that I no longer access x and y directly. X and y are "encapsulated" in the Point. Collaborators should use the functionality on Point to do what they need to do, rather than trying to pry Point apart and get at the data.
Of course, barely anybody writes "OO" code like that, even if they espouse to follow "encapsulation".
I would argue as well that generic functions do not, in themselves offer an OOP environment. You need one more thing: partially applied functions. If you can create a partially applied function, you can bake in some program state and pass it around, offering functionality on that program state. However, nobody on the outside can access that state. This is the encapsulation necessary for OO.
Point = {x, y} is a strange example. What exactly is being encapsulated? Any Point structure I've ever seen has accessors for x and y, and no other internal data.
In general, I don't see the need for encapsulation in order to qualify for object status. The classic counterexample is CLOS, which doesn't have it -- and the debate over that has been raging for decades [1].
The best basis for the essence of object-orientedness that I ever read was suggested in an article that focused on identity. (I don't remember where I saw this -- possibly from Kent Pitman?) If you could guarantee that some value is exactly the same as a value that you had earlier, and not just a mostly-copy but identical in every respect, then you had an object. Everything else falls out of that.
This is a great question. I thought that myself, so I tried to make a system where I couldn't access x and y. It turns out it's completely reasonable to do so. If you want to move a point, you add a translate method. If you want to compare points you can. You can add and subtract points. If you want to align a group of points on the X access, you can write a method on point to do it. There were very few times when I wanted to actually access x and y rather than act on it.
I do agree, though, nobody actually writes code like that. Does that mean that I think nobody actually does OO? I hadn't actually thought it through that far :-) I think this is why the concept is so vague (your excellent example being a great indicator of that). C++ is often referred to as "C with Objects" and I sometimes think we've gone so far as to say anything with a function that operates on a structure is "Object Oriented". If so, I think we've lost our path a little bit.
You can keep adding mathematical transformations to the Point object all day long (though at some point you'll realize the mathematical operations don't belong there, and the concept of "point" isn't abstract enough - the study of right abstractions here is called "algebra"). But there comes a moment when you want to draw that point on the screen, and suddenly there's a good chance you'll have to access individual components, because the API of your drawing device doesn't understand your particular Point class.
Arguably this is still within purview of "Point" abstraction, but:
> If you want to align a group of points on the X access, you can write a method on point to do it.
This calls for a static method really, otherwise the abstraction becomes subtly wrong. And with static methods you quickly realize that you're using the containing class as just a namespace.
CLOS actually resolves this nicely by having methods being their own things separate from classes. As a related side effect, the typical C++/Java case of "object methods" isn't a special thing in CLOS, it's just a method that happens to be polymorphic only on its first argument. The abstraction around methods seems cleaner, and the class in CLOS is just responsible for encapsulation and inheritance, not for namespacing methods.
It sounds like all you're really doing is taking any function that does want access to X and Y, and shoving it into the Point object where it has that access. Or to put it another way, you're defining any function that wants access as "part of the concept of point". You can indeed accommodate any code with this transform, but is it the right thing to do? Do you gain anything by bloating up 'point' with transformations and comparisons and alignment operators and so on?
Of course you gain something. By encapsulating all methods that operate on X and Y in a single place, Point (where X and Y are defined), it is now _much_ easier to change X and Y. It's all right there in one file.
Point {x, y} may not be the best example, but you can certainly see how this can be beneficial should X or Y become something even slightly more complex. And this is the purpose of encapsulation - to make a system easier to understand and therefore easier to change.
The Point could internally be represented in a polar coordinate system.
I kind of agree that encapsulation isn't needed for object status. The caveat only being that it would be a weird object system where you could be sure there was no encapsulation.
I mean, OK, in some languages you know that. But does it change anything important?
Even if a point uses polar coordinates internally, we can have syntax "p.x" which gets the Cartesian x.
Information hiding is an important concept in low-level-ish systems programming languages in which p.x implies a piece of machine code that offsets 24 bytes from the base address of p, and fetches a four byte integer.
This Point class is a poor example because it does not have anything that is not part of its interface, and, as presented here, does not have any constraints on it.
The intended purpose of encapsulation is to try and preserve integrity of a system by restricting the ways a programmer might put it into an inconsistent state. Knowing that certain constraints are in place simplifies the problem of understanding and debugging a system.
Python is build entirely around the idea that encapsulation is mostly useless. "We're all consenting adults" is the general philosophy. As long as there's a convention as to what properties are "public" and which are not, a motivated user of a class should be able to do what they want with it.
There's no point in putting a "private/public/protected" security model in a programming language. It isn't a security boundary.
This vastly simplified the design of OO in Python, and it makes testing far far easier. There's no need to go through the dependency injection acrobatics in order to change one method for testing or proxying.
(There technically is a way to munge a property name in Python but its use is discouraged).
I wish there was a "do not touch unless you know what you're doing" field. Private fields and members are a nuisance if you need to do some unsupported thing with a library. On the other hand, there are often utility functions in an object that are absolutely only ever meant to be used by that object itself, and not to be called from the outside.
There's no point in putting a "private/public/protected" security model in a programming language. It isn't a security boundary.
It’s about intention. If I mark a property or method as private, it means that I can get rid of it, rename it or change the expected behavior without considering it a breaking chance.
Yeah, and Python has some standard nomenclature for marking a property as protected and a little bit of behavior for helping mark it as private. You do not need encapsulation for that.
And there is nothing about the Pythom runtime stopping someone from using it anyway.
With a statically typed language, you really have to go out of your way to do it like using reflection and if your code breaks on an update you get what you deserve.
Note that "encapsulation" refers to two completely different things, one of them by mistaken application of the word:
- combination of methods and data
- information hiding
Information hiding is needed for OOP, but it can be hidden in the implementation. The fact that if I have a point object, I can access point.x is not an example of information hiding being violated. Firstly, it doesn't interfere with polymorphism: the same point.x code can access the x coordinate of points of different types. Secondly, the syntax point.x can actually be a method call which invokes an accessor under the hood, so the object doesn't actually have to have a slot x. There is no semantics or "moral" difference between having a pair of methods point.getx() and point.setx(<number>), and just a point.x. Lastly, sometimes it is perfectly acceptable in an object design to have trivial properties.
Information hiding is more important in languages like C++ because an access like "point.x" compiles to code which assumes that x is at a particular offset in that object, and is of a particular type and so forth. It's less important in a dynamic language in which we can cafely ask for the x slot of any object.
Encapsulation of methods and data isn't required in an OOP system; OOP systems like CLOS work without it just fine.
I'm pretty sure Perl's Native OO has no encapsulation, at least without custom modules or weird wranglings like inside-out objects. It doesn't mean we must peek inside, but the option is there. It is completely functional, but as prone to misuse as the rest of Perl.
One should note that in Perl 6, object encapsulation is complete. You can only directly access attributes inside a class, and only through (usually autogenerated) methods outside of it.
> To me this is the most important part. I like to have private or public, protected is not needed.
OO's encapsulation is about bundling data and operation, not about information hiding, and as kazinator noted generic functions (as in CLOS) prove you can have OO just fine without.
As to information hiding, there are many fine OO languages which don't have it either (Python probably being the prime example there, but I'd say it also applies to languages like Ruby where you can trivially override existing ACL) so it's clearly not necessary either, let alone "the most important part". Especially as conversely many non-OO languages do have access control, hinting that the two are quite likely orthogonal.
Information hiding is useful for engineering and resilience, not for object orientation.
As to private / protected / public, I'd argue it's private which is the least useful (since we've already ascertained none is actually needed): it's literally just hiding stuff from yourself. I think removing that level of ACLs is one of the few good idea Go had.
ACLs also seem to mix concerns. The "private" concept is essentially making life difficult for yourself, but having a built-in mechanism for separating public interface from internal code, that's a bit more than just convention, is useful. For instance, in Common Lisp there's no language-enforced ACL, but when building modules (that may or may not involve a class, or many classes), I assign a package for each module and make it export only the symbols that represent its public interface. In code using that module, this becomes a syntactic difference in a way you refer to these symbols - module:foo means "foo exported from package 'module'", while module::foo means "foo internal to package module". That simple isolation, completely overridable with an extra colon character, is enough to clearly communicate what is and what isn't a part of an abstraction.
Is there a warning when someone overrides that isolation? In C# you can also access private members through reflection and I have seen people use that a lot instead of bothering to understand why the original developer decided to make the variable private.
No, there isn't, because "we're all adults here". But you have to type :: instead of : to do this, which usually tells you something, and you don't have to go through reflection to do this, so such overrides don't cause performance issues. Common Lisp doesn't prevent you from accessing anything.
I wish we he had more adults instead of people fresh from college who think that best practices and things to avoid are obsolete and don’t apply to them :)
That's the unfortunate nature of exponential growth. If the number of programmers doubles, say, every 3 years, that means at any point in time, half of the workforce has less than 3 years of experience.
I mean, you can do pure functions. And those pure functions can operate on structs. And, once you're there, it's probably good hygiene to keep all the functions operating on a given struct in one file.
Congrats, you're back to 'encapsulation'! Encapsulation is actually the only important tool in that particular toolkit. We're mortals, we need coping tools to keep programs manageable.
That kind of "encapsulation" is simply storing code together with the data it belongs to (good practice) and possibly namespacing it together. It's not what they mean by "encapsulation" in OO courses, though it's arguably the only benefits "encapsulation" give you.
Would you be interested in doing a podcast together about programming? I'm a radical javascript programmer (search ONNEMI-4211). You seem smart. I've never made a podcast before.
I like to use classes almost exclusively as a way to couple an object with methods directly related to it. Basically structs with methods. Useful enough for me.
I've noticed OO education has a habit of appropriating every good idea from software engineering as if it was a distinctive feature of OO approach. I think reading SICP opened my eyes to that for the first time. As it turns out, you can do abstraction without objects just fine.
What OO education? I find that people just fling around random tenants when they talk about OO, even worse when they talk about FP. Even crazier is when some darling FP technique like entity component systems specifically use a synonym for object (entity) to avoid being called OO.
Entity Component Systems is an FP technique now? That's new to me.
ECS is one of the bigger confusion of ideas out there. The articles about it tend to mix together at least three of the following, each in different proportions, and without realizing it: composition over inheritance, relational data model and SoA/data-local design.
IMHO, the relational data model is the more fundamental part of ECS. Struct-of-arrays is a bit lower-level of a view on the same (with more emphasis on performance and less on architecture), and composition-over-inheritance shows up elsewhere (although the mechanism for composition is _not_ at the language level, where most people expect it to be).
Once you have the relational model, the Systems part of an ECS architecture almost defines itself as you figure out how to work with your data.
But entities really are not objects, because they neither send nor receive messages, and because they have no associated data (and therefore no encapsulation). In fact, the component-system relationship encourages you to break encapsulation.
OO is just programming with objects, regardless of how you coordinate them. Encapsulation is a multi dimensional thing, ECS just favors one form of encapsulation over another.
I'm not a fan of that as a definition: To me, object-orientation means modelling computation as communication between stateful objects. An object-oriented programming language has features that facilitate such a design. Inheritance would be such a feature, but it could easily be substituted with other ways of subtyping and code reuse.
Absolutely. Tight coupling between data set instance and function that manipulates this data seems to be the current definition of object-orientation (as opposed to something more philosophical like "living entities communicating together", which is a bit vague IMHO).
Rather than having one big memory chunk, and one big function chunk (like the old DATA DIVISION and PROCEDURE DIVISION of cobol), you divide thoses into smaller data+procedure bits that idealy map to existing concepts from your domain.
That seems like a much more effective way of seeing the thing. Although once you start adding parallelism and asynchronous communication, you do probably end-up in something closer to the Kay definition.
Hierarchy - a single ontology - is one of the problems with OO. Ontologies - note the plural - are useful, but choosing just one ontology privileges some axis of abstraction over others, but there are different lenses you can put on things that may make you want to classify by different criteria.
For example, you might have a bunch of things, some of which: (a) can be rendered in some display - could be console, HTML, print output, screen, don't care; (b) can be serialized to a variety of media; (c) have some common configuration, that interacts with some configuration management system; etc.
If you try and shoe-horn these commonalities into a hierarchy, you end up with classes that advertise being able to do too much and need to have "not implemented" error conditions, breaking the Liskov substitution principle.
Interfaces are a way of breaking out. They're additive, rather than hierarchical, from the implementing class perspective.
The other big problem (a bigger problem IMO) with inheritance is coupling. I don't think there's any closer coupling between two pieces of code in an OOP system than inheritance. Changes in a base class can radically alter behaviour in all descendants. Depending on the implementation of overriding, descendant behaviour can be accidentally hijacked with no compile time or run time warning merely by updating a dependency. The API by which base class and descendant class interact is bidirectional, with the flavours of both library (you call them) and framework (we'll call you), and intermediate internal state when e.g. overridden protected methods are being called is usually sorely undocumented and liable to change.
> Interfaces are a way of breaking out. They're additive, rather than hierarchical, from the implementing class perspective.
Java (and .Net by copying) choose to make interfaces additive and superclasses combinatorial. It's a design decision and in no way intrinsic. Other languages have different choices.
Use composition for reuse and interfaces for polymorphism. These can do the same as inheritance and more, and they do it more cleanly. Reuse and polymorphism are orthogonal concerns that inheritance tries to fuse awkwardly together.
Try Go if you’d like a familiar language without the inheritance that you can pick up in an afternoon.
It's not compulsory even in a language that supports it, I'm working on a medium sized C# project that has minimal use of inheritance and only then to fit in with the requirements of a library feature.
It's even harder to define (which might be why OOP is so popular --- it's easy to get dogmatic about "OOP is X", and the cargo-culters will pick it up and spread it like religion), but I'd say "be pragmatic and let it come naturally". Systems tend to naturally form OO-ish things even if they're not written in an OO language; the Linux kernel is one example. I think of OO as just another layer of organisation beyond functions and structures, arising from the desire to group related data and code together.
It's not natural. It depends on your previous experience. Preferring C-style semi-OO stateful programming and OO merely shows what influenced your thinking.
In my opinion we need look at bringing the relational model into code, not just data. OOP is too awkward for complex relationships. Or, at least making code more relational-friendly to avoid the O-R-Impedance-Mismatch: https://en.wikipedia.org/wiki/Object-relational_impedance_mi...
There's nothing particularly wrong with OO as a programming paradigm when used for the right purposes. It was originally designed for simulating real world systems and still works great for that.
This very article ends with '"¿Por qué no los dos?", which means "Why not both?"'. I agree, use both, but don't get carried away with either. If you exclusively use composition you're basically reinventing inheritance anyway, with more steps.
These are the articles where someone saw that the "cool kids" don't like/use inheritance, and so adopted the position to try and be "cool".
Don't try to be cool, try to understand. Inheritance doesn't say, "Create a complex inheritance tree of interdependent classes". That's the strawman. Remember that.
> when my software engineering professor listed the 3 fundamental tenets of OO—inheritance, encapsulation, and polymorphism—and then asked us which one was optional.
None of these is a fundamental tenet. See the article linked here [1]. The fundamental tenets are the five "SOLID" [2] principles.
Object-oriented design is not well-taught in general. The impression most people normally have of it is quite different from what it actually is.
I'm not a huge fan of SOLID. It's treated as a pillar of software development, but I feel like some of the advice sounds good or is right but isn't terribly useful(SL), one was so terrible it was rebranded(O), and some are a useful tool more than a design goal(ID).
Single responsibility principle - something we can all agree on but doesn't help you decide between two non hideous designs
Open/Closed Principle - the original advice was so terrible that we ignore it and pretend it means something else that has very little to do with open/closed
Liskov - technically true but very few applications are bad or incorrect because of this violation(especially with the recent move away from inheritance)
Interface Segregation - Sure it's useful to split up interfaces sometimes.
Dependency Injection - Sometimes useful for testing or IOC.
This seems very much like a semantic debate. Either OOP is characterized by inheritance—which is consistent with the notoriously “OOP” languages like Java, C#, and C++<11 and probably the overwhelming majority of intro-to-OOP literature—or you exclude inheritance as a hallmark and make it so general as to be indistinguishable from many other paradigms, many of whom came before it. If the latter, why bother fighting the semantic debate at all? If OOP really is that unremarkable, why advocate for it as a meaningful label instead of advocating for SOLID generally?
Can't the SOLID principles apply equally well to functional programming by just replacing "class" with function, object, or type (depending on the principle)?
Single Responsibility is achieved by composition
Open/Closed is probably the one that fits least with FP, but it seems like FP achieves the same result by replacing both extension and modification with composition.
Liskov Substitution can be replaced by a subtyping or duck typing.
Interface Segregation seems like its more about design than programming. However, to the degree that it applies to programming, composition seems like it actually better meets this principle than inheritance.
In terms of Dependency Inversion: If anything, it seems like one of the biggest problems with FP is that it makes it easy to take this principle too far by making things too abstract (e.g. the category theory aspects of languages like Haskel seem like they could easily be abused).
I'm not super experienced with OO so am I maybe misinterpreting these principles?
In this issue, proper understanding is muddled by "OOP" languages that have a static type system which uses inheritance as a gatekeeper to polymorphism.
So that is to say, if you want to dynamically substitute objects of class D1 and D2 in the same places in the program, these classes have to inherit some base B. Then D1 and D2 objects can work in places that require a B. For this reason, sometimes the B is a dummy that contains no implementation at all. In recognition of such dummy bases, some languages have "interfaces" and "interface inheritance" to formalize the concept.
In true OOP, there is no such requirement for D1 and D2 to be a new kind of B. Objects are substitutable into a situation if they simply respond to all the required methods. Inheritance is then used optionally just as a trick for implementation reuse. That is to say, we not only want D1 and D2 to be substitutable, but since they are similar, we would like to have a large fraction of their code in common, not to mention the instance data which that code refers to.
The problem is that inheritance is like a software fork. There is this basic implementation of something B, and then these derivations D1 and D2 are like branches: they use the B code, but they replace some of it with their own versions of functions. Those functions initially correctly mimic what their counterparts in B are doing. But if B is subsequently maintained, that can fall apart. B functions can be altered, while their counterparts in D1 and D2 remain the same, not incorporating the new logic. This kind of inheritance situation works best when it's all maintained by one developer, or team, as a cohesive whole. Or else, the base has to be very well designed for inheritance and maintained very carefully.
Inheritance has issues that interact with mutability. The circle-ellipse problem goes away if objects are immutable. In the Common Lisp object system there is an analogy there in that numbers of type rational are subclassed by inheritance into ratio and integer. An integer isn't a new kind of rational; it's a restricted kidn of one, just like a circle is a restricted ellipse. Since we cannot mutate an integer (it is what it is), inheritance models things just fine here; it is quite suitable for partitioning a mathematical set. In terms of substitutability, it is perefect: certainly wherever rationals are allowed, we should be able to use an integer or a ratio, just like where ellipses are allowed, we should be able to use a circle. If we ask an integer what its denominator is, we get 1.
That word "true" is doing a lot of work for you here. There are other definitions of what true OOP is, at least one of which (SOLID) appears in the comments on this article. By calling yours "true", you avoid having to do anything to defend your choice of definition. But if someone does not already buy your definition, calling yours the "true" one is completely unpersuasive.
> There is this basic implementation of something B, and then these derivations D1 and D2 are like branches: they use the B code, but they replace some of it with their own versions of functions. Those functions initially correctly mimic what their counterparts in B are doing. But if B is subsequently maintained, that can fall apart. B functions can be altered, while their counterparts in D1 and D2 remain the same, not incorporating the new logic.
True. But there is no software technique that is proof against bad maintenance. So your argument here is that this technique faces the same problem as every other technique. That's not much of an argument.
If D1 and D2 do not share any base B, then there is no such thing as a change in B done for the sake of D1 unintentionally breaking D2. We can make any change we want in D1, without affecting D2.
The tradeoff is that if D1 and D2 are similar, we wastefully duplicate code, and have future problems like fixing a bug in one, but not the other, and deal with nearly identical but subtly diverging versions of almost-the-same thing.
This is the TXR Lisp interactive listener of TXR 215.
Quit with :quit or Ctrl-D on empty line. Ctrl-X ? for cheatsheet.
1> (defstruct fixed-list nil
a b c
(:method car (me) me.a)
(:method cdr (me) (list me.b me.c)))
#<struct-type fixed-list>
2> (new fixed-list a 1 b 2 c 3)
#S(fixed-list a 1 b 2 c 3)
3> (car *2)
1
4> (cdr *2)
(2 3)
5> [mapcar square *2]
(1 4 9)
6> (cons 0 *2)
(0 . #S(fixed-list a 1 b 2 c 3))
7> [mapcar square (cons 0 *2)]
(0 1 4 9)
>In this issue, proper understanding is muddled by "OOP" languages that have a static type system which uses inheritance as a gatekeeper to polymorphism.
I am trying to think of a commonly used language that exclusively uses inheritance to implement polymorphism. (Unless you consider Java’s interfaces to be inheritance when they are the language’s alternative to using inheritance.)
> (Unless you consider Java’s interfaces to be inheritance when they are the language’s alternative to using inheritance.)
Having come to Java from C++, I always considered Java's interfaces to be a syntactic sugar on top of inheritance. In C++, there is (or was, back then) no meaningful difference between "interface" inheritance and "class inheritance" - and why would there be, if it boils down to stringing up vtables together?
There are, of course, disadvantages to composition. It requires more code than inheritance, as we saw, Ortiz said. It is often more difficult to read the code using composition than it is to read code using inheritance.
I think this is the main disadvantage, at least in languages like C++ and Java; if you want to be dogmatic about the OOP-ism of encapsulation and that stuff, all those "methods that just call another method on the composed sub-object" create lots of bloat that a compiler may or may not be smart enough to inline, but a human certainly would have to deal with --- and that adds overhead when debugging and otherwise maintaining the code. Speaking as someone who really hates such "useless" code, composition (especially sometimes of the nested variety) can get annoying really really quickly ---needing to "thread the call" to the right place through many composed objects just feels more like busywork than accomplishing anything functionally useful. If you inherit, you automatically get the union of the methods (and only when there's name collisions do you need to disambiguate), but inherit too deeply or widely and it gets more difficult to find where the code is.
Of course, none of this is relevant at all to the code I normally write using C, where there is no inheritance and everything is composition, and not being an "OOP purist", I'm perfectly happy with BAR_dosomething(&foo.bar);
>needing to "thread the call" to the right place through many composed objects just feels more like busywork than accomplishing anything functionally useful
Doing this is a pretty strong code smell. If you have chains of object references that need to know about the internal structure of other objects, perhaps think about the Law of Demeter? https://en.m.wikipedia.org/wiki/Law_of_Demeter
That's exactly the problem with being dogmatic about OO --- even the page you linked to mentions it:
Although the LoD increases the adaptiveness of a software system, it may result in having to write many wrapper methods to propagate calls to components; in some cases, this can add noticeable time and space overhead
Suppose foo() inside class X needs an extra parameter or similar, but that parameter actually needs to come from A which contains an instance of B which contains a C which contains a ... ... which contains the X. Instead of changing only foo() and the b.c....foo() in A, being dogmatic means having to change all the do-nothing wrapper methods on that call chain.
If anything, I'd say wrappers are the real code smell. You can probably tell I'm not a fan of OO; I've encountered the aforementioned situation countless times, where the real purposeful change takes a tiny fraction of the time compared to going through all that other do-nothing code and changing it too. The mind-numbing depth of indirection and wasteful bureaucracy is excruciating.
Can you give a brief example of the situation you are describing? Or a link to an article on this?
I am really interested in improving my coding using functional programming, composition, etc... but so far, I haven't been able to understand the benefits in my current circumstances. (I've done a lot of reading already) I am hoping I will run into one, so I can see clearly why it's worth the effort to implement. (ie, implement something new when upgrading/replacing some old OOP code...)
In my example x was a contained object, and y, z, w were member variables or methods on that object. A shorthand for wrappers was exactly what I intended.
Here's the crucial part: in Eiffel, methods can have preconditions and postconditions, and overridden methods in subclasses must honour those conditions in specific ways:
> The principle of subcontracting follows from these observations: a redefined version of r may keep or weaken the precondition; it may keep or strengthen the postcondition. Strengthening the precondition, or weakening the postcondition, would be a case of “dishonest subcontracting” and could lead to disaster. The Eiffel language rules for assertion redefinition support the principle of subcontracting.
In a sense, languages which support inheritance might not actually be doing it correctly if they don't support these rules of 'subcontracting'.
It would be nice if languages provided mechanisms to support composition without the bureaucracy.
Why should we write a ton of boilerplate just to forward a message to a contained object? Why can't we just have:
type MyType {
member containedObject SomeOtherType
forwardTo(containedObject) MethodA, MethodB as SomeOtherName, MethodC
method SomeNewMethod() {some implementation}
}
Then the compiler can optimize the path to call the delegated object directly rather than through a secondary call via this object, since it knows what our intent is.
Inheritance drives me nuts. I'm trying to read through a class and keep it all in my head, then I see a call that isn't defined, so I have to look at the base class. Now I have two classes I'm trying to keep in my head. But oh wait, it inherits from something else. Then I finally find the implementation and ive forgotten the context it was getting invoked in. I'd much rather just have an explicit import providing code reuse.
An IDE would help with this, at least for a strongly typed language ('go to definition'). For something like Perl, good luck!
My other complaint is when the base class is updated independently of the child class. Suddenly your code is breaking because a new field is being set in the original function but not your override. For that reason I tend to inherit to extend only, as much as possible. I prefer to write a wrapper if it's a neater solution.
Re: I'd much rather just have an explicit import providing code reuse.
I tend to agree. We need something closer to set theory to manage variations-on-a-theme rather than hierarchies: a buffet where one can pick and choose the parts as needed, and referencing groups of parts using group names to save time and provide abstraction/factoring. It's why I mentioned we need to get some inspiration from relational, which is heavily influenced by set theory.
Aaron Patterson (aka tenderlove), Ruby and Ruby on Rails contributor, has a good post titled "YAGNI methods are killing me" where he points out the problems when inheriting from builtin classes such as String or Hash.
Side note: I always feel pretty weirded out visiting a website called tenderlovemaking.com. It feels gross and creepy and not something I want when I'm doing my professional job in a professional environment.
Is it just me, or does anyone else feel like this tenderlove/tenderlovemaking thing is a bit uncomfortable-making and maybe not ok?
Python is scary sometimes. If it looks like a duck and quacks like a duck and isinstance(duck), it might be an alien masquerading as a duck using metaclasses to accomplish these feats. But with this craziness comes the ability to make a transparent runtime proxy to just about anything in Python, and do so recursively e.g. to track all uses of an object hierarchy.
I think I'm confused. If you want to delegate _everything_, why not just actually use inheritance? Or even the object itself. What's an example of when you would want an ObjectProxy?
Definitely agree about metaclass magic being a blessing and a curse. That superdelegate library was fun to write and uses a metaclass to make the API pleasant.
If you don't know what you're going to need to wrap (heck, it might be something with methods implemented with native code), but you want to override or intercept a certain subset of calls, then composition/delegation is really the only thing you can do. It's significantly more stable than trying to find the class of an arbitrary object at runtime, create a subclass of it, and try to copy the state of the original object.
For instance, I've seen instrumentation libraries monkey-patch database cursors with wrappers around those cursors, so the library can detect usage of the cursor without changing its behavior.
And I'm using wrapt in a project (which I hope to open source) where, by recursively wrapping the return value of every __getitem__ and __getattr__ with a properly-bookkept proxy object, you can see what subgraph of a complicated object hierarchy was actually used by a function call (say, the rendering of a Django or Jinja template). Possibilities of such a transformation are endless!
You don't have to sacrifice type safety, that's a python problem. Other languages (eg: Groovy) accomplish the same thing in a type-safe way (see @Delegate annotation [1]).
This is missing the biggest difference between the two, at least according to me.
In composition (and its on-steroid cousin, delegation), the component/delegate can't "call back" into the aggregating object.
Of course, this additional power can be misused, but it's what should inform the choice between inheritance and composition. Use the least powerful thing that will work — so composition if you don't need to call back into the aggregating object.
> In composition (and its on-steroid cousin, delegation), the component/delegate can't "call back" into the aggregating object.
It can if you design it to take a pointer to the aggregating object. Pass this or self or whatever keyword your language uses to the component/delegate and then it can call back to the aggregating object through an explicit pointer. This is more explicit than calling through the object's vtable, which makes the code easier to read IMO, e.g., foo.bar() instead of bar().
I can't think offhand of an example to illustrate when you would like this "call back" functionality in a way that you can't anticipate when you design the component object. Do you have a killer-app for inheritance?
Any time the component needs to call a method or access data from the aggregate. The kind of thing that happens all the time but the pattern is abstract so there's no killer example.
e.g. the component manages some kind of list, and the content of this list has to be kept in sync with some field in the aggregate.
No example is going to be perfect, because if you have full control over both classes, well then you can always change their interface in a way that it isn't required anymore. One need to consider the reason the thing should be a component in the first place (maybe the pattern is reused in multiple places) and other external and/or historical constraints on the code.
Maybe a plug-in system? It can dynamically register itself with the parent object so that the plug-in is discovered by the system. All the plug-in author would need to do is inherit from the base plug-in class.
What advantages do proponents of inheritance say that it brings to the table? As far as I can tell, the code reuse aspect can be replaced by composition, substitutability can be replaced by subtyping, and encapsulation can be replaced by immutability and functional purity.
On the other hand, the problem that I see with inheritance is that it ties code reuse and subtyping together. I don't see any reason why the two should necessarily be related.
The mental model of inheritance is easy to grasp. I think inheritance has its place as long as things don’t get too crazy. For 90% of projects out there, inheritance is a simple model to understand and can be used just fine. It’s when “architects” get overzealous and try to force everything into a hierarchy where things get bad.
Go makes composition less powerful than it could be by slicing structs, which means a lot of hoop jumping to get template methods (where blanks are filled in downstream).
I've been playing around with first class environments [0] lately in g-fu [1], which allow convenient and flexible composition of data and behavior using only what was already there.
I have a feeling Lua might be playing similar tricks with its tables, but I lack enough experience to judge.
The issue is that you can't interface methods from pointers to a contained struct, since it has no idea where it is or how it got there. Instead a pointer to the full struct needs to be threaded all the way down to where the overridden function is called.
I sort of understand why, but this is a major missing piece that makes it very frustrating to add anything but trivial default implementations to interfaces in Go from my experience.
It argues that there are three different kinds of inheritance and in OO you often mix them up into one hierachy - which may work in some situations, but in some it just can't. Personally, I've experienced this kind of conflict often enough.
Java esque OO is often tauted as a way to reuse code, but incredibly does the opposite by dictating a tree design and then actively disallowing reuse between arbitrary leaves of the tree
I think the worse part is this plague has descended into other languages, if you want different behaviour for different things (but common in someway) just accept a function into your function & invoke it, tada more efficient dependency injection
I find that in a lot of critiques of programming methods, the proposed solutions often contain the implicit assumption that the 'code' is a small to medium codebase with a single developer.
If a sole developer is the only one working on it, of course any approach can contain the complexity.
But, if in the language you are using, the alternative to inheritance is composition via code duplication, then I think that's even worse than inheritance, especially once the code is unleashed on a whole team of maintenance developers.
Who says all that duplicated code will be left in its pristine state, and used only for it's intended 'compositional' purpose? People might start hanging special logic in there or whatever...
Generally, for projects with large teams like this, the less code the better, because that means there's less code for them to screw up.¹
If that means using inheritance for reuse a bit even if it's not 'pure' OO or whatever, I'd chose that over duplicating code all over the place.
1: I know Java isn't fashionable, but for many reasons, it's perfect for this kind of project.
I guess I'm old school but I have had huge success with OOP and the long lived design patterns.
Patterns like adapter, bridge, and strategy are emensly useful in archetecting software that will last more then the next iteration or 6 months.
Perhaps I havent seen the right resources but so far function programming seem weak to me compared to oop which has things like domain driven design, and a very long list of battle tested design patterns.
I'm partial to Eric Elliott's take[1] which highlights the brittle rigidity of _classical_ inheritance in particular (vs eg prototypal inheritance). "OO" is often conflated with class-based inheritance per se.
Grpc-Java makes staying away from inheritance incredibly difficult (since they dont present an interface to the service methods you want to call). This makes adapters, decorators, and other useful patterns difficult to employ without the rigamarole of defining the interface yourself and duplicating the delegation.
I feel many people are overlooking the most important bit:
> He ended with a Spanish question that was evidently made popular in a US advertisement: "¿Por qué no los dos?", which means "Why not both?". Inheritance and composition are both useful tools; developers should understand when to use each.
When should you use inheritance. I can think of one use where it's acceptable and not dangerous. Implementing base class methods that are stateless helpers for sub classes.
But I'm not sure that inheritance is the best way to solve it. You would probably want to use interfaces and static methods instead. Basically I literally can't think of a case that isn't better solved by another way.
Some languages may not give you those other options, in which case, by all means use inheritance. Just make sure you are careful not to shoot yourself in the foot while doing so.
This is amusing. Earlier today I was reading through Sonya Keene's book on CLOS, and wondering why OOP had to be hierarchical rather than compositional, and wondered if there had been any work done in this space.
The main problem that inheritance introduces -- is irrelevant references to methods and fields.
If I want to see what code is using a method I am interested in, Visual Studio shows me all related code.
But if other developers overrode that method in their own classes, then in addition to seeing ~5 references I like to see, I have to sift through ~50 other references that are irrelevant to my investigation.
So 5 minutes investigation turns into 55 minutes investigation. As you can see, that "references spam" can easily slow down research and development 10x or more.
And then we get programmers that only know those languages claiming that inheritance if bad because the only language they know only supports an useless simulacra of it.
Been hacking on a side project in Go, after coming from a career of nearly exclusively using OO languages, with a little bit of functional here and there. And of course enjoying all of the new functional features that nearly every big language has been racing to implement these days.
At first there I had some quirks, along the lines of "how do I get thing X from A to B", or "these things are similar how do I share code between them".
Everytime this happened I couldn't help but go through a process of solving the problem in OO and then unwrapping it into a non-OO solution. Which doesn't always turn out well.
Eventually, once I became comfortable with Go, I got to the point where I just code things in the most obvious way possible. This struct has these fields. This function takes x and produces y. I dont try to take a big picture view of the software as a whole, Im just solving a bunch of small localized problems one after the other. Then I refactor periodically to get rid of anything completely stupid, and make sure the big picture looks ok. The name of the language is quite fitting, you just go.
Whereas in OO, for me at least, it feels like im constantly bouncing all around the abstraction layers of your program. Any change I make here in class X could affect these other classes. Class A is dependent on Y, but so is class B. How do I share it between them? Maybe dependency injection? Subclasses? Do I refactor to an abstract class? Wait, should it be an abstract class or an interface, what's the difference again? Is this generic enough? I only need to use this in this one place, but I'll make it generic because "best practices". What if I might need this other subclass eventually but it's not on the spec right now?
I'm obviously exaggerating, but I think most of us have been there. I find I often end up in a competition against myself about being more clever or more reusable with my OO code.
Our job is already hard enough when you have to decide and reason about algorithms, data structures, concurrency etc. The extra layer of code patterns usually just obscures things.
I'm not a complete hater though. Using a well designed OO library can be an absolute delight (PyTorch being a great example). But OO is the kind of thing were its not just garbage in garbage out. The garbage multiplies and creates even more garbage.
I wonder why so many books and articles like this one are written as though the approach they're championing is the only right approach, or always the right approach for every situation. There's never any consideration like, "in the following examples, this might be a good tactic..."
Looking at the subject for this post:
Inheritance is a good, straightforward approach when you have multiple related classes which share a lot of code but don't interact directly with other unrelated classes:
class document
{
public function title ()
{
}
public function get_plain_text ()
{
}
}
class word_document extends document
{
public function get_plain_text ()
{
}
}
In a document management application, you might need to represent lots of different kinds of document formats, each with their own specializations, but also with a lot of common attributes too. These document classes might not need to interact directly with a database or a network socket, so a straightforward and traditional class hierarchy would be the natural choice here.
But then let's say you wanted to build an abstract data pipe. A data pipe needs to talk to lots of different things; maybe it's a plain old network socket, maybe it's a database engine, maybe it's an API. Here, you get no advantages from using inheritance, because although each data pipe is trying to mimic the behaviors of any other data pipe, under the hood their code is completely dissimilar. You'd end up having to rewrite so much of the functionality that there'd be nothing really for a base class to actually do.
This is where composability and interfaces are the right approach:
class phabricator_datapipe implements datapipe
{
private $_api_client;
public function __construct ($api_client)
{
$this->_api_client = $api_client;
}
public function search ($parameters)
{
// Build a search query out of $parameters specific to
// this API, then:
$this->_api_client->search(...);
}
}
Each datapipe class strictly follows a common interface structure. Dependency injection is used during instantiation to compose the runtime object, so the responsibilities in the datapipe class are limited to translating the application's business needs into the specific structures required by the API, while the API class takes care of authentication and error handling.
There is room for both of these approaches in the same application, because they solve entirely different problems. The inheritance of the documents classes solves the problem of dealing with similar-but-different units of data in a maintainable, extensible way. The composable interface of the datapipe classes solves the problem of interacting with completely different external components in similar ways.
This likewise is where the post falls over, IMO. It presents an argument for composition, but is too lazy to provide any of the context that really makes composition look like the right answer. Whether a Car object should be modeled as a child of a Vehicle base class, or whether it should be more of a standalone class with other objects injected into it, depends entirely on whether the application just wants to say:
Vehicle->GetTimeToDestination(...)
or whether the application instead needs to:
Vehicle->StartYourEngine(...)
(Aside: I think a major failing of posts and books on programming is using contrived, made-up examples, especially when talking about abstractions like object-oriented programming. Those don't help anybody understand the advantages and disadvantages of different approaches. I'm using some examples from applications I've written.)
Isn't it dependancy injection rather than composition? I'd think JS old time "inheritance" where methods are being copied from class to class is composition.
Ever since then I've been on an anti-inheritance kick, although for the last several years it's polymorphed into an anti-OO kick.
Anyway, I suspect most readers here have heard the idea that inheritance should be used in limited circumstances (or not at all), but I wonder what proportion of college students get that message today.