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.
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.
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...
I believe this would work for you.
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.
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), …
const createCounter = ({ startAt = 0, incremementBy = 1 }) => {
let count = startAt
return {
currentCount: () => count,
step: () => {
count += incremementBy
}
}
}
const counter = createCounter({ startAt: 5 })
counter.step()
versus class Counter {
constructor({ startAt = 0, incrementBy = 1 }) {
this.startAt = startAt
this.incrementBy = incrementBy
this.count = 0
}
step() {
this.count += this.incrementBy
}
}
const counter = new Counter({ startAt: 5 })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.
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.
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.
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]
[0] https://www.youtube.com/watch?v=8bZh5LMaSmE [1] http://cek.io/blog/2014/05/26/poodr-ch-7/ [2] https://stackoverflow.com/questions/2218937/has-a-is-a-termi... [3] https://www.jetbrains.org/intellij/sdk/docs/basics/getting_s...
I'd go with 2:-
- messages
- 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.
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.
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.
It's so odd because when people talked about OO I imagined something live and parallel, kinda like Kay/Erlang buuut it was so far from that.
Actually, the first OO language was probably SketchPad, but it’s object model isn’t very recognizable compared to textual OO language.
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.
[0]: https://www.rubytapas.com/2012/10/17/episode-011-method-and-...
(I pity the educator if he or she didn't know this.)
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.
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.
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].
[1]: http://wiki.c2.com/?HowObjectOrientedIsClos
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.
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.
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.
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.
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?
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.
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.
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).
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.
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.
Do you realize how many hacks is built into Windows just because developers were using undocumented features?
Do you have a reference for that (i.e. the discouraged part)?
- 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.
To me this is the most important part. I like to have private or public, protected is not needed.
I agree that code representation should be kept separate.
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.
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.
Upvoted anyways :)
> I find that people just fling around random tenants
It's tenets. TENETS.
Please do not fling around random tenants! We'll have to re-plaster the walls again!
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.
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.
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.
AFAIK in C++ you must use inheritance if you want polymorphism since there is no real concept of interfaces.
If you want to do things the Java or C# way, then you're correct. You'd want a pure virtual interface, which mimics what you'd get with Java or C#.
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.
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.
Try Go if you’d like a familiar language without the inheritance that you can pick up in an afternoon.
Python manages quite well without encapsulation.
But the linked article is reasonable. Less reasonable articles like this one exist, however: https://codeburst.io/inheritance-is-evil-stop-using-it-6c4f1...
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.
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.
P.S. You may also find [3] interesting.
[1] https://news.ycombinator.com/item?id=18250255
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.
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?
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.