I completely agree that "good design"/"bad design" depends on the problems we're trying to solve, rather than some abstract property of the entities involved (i.e. whether "Car" should extend "Vehicle" or not, and whether those classes should even exist, cannot be answered without knowing what we're trying to accomplish).
Much agreement on this. It's concrete, visible and has behaviors that work well (click, resize, draw etc.) as well as providing a good entry into the thought process when designing modern user interfaces with components.
I'm a game dev and usually program in C#. I love C# and honestly some features of OO (like objects and classes) are great for programming games.
But inheritance is just a shit feature imo. Situations where it would be beneficial are extremely rare. I generally just pretend it doesnt exist. There are much better alternatives to it for most situations (eg. composition, build a class from other smaller classes that do one thing)
Inheritance is just a total mess. Overriding a method, especially a non-abstract one, feels more like monkeypatching. It feels unsafe, it's not clear if I should do it, etc. A lot of Apple APIs mark which methods should be overridden and which shouldn't be. It just generally feels like a crappy approach. Languages with deep inheritance trees like Ruby and Smalltalk and even Objective-C are also extremely difficult to learn how to use due to how much basic functionality of the object comes from far up the chain. Remember when Steve Jobs wanted people to make companies that just sell objects? Sounds like my worst nightmare. I don't see why Rust's dynamic traits, or just duck typing, aren't totally superior
The user you responded to uses C#, you can't override arbitrary methods there. Only methods explicity declared as virtual can be overriden, so this is different than many other OO languages.
I think this is also the better approach, making inheritance something you have to explicitly design for instead of a way to monkeypatch almost everything.
> Only methods explicity declared as virtual can be overriden
You can with "new", no?
public class Foo
{
public void Say() { Console.WriteLine("Foo!"); }
public void Think() { Console.WriteLine("I;m thinkin about thos beans");
}
public class Bar : Foo
{
new public void Say() { Console.WriteLine("Actually, Bar!"); }
}
So with Bar we have inherited Think(), but have overridden Say(). Ok technically the MSDN docs will say that we've "hidden" the inherited method Say(), but the effect here is that we've overridden something not marked "virtual"
edit: Oops yeah I'm talking rubbish, see replies for more info :D
Using new is still very different from using override, there are real differences in behaviour there. Though I never used "new", it's usually described as a really bad idea unless you know what you're doing, I never saw the need to use this.
Once you've ditched hierarchy, haven't you entered "functional programming" territory? It's just types, structs/maps/dictionaries, factories, and modules/closures now, right?
You still have classes, even if you ignore inheritance entirely. And e.g. with the built-in dependency injection in ASP.NET it feels like you're subtly encouraged to use composition for many cases. This is still very much OO-style programming, inheritance is only one part of this.
You've still got the concept of data hiding and an object lifecycle, I think. You're right in functional programming we often operate in quite a similar way to "objects without inheritance" - your module has an internal data structure, creates it at some point, manipulates it through a series of module functions. of course in functional you are usually not usually mutating directly.
The atomic element of code is the function. If I just religiously stick the fundamentals of good programming, tiny functions, avoid coupling, avoid duplication (within reason), then I am at least half way to having nice maintainable code already.
Once I have all these really nice tiny functions, Its trivial to reorganise and replace them.
Though I find classes also very useful, a class is mainly a bucket that you put functions into. Ideally a class is a black box that does one thing. All the functions needed to do that thing are in the class and also any relevant fields. Not being able to have fields in there would be counter productive. It's just so handy to have all the functions + fields that govern this one element of your game all in one place.. When you need to make changes everything relevant is one place, and that place also functions as a black box that the other elements dont know about. So to change one feature in the game u just change or replace that one class.
Just looking at the functions, nice maintainable code would look identical in a functional or OO language. I think the big differences between functional and OO are actually not important. I could put functions in a class, or just together in the same file.
Putting certain functions and fields into this same bucket is just a convenience and its intuitive to understand imo. Like I said in 6 months time when I have to change feature X I know where to look.
It's not a million miles from it though. If you start from DRY as an approach to OO design you'll produce something vaguely reasonable and be close enough to where you want to be that you can learn the differences.
I should have been a bit more precise. There are essentialy two ways to approach DRY in OO. One is to inherit implementation, which isn't the best way to go about things since it leads to more brittle code. The other way is to make sensible use of polymorphism and composition.
There are aspects of DRY that are worth pursuing (like reducing the number of places you need to get things right) and some that aren't worth pursuing (thinking you reduce the amount of work by re-using code heavily). For code to be reusable you have to be sure that developers only have to care about its interface. Implementation inheritance often doesn't do that. I've worked on projects with people who were obsessed with DRY and made heavy use of implementation inheritance, and the projects inevitably needed extra work to untangle a brittle mess with lots of surprising behaviors.
Yes, to be fair the simplest advice to new programmers about how to use inheritance should be "don't". Aggregation good, polymorphism good, inheritance bad more often than not.
The thing about the natural world is that “Nature does not practice OOP.” Our nice hierarchies of animals reflect our desire to build hierarchies. Those hierarchies are tools that are useful for many purposes, but they weren’t blueprints for constructing life.
I speculate that if we think of our hierarchies as tools, they say at least as much about our own brains and the problems we’re trying to solve as the do about the domain.
Which is fine, tools exist as levers for the mind. But we shouldn’t confuse them with reality. The map is not the terrain.
I disagree. Nature does practice OOP and taxonomic hierarchies are real things in nature if they are based on their evolutionary history. People say penguins are "flightless birds", but look how they swim. They don't swim like fishes. They basically are flying in water because they share the same genetic programming as other birds, just slightly modified to use a different medium. Nature doesn't re-implement things from scratch but is great at code reuse.
True but irrelevant. The fact is nature "subclasses" (in the OOP sense) animals all the time in evolution even if it is an unintelligent process ultimately due to chance modification.
I think I understand what your saying. In this context a penguin is implementing a quality it’s inherited from the gene it has as a bird. A Penguin may not be a bird in the same way we may thing a fly method should work , but it’s fly method derived from the fact that it’s genetically a bird.
DRY comes from a time where you had people not using abstractions at all. I have seen code from the 80s where code for printing forms were one function each writing directly what gets sent to the printer for each form with no abstraction at all. DRY was meant for people that wrote that kind of code. If all you have ever come into contact with is normal modern code, than DRY goes overboard: you start to abstract too much and make the code harder to read and maintain.
The real art is maintaining the balance between abstraction and pragmatic simple code.
It does not replace OOP because there are no answers to other questions like: does it support polymorphism? Or data encapsulation? Etc. It's a data access design principle, and that's about it.
After many years of OOP, I'm not sure if we gained anything from OOP, and if it wasn't a solution in search for a problem.
At first I was enthusiastic. Then I've realized that there might be better ways to solve problems than trying to fit everything in a "everything is an object" mentality.
OOP can lead to overly complex code, uneeeded abstractions and design patterns thrown on top of each other for no good reason.
There was a thinking that using objects you can model or imitate real life, but that is silly. And of course, programming doesn't deal with real life, but with bytes and instructions, data and actions performed on the data.
I still use OOP because most of the industry demands it, but when I can, I try to use a data oriented and a bit of a functional approach, even if I am using an OOP language.
As one can see in React, which contorted itself to be "functional" and ended up inventing bad, new syntax for objects in a language that already had objects built-in. They evidently couldn't figure out a better way to handle it, and landed on reinventing the wheel, badly, and but calling it something else.
For anyone unfamiliar, I recommend the whole series, but it also culminates in the presenter rewriting an entire nontrivial OOP program into a procedural one and saving whopping amounts of code/complexity. The big example helps to show this isn't just abstract rambling against OOP.
To me, his title is somewhat misleading though. It seems he's against the extreme "everything is an object" version of OOP. I think taking things to the extreme is almost always a bad thing. Sometimes goto is a good solution.
I do agree with most of his points though, which just so happens to be closely aligned with how I've ended up programming. I like to use interfaces, so my objects hierarchies are very shallow and use a lot of delegation, overriding few if any methods.
I also write a lot of free-standing functions, some long, when I feel that's best. I also mix in a fair bit of functional-like programming, especially when massaging data.
> he's against the extreme "everything is an object" version of OOP
I totally agree with you. I think he focuses on this style because it's generally what's taught in Universities, and he also has tons of tutorials geared towards students. So he's exposing his audience to the idea that some OOP principles are good, but overly adhering to the paradigm (in the way that would get you a 100% grade in an OOP class in college) is actually a bad way to program in general.
That's a fair point, and as I mentioned I do agree with his conclusion.
Being self-taught, I was already a proficient programmer in Delphi and C++ by the time I went to University. So to me OOP has always been "OOP when you need it".
Though now that you mention it, even by the time I started University I had a profound dislike for Java's "OO all the things" approach. It has not subsided...
"It should be clear to anyone that models of the world are completely different from models of software. The world does not consist of objects sending each other messages, and we would have to be seriously mesmerised by object jargon to believe that it does.
… we use different sets of building blocks for modelling the world and modelling the software …"
Good OOP stuff is usually in a standard library or has to do with computer science stuff instead of business applications. Writing OOP style services and repositories and real-world object hierarchies (Prius inherits from Car and implements IVehicle) is where it sucks.
The best thing that object orientation popularized is the interface/protocol abstraction. All the modern languages I learned put this front and center: Go's interfaces, Clojure's protocols and multimethods, Rust's traits.
This apparently goes back to part of the inspiration of Smalltalk (dataless programming). It enables you to create an ubiquitous language and radically improves your ability to structure a program in terms of reusable abstractions. It is fundamentally about hiding complexity and specificity on one end and only requiring or assuming what you _need_ to require or assume on the other.
In comparison to this, inheritance feels like a half baked implementation detail that got out of hand. Especially static inheritance hierarchies feel like the antithesis of object orientation. Pinning everything down and turning a codebase into immovable concrete.
>OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.
- Alan Kay -
Other remarks from him about OOP are (paraphrased):
- OOP is a recursion on the notion of the computer itself
- The internet\Erlang is more OOP than java
His vision for OOP is something akin to distributed computing: Each object is a seperate computer with its own private memory that absolutely nobody can write to, it presents to the world an interface which is the set of all messages it can respond to (messages might or might not be asynchronous, and might or might not be unreliable in order,delivery,etc...). "Late Binding" because you have zero gaurantees about anything not explicitly stated in the object's contract, anything the object doesn't promise to respect will be violated, you better not depend on it. This is to give freedom to as much different implementation of the same interface as possible.
Two other analogies I encountered are
- Objects are like hardware ICs (their interfaces are the external pins and the type of signals the IC expects on them, their internals is the private hardware and signals that nobody is expected to inspect or modify)
- Objects are like seperate programs (their interfaces are the GUI/cmd the program presents and the set of all possible actions you can do, their internals are the executable code and in-memory data structures that nobody is expected to inspect or modify)
Inheritance actually precedes mature OOP, it was introduced way back in Simula-62, the proto-OOP language that wasn't a proper language as much as a simulation framework. Inheritance makes perfect sense in a simulation framework, because you're modelling a small world with a definite ontology that you can articulate and be sure of, the creators of Simula stumbled on it when they found they were repeatedly duplicating code verbatium. The analogies of Car<Vehicle and Cat<Animal make perfect sense, this is what inheritance is really made for, it's just that most real software isn't discrete event simulations with platonic ontologies.
I respect Alan Kay as a programmer and an intellectual, but the plain and simple reality is that his definition of OO is not the one that carried the day, and his definition has very little relevance in the world today.
If one squints hard enough, one could argue that we are continuing to move in his direction even so, slowly and in fits. But it's not because we have an Alay-Kay-OO language, and to the extent it is happening, it's not clear that it's happening at a level of abstraction that languages matter much with. When people use OO as a term today, it's a different definition.
Modern OOP was introduced later. From what I can tell, the only new technical capability was implementation inheritance, which is strongly frowned upon these days.
Rust is a bit closer to the older OO ideal in that each object is its own private machine that encapsulates/protects its own state, and is late bound. The borrow checker allows the compiler to statically check that each use case of a given class is memory/threadsafe, and enforces basic state machines like "finish using this reference before calling next on the iterator", or "give up the ability to read() this network handle before transferring it to another thread". Modern OO completely fails to provide safety for those sorts of APIs (e.g. Java's ConcurrentModificationException is a compile time error in Rust)
On top of that base, you can build progressively more expressive OO-style API contracts, such as connection pools.
> “The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.” – Joe Armstrong
> OOP can lead to overly complex code, uneeeded abstractions and design patterns thrown on top of each other for no good reason.
All programming paradigms can do that, you are not making any particularly salient point about OOP here.
OOP doesn't mean that everything needs to be an object, but inheritance and shared behaviors are concepts that map neatly between the real world and the programming world, which is why OOP has been such a resounding success.
We have refined this approach now (e.g. using delegation over inheritance), but these basic concepts of shared behavior and reuse remain.
Any OOP course covers the 'reasons' for OOP (such as they are). Polymorphism, inheritance, and encapsulation. Yes, you can model much the same behavior with structs and associated functions (which is really still OOP to some extent, just without any language support), but it's quite a bit of additional work.
Late binding polymorphism and encapsulation are both very important concepts but there's no particular reason that your unit of inheritance should be your unit of polymorphism, often the boundaries should be drawn differently. And inheritance is just a way of achieving code re-use that tends to make suboptimal tradeoffs between flexibility and DRY.
There's a certain conceptual and implementation elegance to OoO so I understand why it became popular. But in practice all the new languages moving away from it are doing so for good reason.
Bound methods simply has the potential to be a tiny bit more readable:
`thing.doWork(arg)`
`doWork(thing, arg)`
`doThingWork(thing, arg)`
The first example is marginally more readable IMO. The second one sucks from a human perspective because we don't immediately know (unlike the IDE) that the function signature only accepts Things. The last one addresses this but is even longer, and we still have to mentally unpack the args.
IMO there is not much profound about OOP, but it can lead to slightly more readable code if used appropriately.
Functions can be invoked in at least 3 different ways or a mixture of the three
1. Prefix - C style f(x,y,z)
2. Infix - x + y; x.f(y,z); a join b
3. Post fix- (x,y,z) $ f
Infix is the best style in most cases, OOP accidentally helped programmers use a more readable infix style.
A lot of the benefits of OOP can be had by allowing good infix invocation support and interface support in an imperative non OOP language. Might as well throw in generics.
Infix is tricky because it doesn't scale well to complex expressions. I think Python does it right with plenty of infix keywords a la "if thing not in myset: ...".
Is your first/native language SVO (subject-verb-object)? Mine isn't, it's VSO (verb-subject-object) or SOV (subject-object-verb), and for me the first example isn't more readable so I wonder if that also changes our perspective on what option we see as the most natural.
I can admit though that from an IDE point of view having auto-completion upon writing thing and typing the dot and seeing the results is very useful, whereas there is nothing (as far as I know) that comes even close in functional languages where the second example is more typical.
Small thought from someone that's spent lots of time messing with embedded C.
I feel like classic OOP is a mistake because you're binding data objects with functional objects. The latter being a collection of functions that performs operations on the former.
If you break them up you can have multiple widgets for dealing with a data object. Each of them with whatever dependencies they need and nothing else. I feel like this style of programing has become more important because it fits in the the idea of computing as an assembly line. Passing data through a data pipelines. Steps which often happen on different processes or machines.
Dependency injection is also really really useful for clean C code.
> … using objects you can model or imitate real life, but that is silly.
1994 "It should be clear to anyone that models of the world are completely different from models of software. The world does not consist of objects sending each other messages, and we would have to be seriously mesmerised by object jargon to believe that it does.
… we use different sets of building blocks for modelling the world and modelling the software …"
OOP was a big step up from the procedural programming that dominated at the time. Unfortunately that step was kicked out from underneath us because the industry chose C with Class and Java, rather than Smalltalk, or CLOS, or even objective C.
I actually like to ask people in interviews to model a car and car factory because I want to see right away if they go deep into the nonsense of extending everything, or if they can use composition, or get away with something focused on maintainable code that meets requirements.
I've seen it all. Prius extends Car extends vehicle extends motor extends ...
Right within the first five minutes of the interview.
But I also think these old "a bike extends a " tutorials were the most frustrating thing in the world and made no sense to me when I was trying to learn OOP in the first place. From my memory, I recall all the examples I could find were absolutely nonsense but blogs were full of posts with lots of non-real-world about connecting web requests things to a DB code and lots of "yeah it's just like a person is-a mammal which is-an animal which...".
It wasn't until I wrote some c++ and looked at how it worked in the heap and stack before I started to understand it better. Then later I learnt about SOLID and discovered that you almost never really want to use extension in practice (at least in the problems I worked on), except for things like interfaces in places you expect (and plan to have, and even better if you already do have) a reason for variations in sub-types that have the same contract, or need to cross system boundaries.
I almost always want to use composition. I will use extensions but this should be really specifically be selected for a few key points.
The problem with that is you might feel pressured to talk about Prius extends Car extends Vehicle etc. even if you wouldn't do that in real code because it seems the question is meant to probe the concept of inheritence so it's natural to answer that way.
It takes a brave interview candidate to say "the best solution depends on the exact nature of the problem - and your problem doesn't make any sense so there's no valid solution".
I feel like you picked up on the wrong problem in their example...
> Prius extends Car extends vehicle extends motor extends
The problem isn't that it's too in-depth, or too overbuilt, that answer is as close to objectively wrong as you get on a pretty open ended question and I'd imagine only an extremely junior developer would come up with that.
For example, you could maybe argue that a model of car extends a specific car. I'd expect an experienced developer to favor composition over inheritance at that stage, but that's open to debate.
But if a Vehicle extends a motor, does a motor extend a piston? Does a wheel extend a lug nut?
At that point you're moving away from "arguably correct" to just plain failing to answer the question. It shows they don't know the difference between composition and inheritance.
> It takes a brave interview candidate to say "the best solution depends on the exact nature of the problem - and your problem doesn't make any sense so there's no valid solution".
You're free to ask questions, we're supposed to be trying to collaborate. I don't think it's "brave" to instead say "your problem doesn't make sense". Shows more hubris than anything.
How is it any different than a systems design question?
> It takes a brave interview candidate to say "the best solution depends on the exact nature of the problem - and your problem doesn't make any sense so there's no valid solution".
I got that a few times and it's a good sign I have a senior candidate.
The real-world use case is simple when you imagine that I want to get a JSON payload describing different car configurations, possibly for a "car configurator" on a sales website, or perhaps for a videogame where there are different cars with different attributes at random. Once I go from "make a car" to "give me some json" everyone started to see it's as a less fictitious problem.
Yes. This is why "default virtual" is such a dumb idea.
Virtual functions are about implementation: a derived type that overrides a virtual function does so specifically in order to deliver a specialized implementation.
The degree to which your class's virtuals match its public interface is an exact measure of how bad that interface is, as an OO abstraction. If your class was doing enough work to earn its keep, it would be presenting an interface in terms the client wants to see. Those are implemented for variant internal representations by composing calls to one or more private virtual interfaces.
The above is true about OO subsystems. But not all uses of virtual functions are about OO. Virtual functions are a mechanism. Anywhere the mechanism is useful, it is OK to use it, OO or no OO.
Java offers no other organizational facilities than OO mechanisms, so there you have little choice but to use them everywhere. In problems not suited to OO solutions, use of virtual calls may have nothing to do with OO, and none of the OO rules need apply: if it works, it works.
Any language that offers only one kind of abstraction is a very poor and limited language. Calling it "pure" should not fool anybody. The world is complicated and demands many kinds of tools. Any one will only match certain aspects of certain problems.
In job interviews, people don't write the code they would write normally, they write the code that they think the interviewer wants to see.
And that brings me to a topic that's entirely different but also very relevant: job interviews bring interviewer-biases with them.
If you run into an old-school interviewer who would do exactly that "Prius extends Car extends Vehicle, etc." nonsense, but you don't know it, they would rate you negatively.
If you run into someone who is just in love with functional programming, you'll lose any OO implementation.
If you run into someone who doesn't like it when you ask questions, you'll lose. If you run into someone who doesn't like you asking questions they don't know the answer to, you lose.
And if you get sent a 3-hour long Hackerrank or Leetcode algorithmic test, everybody loses.
Tech job interviews are just horribly biased and the game is won if you read your audience correctly. And even then, if the other person is a racist, or just doesn't like your face, or had a bad day, or feels threatened, or disapproves of how you write "Javascript" instead of "JavaScript", you still lose.
Every example you listed would be dodging an enormous bullet.
The exact example the grandparent used was essentially a wrong answer, so our "old-school interviewer" (that sounds kind of ageist doesn't it?) marking us down for not using it is a bad thing.
An interviewer who loves functional programming and doesn't communicate any preference then marks you down for not reading their mind is someone to avoid.
An interviewer who punishes you for asking questions is a huge red flag and you'll be dodging a huge bullet.
I'm a self-taught dev so I definitely have some thoughts on how tech interviewing goes, but an interview runs both ways. I'd much rather miss out on a job because the other person was racist or they hate when people ask questions, than to end up working with them.
"Your advice doesn't work out in every single situation ever experienced by a human so I'm going to come up with a sarcastic comment that says nothing of value to point that out."
You'd think common sense would tell one not to define what's good for them in general based on what's good for you when they're "missing rent with an eviction and sleeping in your car".
Yes, our industry has a lot of inexperienced managers and interviewers who don't know what to evaluate or how to clarify the goal for a candidate.
I think this is a different challenge related to the "background" hidden context: training interviewers. I think the "foreground" discussion point ("please make me a car") is way less relevant than the skill of an interviewer.
For about a year I tried this with every candidate: literally tell them what I am looking for in the interview and what is on my scorecard. Nobody asked any questions even once.
Not including the racist discrimination example, I'm not sure that this is a bad problem. I believe there is a tendency for us to leave the schooling system and start viewing interviews as though they were our new exams, where failure is your own fault. I argue that interviews are the starting point of a new relationship, so the closest analogy is actually a date. If you're on a date and express your values and the other person rejects you for it, it simply means you don't make a good relationship - even if it's just because the other person has unfair demands.
So don't project programming ideals you don't believe in. If they can't reconcile different opinions, it's their problem. You won't have the conviction or experience to do it convincingly anyway.
It's always this. Every interview I've had is a quest for the interviewer to demonstrate they know more about the subject they quiz me about than I do. This is especially true with kubernetes, it's a vast project with many areas, but if you don't know whatever niche the interviewer is interested in, you will struggle.
> If you run into someone who doesn't like it when you ask questions, you'll lose. If you run into someone who doesn't like you asking questions they don't know the answer to, you lose.
Just thinking here, but what if you take the guesswork out of it "Here at Company X our philosophy is to avoid deep class hierarchies. How would you model..."
I think it's a bad question, just like every single request to "model real world things X,Y,Z for no reason" and the article explains this:
You can’t add code to ducks.
You can’t refactor ducks.
Ducks don’t implement protocols.
If I got asked this question I'd probably spend the whole interview inquiring about the purpose of the modeling. It's for a simulation? What info do you want to get from the simulation? It's for an ERP app?
I don't have problems with simple tasks which are not real-world but abstract something from the real-world use cases. The problem with "model me a car" is that it abstracts nothing.
Also for the most real-life purposes of modeling a car factory you wouldn't even have a "Car" in your object hierarchy.
"Car/Animal extends..." slowed my ability to comprehend OOP significantly when I was learning to code. It just did not make any sense to me and I felt like an idiot for not being able to equate these "real-world" examples to the code I was trying to write. I just could not make the connection.
It wasn't until I started trying to make a simple video game that these concepts made sense: an "Enemy" class could have properties of health and speed that a "Boss" or "Minion" could inherit from, etc.
Even in games composition is much better and what pretty much every engine forces you to use these days. It’s a lot more flexible too as you can compose a new boss out of ten different components rather than having to refactor a crazy tree of nonsense
So you literally say "can you model a car and a car factory?" because that's a pretty meaningless question so I'm not surprised they think "what is he talking about? ohhh he's referring to the classic car inherits vehicle thing and wants to see if I understand inheritance. ok I'll show him some inheritance!"
The book "Design Patterns: Elements of Reusable Object-Oriented Software" has an initial chapter that provides one of the most concise introductions to OO I've read. Its main focus is on polymorphism and it provides some easy to understand examples.
In fact, if you think of Go as an OO language (which I guess many won't), it has all the things you actually care about from OO. It doesn't have inheritance and you don't explicitly declare that a type implements some interface (). Since there is no inheritance of implementation you are forced to use composition, which is what people tend to end up recommending after a few years of fighting with OO code that has been written by people in love with a Linnean hierarchies and complex approaches to DRY.
() There are tricks to accomplish this, for instance creating a New() function that returns an interface type, which will make the compiler ensure you implement the interface).
* That has a hardcoded list of strings baked into it
* Accepts as inputs a filename (but can be empty) and a substring to search for
* At runtime, either (a) (if filename empty) iterates over hardcoded list of strings, or (b) opens file and iterates over lines in it
* For each string / line, if contains search string then output to stdout (and maybe also increment a counter).
It's very artificial but does allow the basic idea of OO to be illustrated and it's far less code than a GUI example. You have a base class and two derived classes that really do have a different mechanism, and even have different state (file handle or integer index). The program logic is also quite decoupled (the class interface is not tied to the fact you're doing string search).
You could solve the problem without classes by reading the whole file into the same list structure as the hardcoded strings, but that aspect is actually quite nice because you can present that first and then ask what to do if you want to avoid that unnecessary memory usage. It's also nice that you're illustrating a really common OO pattern (iterator).
The only slight downside is that some languages make this easy to do without (explicit) classes e.g. Python generator functions. But you can add a footnote that they exist and this is just to illustrate classes - it still beats the abstract examples.
I'm not clear on what you GetLines() method is returning (thanks to the var keyword - very useful but sometimes problematic, this is one of those times).
If your GetLines() is returning a string[] then I agree there was no benefit. But that's not what I meant. As I said, I'm imagining the base class (ILinesProvider) to have an iterator-like interface. So, rather than string[] GetLines() method, it would have a string getNextLine() method that you call in a loop. That way, with the file, you don't load the whole thing in memory (as I also said).
If your GetLines() method is returning an IEnumerable<string> (since this seems to be C#) then this is the problem I mentioned at the end of my comment - the base class I'm imagining is similar to an existing base class in the language, and it's confusing to write your own similar-but-slightly-different version in an example. But I think it's best to do it anyway and explain it away in a footnote. (But my ILinesProvider wouldn't return an IEnumerable, like in your snippet - instead, ILinesProvider actually is the iterator (but with a slightly different interface).)
When I first learned OOP from the dry examples like Shape is the base class of Circle and Triangle, and so on … I just didn’t appreciate why that was much better than the flat imperative style of plain old C and retreated back to the comforts of C. Then I started writing larger C programs with structures that had common function pointers called “draw” and “rotate” and realized these are just obj ct methods … and the structures were different implementations of a common base class … OK, so I guess OOP is useful after all.
So it helps to compare the old ways to the new ways side by side, with a real world example, to understand why the new ways are better.
When you're introducing OOP to beginning programming students, these metaphors still have some value. Beyond that, though, it's time to go with more realistic examples of polymorphism.
"You can’t fake the ability to turn a duck into a penguin by moving its
duckness into an animal of some other species that can be replaced at
runtime."
It's clear that OOP in the sense of "Car extends Vehicle" is not a good idea, yet the classic OOP languages go out of their way to support this kind of programming. Meanwhile the alternative approaches require you to jump through a bunch of hoops or apply convoluted "design patterns". Writing good, modern "OOP" you are often fighting the OOP language! Things are getting somewhat better (e.g. Java Record types) but perhaps it is time to admit these languages just aren't a good fit?
The only real difference is that inheritance as explicit language support while composition is something you can easily implement with common language features. It's not really more difficult to use composition than inheritance.
As an example, if I want to use composition in C# using ASP.NET, I simply inject the class I want to use into my new class using the built-in DI. This is as simple as extending the class.
Agreed, although this a recent addition in Java and a step away from old school OOP
>- List comprehensions
Enumerable.Range is not equivalent to list comprehensions. For example, in F# you can build a list using a comprehension containing more or less any code you like. It's incredibly powerful for things like building HTML.
>- Custom operators (used sparingly for building DSLs)
It suits cases (e.g. modelling business logic) where you have that kind of edge case. Until recently non-OO languages had very little support for delegation, and so while inheritance bundles delegation with some other things it really shouldn't be bundled with, it was often the least-bad option. In particular this made it a natural fit for GUI, where you very often want "this widget is almost like this one, except...", and so it was able to ride the coattails of that.
Add on the fact that people somehow seem to think Alan Kay has some insights (even if they can never tell you what they are), and Bob's your uncle.
I mean something like "this value contains this kind of value and implements the same interface as it by forwarding methods to the corresponding methods on that". E.g. https://kotlinlang.org/docs/delegation.html . (i.e. imagine doing something similar to inheritance but having the "parent" object be explicitly a field of your child object rather than just mushed together with it).
Typeclass instances themselves can do delegation because they have inheritance (or something very similar to it). The relationship between Monoid and Semigroup is very much like OO subclassing. I'm talking about doing delegation for regular values, not typeclass instances.
Oh, well then you are talking perhaps using a newtype to wrap another type and perhaps use deriving to get behaviors from underlying type automatically.
Or with a record you just explicitly implement typeclass methods in reference to the contained data.
If you mean doing it without doing a typeclass, well the functions need to have different names. If that’s exactly what you want. In order to have the same function name you need a typeclass.
> Oh, well then you are talking perhaps using a newtype to wrap another type and perhaps use deriving to get behaviors from underlying type automatically.
Deriving sort of does what delegation does, but in a very limited way, and even when it does work you can't use it for user-defined typeclasses, so it's not a solution to the problem except in a handful of special cases.
> Or with a record you just explicitly implement typeclass methods in reference to the contained data.
The whole point of a delegation feature is that you don't have to do this explicitly for each method.
It's really not. For example C# has no guiding principles imo. It's just a hodgepodge of any and all popular language features all lumped together. Java was popular at its inception so it stole lots of Java, as new things have become popular they are added (eg. anonymous functions)
As a dev you just use whatever features u like and can safely ignore the majority of them. Its very non-prescriptive unlike some languages where you must do a thing only one way.
Eg. Inheritance is pretty much terrible, I just never use it. Same way I try to not write bad code and instead write good code.
Also sure you need to put functions in classes, but thats fine, classes are just buckets for code. In any language you need to put your functions somewhere eg. in a text file.
Didn’t read the article, but based solely on the title, I assume it’s a straw man. I.e. declare that vehicle is a sensible abstraction for our needs, then show why it isn’t.
A base class should only exist where there are implementations -it needs to be plural. Otherwise just fold the base into the child, or better yet use composition not inheritance. I generally do not count any form of test double (mock...) as a an implementation, since only rarely is the real implementation something that you should mock.
> Penguins don’t implement the “fly” method that can be found in birds.
Yep. That would be like having immutable lists which implement List which has an "add" method. Obviously that would be too stupid for a sensible programming language.
Any sort of plugin architecture, e.g. VST audio plugins. Often there's an interface which you could implement directly, or you could inherit from an abstract class which provides some common functionality. I like this because it stresses that deep hierarchies aren't necessary to be useful. Also, if you're refactoring and see a "switch on type" then a little bit of inheritance is probably OK.
I admit I taught intro to C++ and used shapes as an example. I spent a few weeks building a little "ASCII render" thingy with a 80x30 "canvas" where each character is a pixel. Then you could place Shapes on the canvas and the shape would decide whether a given pixel was contained() inside of it.
I remember my first OO class where the car analogy was proven to be bad using the existence of the El Camino.
I spent a LOT of time in the late 80s and the 90s wrestling with class hierarchies, coming up with one tower of abstraction after another. So much wasted time; perhaps it didn't help that my vehicles were C++ and Java, but I think that I would have fared just as well in any other OOPy language.
What I got as a side-effect was a bunch of re-written code. And I wondered:
Maybe OOP is a terrible paradigm. Maybe the designs that come out of it are so awful that it causes people to rewrite code, over and over. Re-examined systems are often better than first attempts. Once you reimplement something five or six times, you've probably explored most of the problem space and are pretty good at it. It's even likely that you are thoroughly sick of the project and just want to get the stupid thing done and shipped. (Yeah, your PMs and entire management chain just smiled behind your back).
In other words, it's Brook's "plan to throw one away, you will anyway" in action, while telling your boss that you had to rewrite your framework again because it didn't handle the flightless, swimming bird scenarios.
Object oriented programming was meant to be used in a Smalltalk environment, where immediacy & liveliness were key principles of the design.
OOP wasn't meant to be used by professional programmers ever, but this is always always forgotten. Why do we still talk about OOP when it comes to professional / full-time programming? OOP was always meant to be about providing end-users a way to create programs & dynamic behavior without becoming full-time programmers.
I don’t get this originalist argument. After it’s introduction, OOP very clearly evolved in a professional direction almost immediately. Why would you erase that history, and the work of hundreds to thousands of people working on object technologies, because it wasn’t the original conception?
Fair point! I guess when I see people saying "OOP isn't getting us what we want" ... then I want to point out that it was adopted for a different use case for which people are claiming it's falling short for?
But OOP was specifically created to model real world objects and their relationships. If this article is saying OOP doesn't do that very well and we should not think in those terms, what's it good for?
As someone who has been using OOP since C++ was Cfront and you fetched it from usenet ... I find these conversations fascinating.
It's like listening to a conversation about cars where someone says they hate cars because you have to figure out a manual choke and the many inner tube failures.
Do people still use these crazy inheritance trees? I haven't encountered it since the early 2000's.
I do scientific modeling. We have classes and hierarchies of different physical components we simulate, like fuel assemblies and pumps and heat exchangers. I guess this falls into the exception of "clone of The Sims or something" so perhaps my complaint is covered.
The problem is “A extends B”. You want to teach “A implements B” first. The important idea is that there’s a protocol/interface for which there can be different implementations used in parallel at runtime, and the interface type allows the client code to abstract from the different implementations. The next step is that an implementation may implement multiple interfaces at once, which also may be subinterfaces of one another. Only as a last step you can introduce implementation inheritance, which is just an optimization (both in runtime mechanism and code verbosity) when reusing code, an optimization which comes at a cost regarding maintenance and implementation dependencies.
176 comments
[ 3.7 ms ] story [ 215 ms ] threadSee, for example, section 3.2 in https://www.econstor.eu/obitstream/10419/66819/1/717744094.p...
The suggestion of GUI widgets is certainly better and more concrete; although it's skating dangerously close to "Circle extends Ellipse" and "Square extends Rectangle" territory! (e.g. https://softwareengineering.stackexchange.com/questions/2381... )
But inheritance is just a shit feature imo. Situations where it would be beneficial are extremely rare. I generally just pretend it doesnt exist. There are much better alternatives to it for most situations (eg. composition, build a class from other smaller classes that do one thing)
I think this is also the better approach, making inheritance something you have to explicitly design for instead of a way to monkeypatch almost everything.
You can with "new", no?
So with Bar we have inherited Think(), but have overridden Say(). Ok technically the MSDN docs will say that we've "hidden" the inherited method Say(), but the effect here is that we've overridden something not marked "virtual"edit: Oops yeah I'm talking rubbish, see replies for more info :D
https://dotnetfiddle.net/zcVPof
Once I have all these really nice tiny functions, Its trivial to reorganise and replace them.
Though I find classes also very useful, a class is mainly a bucket that you put functions into. Ideally a class is a black box that does one thing. All the functions needed to do that thing are in the class and also any relevant fields. Not being able to have fields in there would be counter productive. It's just so handy to have all the functions + fields that govern this one element of your game all in one place.. When you need to make changes everything relevant is one place, and that place also functions as a black box that the other elements dont know about. So to change one feature in the game u just change or replace that one class.
Just looking at the functions, nice maintainable code would look identical in a functional or OO language. I think the big differences between functional and OO are actually not important. I could put functions in a class, or just together in the same file. Putting certain functions and fields into this same bucket is just a convenience and its intuitive to understand imo. Like I said in 6 months time when I have to change feature X I know where to look.
I wish someone had told me this when I first started coding. I wasted so much time building pointless hierarchies based on ontology rather than DRY.
There are aspects of DRY that are worth pursuing (like reducing the number of places you need to get things right) and some that aren't worth pursuing (thinking you reduce the amount of work by re-using code heavily). For code to be reusable you have to be sure that developers only have to care about its interface. Implementation inheritance often doesn't do that. I've worked on projects with people who were obsessed with DRY and made heavy use of implementation inheritance, and the projects inevitably needed extra work to untangle a brittle mess with lots of surprising behaviors.
I speculate that if we think of our hierarchies as tools, they say at least as much about our own brains and the problems we’re trying to solve as the do about the domain.
Which is fine, tools exist as levers for the mind. But we shouldn’t confuse them with reality. The map is not the terrain.
Perhaps you mean — The analogy is…
The real art is maintaining the balance between abstraction and pragmatic simple code.
At first I was enthusiastic. Then I've realized that there might be better ways to solve problems than trying to fit everything in a "everything is an object" mentality.
OOP can lead to overly complex code, uneeeded abstractions and design patterns thrown on top of each other for no good reason.
There was a thinking that using objects you can model or imitate real life, but that is silly. And of course, programming doesn't deal with real life, but with bytes and instructions, data and actions performed on the data.
I still use OOP because most of the industry demands it, but when I can, I try to use a data oriented and a bit of a functional approach, even if I am using an OOP language.
Personally, after working with vanilla JS, then jquery, I looked at angular and react. I liked react so much better
https://m.youtube.com/watch?v=V6VP-2aIcSc
I do agree with most of his points though, which just so happens to be closely aligned with how I've ended up programming. I like to use interfaces, so my objects hierarchies are very shallow and use a lot of delegation, overriding few if any methods.
I also write a lot of free-standing functions, some long, when I feel that's best. I also mix in a fair bit of functional-like programming, especially when massaging data.
I totally agree with you. I think he focuses on this style because it's generally what's taught in Universities, and he also has tons of tutorials geared towards students. So he's exposing his audience to the idea that some OOP principles are good, but overly adhering to the paradigm (in the way that would get you a 100% grade in an OOP class in college) is actually a bad way to program in general.
Being self-taught, I was already a proficient programmer in Delphi and C++ by the time I went to University. So to me OOP has always been "OOP when you need it".
Though now that you mention it, even by the time I started University I had a profound dislike for Java's "OO all the things" approach. It has not subsided...
"Modeling the world as OOP", OTOH, is a load of bull.
… we use different sets of building blocks for modelling the world and modelling the software …"
1994 "Designing Object Systems"
https://www.google.com/books/edition/Designing_Object_System...
This apparently goes back to part of the inspiration of Smalltalk (dataless programming). It enables you to create an ubiquitous language and radically improves your ability to structure a program in terms of reusable abstractions. It is fundamentally about hiding complexity and specificity on one end and only requiring or assuming what you _need_ to require or assume on the other.
In comparison to this, inheritance feels like a half baked implementation detail that got out of hand. Especially static inheritance hierarchies feel like the antithesis of object orientation. Pinning everything down and turning a codebase into immovable concrete.
- Alan Kay -
Other remarks from him about OOP are (paraphrased):
- OOP is a recursion on the notion of the computer itself
- The internet\Erlang is more OOP than java
His vision for OOP is something akin to distributed computing: Each object is a seperate computer with its own private memory that absolutely nobody can write to, it presents to the world an interface which is the set of all messages it can respond to (messages might or might not be asynchronous, and might or might not be unreliable in order,delivery,etc...). "Late Binding" because you have zero gaurantees about anything not explicitly stated in the object's contract, anything the object doesn't promise to respect will be violated, you better not depend on it. This is to give freedom to as much different implementation of the same interface as possible.
Two other analogies I encountered are
- Objects are like hardware ICs (their interfaces are the external pins and the type of signals the IC expects on them, their internals is the private hardware and signals that nobody is expected to inspect or modify)
- Objects are like seperate programs (their interfaces are the GUI/cmd the program presents and the set of all possible actions you can do, their internals are the executable code and in-memory data structures that nobody is expected to inspect or modify)
Inheritance actually precedes mature OOP, it was introduced way back in Simula-62, the proto-OOP language that wasn't a proper language as much as a simulation framework. Inheritance makes perfect sense in a simulation framework, because you're modelling a small world with a definite ontology that you can articulate and be sure of, the creators of Simula stumbled on it when they found they were repeatedly duplicating code verbatium. The analogies of Car<Vehicle and Cat<Animal make perfect sense, this is what inheritance is really made for, it's just that most real software isn't discrete event simulations with platonic ontologies.
If one squints hard enough, one could argue that we are continuing to move in his direction even so, slowly and in fits. But it's not because we have an Alay-Kay-OO language, and to the extent it is happening, it's not clear that it's happening at a level of abstraction that languages matter much with. When people use OO as a term today, it's a different definition.
Rust is a bit closer to the older OO ideal in that each object is its own private machine that encapsulates/protects its own state, and is late bound. The borrow checker allows the compiler to statically check that each use case of a given class is memory/threadsafe, and enforces basic state machines like "finish using this reference before calling next on the iterator", or "give up the ability to read() this network handle before transferring it to another thread". Modern OO completely fails to provide safety for those sorts of APIs (e.g. Java's ConcurrentModificationException is a compile time error in Rust)
On top of that base, you can build progressively more expressive OO-style API contracts, such as connection pools.
All programming paradigms can do that, you are not making any particularly salient point about OOP here.
OOP doesn't mean that everything needs to be an object, but inheritance and shared behaviors are concepts that map neatly between the real world and the programming world, which is why OOP has been such a resounding success.
We have refined this approach now (e.g. using delegation over inheritance), but these basic concepts of shared behavior and reuse remain.
If I need some functionality, I write a function and pass it a struct. I’ve yet to find a time when this approach is too limiting, or chaotic.
Even for interfaces, function pointers achieve that goal entirely.
I do miss type safe vectors, e.g templating. Thats above and beyond the most common thing I miss.
There's a certain conceptual and implementation elegance to OoO so I understand why it became popular. But in practice all the new languages moving away from it are doing so for good reason.
`thing.doWork(arg)`
`doWork(thing, arg)`
`doThingWork(thing, arg)`
The first example is marginally more readable IMO. The second one sucks from a human perspective because we don't immediately know (unlike the IDE) that the function signature only accepts Things. The last one addresses this but is even longer, and we still have to mentally unpack the args.
IMO there is not much profound about OOP, but it can lead to slightly more readable code if used appropriately.
Functions can be invoked in at least 3 different ways or a mixture of the three
1. Prefix - C style f(x,y,z)
2. Infix - x + y; x.f(y,z); a join b
3. Post fix- (x,y,z) $ f
Infix is the best style in most cases, OOP accidentally helped programmers use a more readable infix style.
A lot of the benefits of OOP can be had by allowing good infix invocation support and interface support in an imperative non OOP language. Might as well throw in generics.
Infix is tricky because it doesn't scale well to complex expressions. I think Python does it right with plenty of infix keywords a la "if thing not in myset: ...".
I can admit though that from an IDE point of view having auto-completion upon writing thing and typing the dot and seeing the results is very useful, whereas there is nothing (as far as I know) that comes even close in functional languages where the second example is more typical.
Long function invocations are an annoying consequence.
I feel like classic OOP is a mistake because you're binding data objects with functional objects. The latter being a collection of functions that performs operations on the former.
If you break them up you can have multiple widgets for dealing with a data object. Each of them with whatever dependencies they need and nothing else. I feel like this style of programing has become more important because it fits in the the idea of computing as an assembly line. Passing data through a data pipelines. Steps which often happen on different processes or machines.
Dependency injection is also really really useful for clean C code.
1994 "It should be clear to anyone that models of the world are completely different from models of software. The world does not consist of objects sending each other messages, and we would have to be seriously mesmerised by object jargon to believe that it does.
… we use different sets of building blocks for modelling the world and modelling the software …"
page 6 "Designing Object Systems"
https://www.google.com/books/edition/Designing_Object_System...
Yea, sure, we didn't gain anything.
Except milions of programmers being able to model complex real world into their software that powers almost everything now.
Even if it is "shitty" "hard to maintain"
then it at least works
I've seen it all. Prius extends Car extends vehicle extends motor extends ...
Right within the first five minutes of the interview.
But I also think these old "a bike extends a " tutorials were the most frustrating thing in the world and made no sense to me when I was trying to learn OOP in the first place. From my memory, I recall all the examples I could find were absolutely nonsense but blogs were full of posts with lots of non-real-world about connecting web requests things to a DB code and lots of "yeah it's just like a person is-a mammal which is-an animal which...".
It wasn't until I wrote some c++ and looked at how it worked in the heap and stack before I started to understand it better. Then later I learnt about SOLID and discovered that you almost never really want to use extension in practice (at least in the problems I worked on), except for things like interfaces in places you expect (and plan to have, and even better if you already do have) a reason for variations in sub-types that have the same contract, or need to cross system boundaries.
I almost always want to use composition. I will use extensions but this should be really specifically be selected for a few key points.
It takes a brave interview candidate to say "the best solution depends on the exact nature of the problem - and your problem doesn't make any sense so there's no valid solution".
> Prius extends Car extends vehicle extends motor extends
The problem isn't that it's too in-depth, or too overbuilt, that answer is as close to objectively wrong as you get on a pretty open ended question and I'd imagine only an extremely junior developer would come up with that.
For example, you could maybe argue that a model of car extends a specific car. I'd expect an experienced developer to favor composition over inheritance at that stage, but that's open to debate.
But if a Vehicle extends a motor, does a motor extend a piston? Does a wheel extend a lug nut?
At that point you're moving away from "arguably correct" to just plain failing to answer the question. It shows they don't know the difference between composition and inheritance.
> It takes a brave interview candidate to say "the best solution depends on the exact nature of the problem - and your problem doesn't make any sense so there's no valid solution".
You're free to ask questions, we're supposed to be trying to collaborate. I don't think it's "brave" to instead say "your problem doesn't make sense". Shows more hubris than anything.
How is it any different than a systems design question?
I got that a few times and it's a good sign I have a senior candidate.
The real-world use case is simple when you imagine that I want to get a JSON payload describing different car configurations, possibly for a "car configurator" on a sales website, or perhaps for a videogame where there are different cars with different attributes at random. Once I go from "make a car" to "give me some json" everyone started to see it's as a less fictitious problem.
Virtual functions are about implementation: a derived type that overrides a virtual function does so specifically in order to deliver a specialized implementation.
The degree to which your class's virtuals match its public interface is an exact measure of how bad that interface is, as an OO abstraction. If your class was doing enough work to earn its keep, it would be presenting an interface in terms the client wants to see. Those are implemented for variant internal representations by composing calls to one or more private virtual interfaces.
The above is true about OO subsystems. But not all uses of virtual functions are about OO. Virtual functions are a mechanism. Anywhere the mechanism is useful, it is OK to use it, OO or no OO.
Java offers no other organizational facilities than OO mechanisms, so there you have little choice but to use them everywhere. In problems not suited to OO solutions, use of virtual calls may have nothing to do with OO, and none of the OO rules need apply: if it works, it works.
Any language that offers only one kind of abstraction is a very poor and limited language. Calling it "pure" should not fool anybody. The world is complicated and demands many kinds of tools. Any one will only match certain aspects of certain problems.
And that brings me to a topic that's entirely different but also very relevant: job interviews bring interviewer-biases with them.
If you run into an old-school interviewer who would do exactly that "Prius extends Car extends Vehicle, etc." nonsense, but you don't know it, they would rate you negatively.
If you run into someone who is just in love with functional programming, you'll lose any OO implementation.
If you run into someone who doesn't like it when you ask questions, you'll lose. If you run into someone who doesn't like you asking questions they don't know the answer to, you lose.
And if you get sent a 3-hour long Hackerrank or Leetcode algorithmic test, everybody loses.
Tech job interviews are just horribly biased and the game is won if you read your audience correctly. And even then, if the other person is a racist, or just doesn't like your face, or had a bad day, or feels threatened, or disapproves of how you write "Javascript" instead of "JavaScript", you still lose.
The exact example the grandparent used was essentially a wrong answer, so our "old-school interviewer" (that sounds kind of ageist doesn't it?) marking us down for not using it is a bad thing.
An interviewer who loves functional programming and doesn't communicate any preference then marks you down for not reading their mind is someone to avoid.
An interviewer who punishes you for asking questions is a huge red flag and you'll be dodging a huge bullet.
I'm a self-taught dev so I definitely have some thoughts on how tech interviewing goes, but an interview runs both ways. I'd much rather miss out on a job because the other person was racist or they hate when people ask questions, than to end up working with them.
Similar works backwards, if you're using the interview to also ask the right questions, good people are generally not going to lie to you...
You'd think common sense would tell one not to define what's good for them in general based on what's good for you when they're "missing rent with an eviction and sleeping in your car".
I think this is a different challenge related to the "background" hidden context: training interviewers. I think the "foreground" discussion point ("please make me a car") is way less relevant than the skill of an interviewer.
For about a year I tried this with every candidate: literally tell them what I am looking for in the interview and what is on my scorecard. Nobody asked any questions even once.
So don't project programming ideals you don't believe in. If they can't reconcile different opinions, it's their problem. You won't have the conviction or experience to do it convincingly anyway.
That's actually a win in your book, not a loss!
I don't have problems with simple tasks which are not real-world but abstract something from the real-world use cases. The problem with "model me a car" is that it abstracts nothing.
Also for the most real-life purposes of modeling a car factory you wouldn't even have a "Car" in your object hierarchy.
It wasn't until I started trying to make a simple video game that these concepts made sense: an "Enemy" class could have properties of health and speed that a "Boss" or "Minion" could inherit from, etc.
In fact, if you think of Go as an OO language (which I guess many won't), it has all the things you actually care about from OO. It doesn't have inheritance and you don't explicitly declare that a type implements some interface (). Since there is no inheritance of implementation you are forced to use composition, which is what people tend to end up recommending after a few years of fighting with OO code that has been written by people in love with a Linnean hierarchies and complex approaches to DRY.
() There are tricks to accomplish this, for instance creating a New() function that returns an interface type, which will make the compiler ensure you implement the interface).
* That has a hardcoded list of strings baked into it
* Accepts as inputs a filename (but can be empty) and a substring to search for
* At runtime, either (a) (if filename empty) iterates over hardcoded list of strings, or (b) opens file and iterates over lines in it
* For each string / line, if contains search string then output to stdout (and maybe also increment a counter).
It's very artificial but does allow the basic idea of OO to be illustrated and it's far less code than a GUI example. You have a base class and two derived classes that really do have a different mechanism, and even have different state (file handle or integer index). The program logic is also quite decoupled (the class interface is not tied to the fact you're doing string search).
You could solve the problem without classes by reading the whole file into the same list structure as the hardcoded strings, but that aspect is actually quite nice because you can present that first and then ask what to do if you want to avoid that unnecessary memory usage. It's also nice that you're illustrating a really common OO pattern (iterator).
The only slight downside is that some languages make this easy to do without (explicit) classes e.g. Python generator functions. But you can add a footnote that they exist and this is just to illustrate classes - it still beats the abstract examples.
If your GetLines() is returning a string[] then I agree there was no benefit. But that's not what I meant. As I said, I'm imagining the base class (ILinesProvider) to have an iterator-like interface. So, rather than string[] GetLines() method, it would have a string getNextLine() method that you call in a loop. That way, with the file, you don't load the whole thing in memory (as I also said).
If your GetLines() method is returning an IEnumerable<string> (since this seems to be C#) then this is the problem I mentioned at the end of my comment - the base class I'm imagining is similar to an existing base class in the language, and it's confusing to write your own similar-but-slightly-different version in an example. But I think it's best to do it anyway and explain it away in a footnote. (But my ILinesProvider wouldn't return an IEnumerable, like in your snippet - instead, ILinesProvider actually is the iterator (but with a slightly different interface).)
So it helps to compare the old ways to the new ways side by side, with a real world example, to understand why the new ways are better.
"You can’t fake the ability to turn a duck into a penguin by moving its duckness into an animal of some other species that can be replaced at runtime."
It's clear that OOP in the sense of "Car extends Vehicle" is not a good idea, yet the classic OOP languages go out of their way to support this kind of programming. Meanwhile the alternative approaches require you to jump through a bunch of hoops or apply convoluted "design patterns". Writing good, modern "OOP" you are often fighting the OOP language! Things are getting somewhat better (e.g. Java Record types) but perhaps it is time to admit these languages just aren't a good fit?
As an example, if I want to use composition in C# using ASP.NET, I simply inject the class I want to use into my new class using the built-in DI. This is as simple as extending the class.
- Discriminated unions
- Immutable data types (records)
- Free functions (or modules of free functions)
- Software transactional memory (STM), atoms
- Ad-hoc polymorphism (traits)
- Custom operators (used sparingly for building DSLs)
- List comprehensions
Instead we get anti-features like automatic properties, implementation inheritance, events, object initializers, etc...
there are
>- List comprehensions
Enumerable.Range?
>- Custom operators (used sparingly for building DSLs)
Could you please show what kind of DSL you'd want to write?
Agreed, although this a recent addition in Java and a step away from old school OOP
>- List comprehensions
Enumerable.Range is not equivalent to list comprehensions. For example, in F# you can build a list using a comprehension containing more or less any code you like. It's incredibly powerful for things like building HTML.
>- Custom operators (used sparingly for building DSLs)
These are used in many places, but to give one example https://www.cs.tufts.edu/~nr/cs257/archive/simon-peyton-jone...
Add on the fact that people somehow seem to think Alan Kay has some insights (even if they can never tell you what they are), and Bob's your uncle.
What do you mean by "delegation" here?
> Until recently non-OO languages had very little support for delegation
No they don't. Show me how you'd do that in e.g. Haskell. And note that Rust has `delegate` as a macro even though it has a trait system.
Or with a record you just explicitly implement typeclass methods in reference to the contained data.
If you mean doing it without doing a typeclass, well the functions need to have different names. If that’s exactly what you want. In order to have the same function name you need a typeclass.
Deriving sort of does what delegation does, but in a very limited way, and even when it does work you can't use it for user-defined typeclasses, so it's not a solution to the problem except in a handful of special cases.
> Or with a record you just explicitly implement typeclass methods in reference to the contained data.
The whole point of a delegation feature is that you don't have to do this explicitly for each method.
It's not a bad base for UI programming. There is quite a lot of functionality that is suited to the OOP paradigm.
As a dev you just use whatever features u like and can safely ignore the majority of them. Its very non-prescriptive unlike some languages where you must do a thing only one way.
Eg. Inheritance is pretty much terrible, I just never use it. Same way I try to not write bad code and instead write good code.
Also sure you need to put functions in classes, but thats fine, classes are just buckets for code. In any language you need to put your functions somewhere eg. in a text file.
- A base class and its implementation(s) must never be written by the same person.
Obviously a bit of a strong opinion, but inheritance should delimit layers of abstraction, technically separating the responsibility of developers.
Yep. That would be like having immutable lists which implement List which has an "add" method. Obviously that would be too stupid for a sensible programming language.
I've thought about this for quite some time and couldn't come up with anything that strikes a nice balance between simple and actually useful.
I admit I taught intro to C++ and used shapes as an example. I spent a few weeks building a little "ASCII render" thingy with a 80x30 "canvas" where each character is a pixel. Then you could place Shapes on the canvas and the shape would decide whether a given pixel was contained() inside of it.
I remember my first OO class where the car analogy was proven to be bad using the existence of the El Camino.
What I got as a side-effect was a bunch of re-written code. And I wondered:
Maybe OOP is a terrible paradigm. Maybe the designs that come out of it are so awful that it causes people to rewrite code, over and over. Re-examined systems are often better than first attempts. Once you reimplement something five or six times, you've probably explored most of the problem space and are pretty good at it. It's even likely that you are thoroughly sick of the project and just want to get the stupid thing done and shipped. (Yeah, your PMs and entire management chain just smiled behind your back).
In other words, it's Brook's "plan to throw one away, you will anyway" in action, while telling your boss that you had to rewrite your framework again because it didn't handle the flightless, swimming bird scenarios.
OOP wasn't meant to be used by professional programmers ever, but this is always always forgotten. Why do we still talk about OOP when it comes to professional / full-time programming? OOP was always meant to be about providing end-users a way to create programs & dynamic behavior without becoming full-time programmers.
- http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay...
- https://wiki.c2.com/?AlanKaysDefinitionOfObjectOriented
It's like listening to a conversation about cars where someone says they hate cars because you have to figure out a manual choke and the many inner tube failures.
Do people still use these crazy inheritance trees? I haven't encountered it since the early 2000's.