The controller pattern is awful, and other OO heresy (2013)
eev.ee
eev.ee
To this day, oop advocates can't even agree on what oop even is or means.
Apparently oop as envisioned by Alan Kay was supposed to work like cells in the body that pass messages between each other and take actions independently.
Why? Who knows! It was never really explained why literally one of the most complex systems imaginable, one that we still really have very little idea how it even works, should be the model for what could and should probably be a lot simpler.
Today's modern oop languages are probably very far from what Kay envisioned (whatever that was), but it's remains unclear why classes and objects are "better" than the alternatives.
And before anyone goes and comments aksully code organization blabla like yes but code organization can be great or shit in oop or fp or procedural codebases, it has nothing to do with the "paradigm".
Let alone that the entrenched, canonical, idiomatic coding styles of most modern oop languages encourage state, mutability, nulls, exceptions and god knows how many trivially preventable entire classes of errors. Granted, most have now started to come around and are adopting more fp features and ideas every year, but still.
Don't get me wrong, writing programs like cells in the body that pass messages between each other and take actions independently is an interesting idea which deserves pursuing, if nothing else but to satisfy our curiosity and seeing to what if anything it's applicable and suited. (and even if the answer turns out to be "nothing", we've still learned something!)
But going from there to making strong claims about it being a more or less universally superior paradigm for computing and writing code, with little to zero evidence, that's a huge, huge stretch.
To the degree Erlang and Actors work, I think that's kind of a happy coincidence, and not due to any rigorous work on Alan Kay's part.
From my understanding, it is that every object has a very limited set of functionalities it manages. And that a goal is for each object to know as little as possible of the outside world. And along with that, to not bind to specific objects/classes but to bind to specific messages (Including multiple dispatch)
IIRC he was interested in an abstraction that worked "all the way up" - he wanted the abstraction to essentially be a tiny computer. It's a cool idea with a lot of power. But it really is too powerful for applications, and leads to the same complexity problems you get in large distributed systems. And to not have 'function' as a primitive is inexcusable in any language.
Java's lack of a function primitive is a key weakness. It means you must have at least one "utility class" per project - a public class with static methods that are themselves simple functions. (The relatively recent introduction of lambdas, or anonymous inner classes, does not really address the problem. That's just ugly syntax sugar).
print('hello world')
is a waste. If the language violates Exupery's law why shouldn't developers?People who have been harmed by too much oop won't just define a function and call it. They'll define a class solely to contain that function and then call it more verbosely.
On the face of it it's not that bad but once you start tolerating a habit for doing things because that's how they're done and not because you actually think it's the right way you end up picking up all of these fragments from places where they do belong and forcing them into places where they don't.
Or at least, that's what I struggled with when I was new and insecure and wanted people to look at my code and think that I knew what I was doing because I showed evidence of knowing the dogmatic patterns, and I've seen it in others too.
A lot of the criticisms being leveled here at oop actually me question their programming experience. There are many valid criticisms, but not a lot levelled here.
Obviously that's an amateur move (and being an amateur myself I didn't have the confidence to show him a better way). But then it's never the skilled practitioners that make something look bad anyway.
See Steve Yegge's rant: https://steve-yegge.blogspot.com/2006/03/execution-in-kingdo...
But the parent went so far as to say that scoping functions to a class name is bad and I just don't see it. I think a pure global scope would make Java worse.
I do think both languages would benefit from allowing/ encouraging namespace-level functions (C# comes close with static classes but that's pretty clearly an abuse of any logical definition of the word "class"). However I probably wouldn't use them for implementing "controllers" in web applications - while I tend to agree it's not a good use of OOP, having key information about the current request/response available via properties of the current controller (and hence kept separate from function-level parameters that represent semantics specific to the endpoint being handled) plus various built-in functionality at the framework-supplied "controller" level isn't something I'd see a benefit in giving up.
But I'll admit there are few cases where a single controller class with methods for various CRUD like operations on a particular resource type makes any sense from an OOP perspective. I did think for C# what might actually work is to be able to define extension methods on the framework Controller class and have an automatic routing mechanism based on convention (e.g. if you defined an extension function "Get_Widget" then GET requests to /widgets would be automatically routed to it. Or it could be based on the namespace). But in general it would likely make the code more verbose (lack of implicit "this") for questionable gain.
Is that worse than the global scope as the dumping ground?
One other benefit of forcing classes is that there's a logical place for private functions and fields. Private state is an anti-pattern to some but it's more consistent design for Java, I think.
I wouldn't have a problem with requiring everything to at least be in a namespace, though it needn't be enforced at the language level (vs a linter rule).
I guess I don't see the problem here; what's wrong with this idea?
I've seen people claim "It's not OOP" but without any ability to explain why and I'd love to see the practical difference.
Using static methods on a class seems perfectly fine to me; that's what they're there for. If you have a handy utility class to work around problems in the standard library, then that's an issue with the standard library; Java has this but has gotten better over the years.
(Please don't take this comment as an endorsement of OOP as practiced in the corporate world or that traumatic memory you have of an OOP codebase.)
*bear with me - admittedly there are languages/technologies where directory structures are required, but hopefully you get my point.
class {public function call() {...}}
then it is going to be unergonimic to write what other languages write as array.map((elem)=>{...})"The HandlerCreationFactory created a Handler based on the Request." (oop)
"A Handler is created based on the Request." (passive) or "The program (or process) creates a Handler based on the Request. "
Only that the first one needs a lot more nouns, even when they look silly.
https://en.wikipedia.org/wiki/Subject-oriented_programming
https://en.wikipedia.org/wiki/Aspect-oriented_programming
"A block is essentially an anonymous function."
[ :x | 1 + x ] value: 2
⇒ 3
https://cuis-smalltalk.github.io/TheCuisBook/Block-syntax.ht...I also think this discussion shouldn’t be about languages at all, you can have objects of some form in any imperative language including C. It’s more about what design patterns make sense for specific problems, and sometimes the answer is OO-style (eg with class hierarchies) and sometimes not.
Then I ran the game. And it crawled at 15fps and I wondered why.
Of course I was new to programming so I can’t just blame OOP but it was my first lesson in how OOP isn’t a magic wand by any means. And later I got into more functional programming and today I find myself writing function-based components in react for a living and loving it. It’s funny how things go like that.
https://cs.stanford.edu/people/eroberts/courses/soco/project...
For the higher-level code, it's often less useful.
I mean, the only clue we have is that it is some paradigm that isn’t OO, but that leaves... a whole lof options.
Do functional languages actually discourage "state, mutability, nulls, exceptions"
I know why we might argue against these features, but implying that functional programming doesn't have those features seems a little weird (or equivalent e.g. call/cc can cause execution flow less predictable than exceptions)!
Same goes for evidence based ideas. Odds are stupid high that you cannot find a single large scale codebase that has succeeded using any "pure" technique. Heck, I'd take small scale codebase for a fun thing to look at, at this point. Make performance a requirement, and really get ready for tears.
When I think of programs I think of modules of code that act like machines on a conveyor belt or assembly line operating on and pushing data along from station to station. This is why Erlang, Go and Plan 9's thread(2) library are how I want to program.
> To the degree Erlang and Actors work, I think that's kind of a happy coincidence, and not due to any rigorous work on Alan Kay's part.
https://www.infoq.com/interviews/johnson-armstrong-oop/ (3. Is Erlang object oriented?) three points: message passing, isolation between objects, and polymorphism.
The industry seems to have ran away with #3 while ignoring #2 and forgetting completely about #1.
> Why? Who knows! It was never really explained…
Perhaps "Design Principles Behind Smalltalk" ?
https://www.cs.virginia.edu/~evans/cs655/readings/smalltalk....
State isn't a feature solely of OOP. It's very easy to see the FP craze as a similarly dogmatic insistence on pure everything as removing useful features instead of preventing errors.
If FP truly was the indisputable way of the future, Scala would be the CRUDspeak of choice by now.
Is-a vs has-a is a lot more clear in context of widgets and windows. It still isn't perfect, most popular gii used inheritance whereas composition would probably be better.
Inheritance ended up as a core component of oop principles when ultimately it is a flawed approach compared to more flexible composition methods.
Liskov substitution was a massive success, classes were an improvement over structs, private vs public was generally a good idea even if the astro architects went nuts with it.
I'll bite. Having worked on "enterprise" application the OOP (or more like the dreaded enterprise patterns) have sort of "clicked" to me.
An aside. Git-flow is more than decade old at this point and people still have heated debates from time to time on whether that approach is any good. In essence git-flow merely introduces organization to patchsets. If you are building a SaaS like system, most probably every environment you have runs "current" code and each stage (prod, test, dev) is merely a shifted pointer that will eventually catch up. Why would you structure your teamwork around identifiable patchsets, why would you keep that information around when you do trunk-based development? There is seemingly no need to do that and you would rightfully diss on git-flow as overly complex.
However, if you build hardware appliances or installable offline application with multiple versions (think e.g. MS Office in 2000-2010 - multiple versions and tiers of those out in the wild under support), suddenly there are situations where you do in fact need to checkout and work on an exact patchset that is installed at customer site. If your field requires any form of certification, you will quickly learn that having patchsets provably not touching frozen (already certified or submitted for certification) code is highly valuable. Ability to cherry-pick a hotfix can save organization man-months.
Back to code organization. So you have this "enterprise" application that has various software modules/components, some of which are built by external contractors, some are off-the-shelf components, some have been left and forgotten five years ago and for one reason or another you want to work on one of these components. Whatever team you assemble, majority will have zero understanding of assumptions baked into code. Documentation, however meticulously maintained, will still leave holes in understanding. How do you make changes to a decade old component that is used in various weird ways all throughout the project with any amount of confidence that you are not breaking stuff at a distance? Apparently, OOP enterprise patterns that introduce decoupling points for implementation details and pass around objects with assumptions abstracted away are pretty solid barriers. They are cumbersome, difficult to use at early stages of development when all complexity of the task can fit into the heads of programmers implementing features. Similarly to git-flow, the advantages of these structures only become apparent when you start needing them and that will quite probably be when the principal engineers greenfielding the project have long left the company altogether.
A very specific example: factory pattern. Why would you have a layer of indirection to simply instantiate an object? All refcounting shenanigans aside, you just cannot change interface of object/component without affecting call sites. Call sites that may be outside of your control, code frozen or simply too dreadful to touch without strong justification. Factory pattern introduces a decoupling point between call sites and implementation. Having a factory you can implement interface shims and leave call sites untouched while having the freedom to rework component and its interface. You just cannot achieve that without OOP.
> Let alone that the entrenched, canonical, idiomatic coding styles of most modern oop languages encourage state, mutability, nulls, exceptions and god knows how many trivially preventable entire classes of errors.
I will agree that OOP implementations in mainstream languages leave much to be desired. That is part of the price we pay for backwards compatibility.
> I have a hypothesis: this pattern is so common for the simple reason that Java doesn’t have first-class functions.
She’s picking on Java deservedly because Gosling made what in retrospect was a mistake to go so all-in on classes, and because it became such an important pedagogical and deployment language (and nobody will shoot at you if you’re unsuccessful).
Say what you will about C++ but it took a different path, not only changing its name from “C with Classes” but making classes a tool for manipulating the type system (and not the only one) and supporting the true benefit of a class system, generics.
Edit: I changed an incorrect "He" pronoun to "She". Thanks to quickthrower2 for pointing out this embarassing blunder.
IIRC, Kay's vision is that OOP is about messages.
I have my own pet theory, of course it must be wrong too, but if I may, only as food for thought: The core usefulness of OOP is usability. A language is a matter of connecting thoughts through a mental model. Subject, verb, predicate. Object, method, parameters. That's mostly it. The rest are implementation details and lots of bikeshedding.
The idea is that you have classes which model state. And then you have generics that model functionality. And you define methods which provide an implementation of the generic for a class. But it's more flexible than what you see in Java because such a method can be easily added after the fact as it's not intrinsically part of the class' definition.
If you're familiar at all with Typeclasses in Haskell & Scala, personally I find those to be similar enough to get the gist. Likewise Dylan and R's S4 objects are modeled after CLOS' structure.
I also enjoyed the ability to not be locked into the rigor of a class structure when it comes to methods. Since CLOS is function based, it's trivial to add functions to a class that you don't even have the source code to.
There have been many times working with a 3rd party or system library where I've had that "if only I had this little method" moment that would make my life easier. I'd rather have that capability and fight, say, namespace issues for the "Well what if everyone added their own 'upshiftFirstLetter' method to the String class" problems on an ad hoc basis.
Part of this, of course, stems from the locked down nature of the scope of classes. Not having access to internal structures and state. I'd rather take those risks of leveraging internal state knowledge not supported by the original designer vs the alternatives of sometimes having to throw out the baby with the bathwater.
Yes. The actor model is closer to what Kay proposed than the C++/Java style of OOP.
This is the reason I find it useful. To me, OOP is as much about your organization as it is about the best way to load, transform, present, edit, and store data. I think the culture of some companies lends itself to various kinds of programming, but it's the cultureless companies where OOP is most useful. The places where nobody is trying to change the world, where people work to pay their mortgages, where an executive may only work for two years and a programmer may only work for six months.
It's in an environment like that where a self-documenting, self-configuring code base with custom classes and exceptions that guide the next developer is essential.
Every developer should have two users in mind. The person using the software, and the next developer who maintains the software after you're gone. OOP is a great way to empower the second user when the only thing that will reliably outlive the developer is the code base.
For smaller projects I don't care one way or another about OOP practices, but once you start getting into hundreds of thousands of lines of code IMO it becomes an absolute necessity.
If I create a REST API no one complains they don't have access to the inner-workings, local variables, etc. But if I give a similar experience and call it a "class" suddenly it's ugly and mean.
The other side is that classes aren't the only way to get this sort of encapsulation. The classic example is closures - data inside the closure acts as the encapsulated data, and the returned type of the closure is its public API. ML languages typically use modules in place of classes - the module signature defines the public API, and rather than calling methods, you instead call functions with arguments (not `list.length()` but `length(list)`). But again, that's just syntax - we're keeping the same encapsulation because the module-defined functions are the only ones capable of fiddling with the value's internals. You also see this in Rust, which does use method syntax, but has traits and types that act more like modules than typical classes.
All in all, I don't think anyone's complaining about encapsulation, but rather it's a question of whether typical OOP (with all that that typically includes) is the best form of it.
Code is much easier to deal with when you define abstractions around small sets of functionality and then allow the caller to pass in various objects that provide those functions to the code that needs it.
You can have an application that accepts a 'data_backend', and then provide a data_backend that just stores information into a dict for testing and getting the app initially written, one that tracks all of the changes made or exposes them for tests to check, or another that stores it to sqlite for running a local instance, and another that stores it to some real database for a larger one.
The calling code doesn't need to know what the data_backend does or how it works, it just tells it "store this", "read that" and the data_backend does whatever it does and data goes in and out of it.
You build up all your code that way and you'll be able to easily stub chunks and replace them with functional implementations, and then swap those out when needs change or by options given at runtime.
It's a lot easier to read and write than code that's littered with a million if statements trying to keep track of too much complexity in too many ways all at once.
OOP is just syntax sugar and compiler constraint enforcement for the same kinds of things you see the linux VFS do. There are many filesystems for linux, but each just provides a handful of functions in a structure that should be called against the structures representing the various types of filesystems. In Linux's C you have to slap it all through a (void *), but in languages with objects, you can use those as the medium of abstraction instead of doing it manually.
Some make you do a bunch of inheritance garbage with stapling objects together, others will let you build to interface definitions, or just check the structure of the object to see if it matches the needs of the caller, or be a dynamic language that just checks for the members at runtime.
Maybe one day modules will hit the mainstream.
There's very few actual rules despite opinionations derived from arbitrary paradigms and what should be done to work cohesively as a team. I wish there were a smidge less ivory towers and a smidge more common language.
Incidentally, this is exactly how I came to "reinvent" OOP as a newb programmer. I was working in a codebase where I didn't know what tools I had at my disposal, but we had a few modules where I could just type `module.` and see a list of all the methods, right there in front of me (in VSCode's intellisense). I asked, "Why don't we do this for our helper functions?" and we ended up with a handful of major objects to import with easily discoverable methods.
now of course I could have crawled the codebase to get the same information, but for someone new to programming and/or someone brand new to the codebase, that isn't necessarily a good use of a time. it can be a lot of slow unraveling of "what goes where", whereas organizing things in "OOP" (I still don't know if I'm actually using this term right because it was just part of how I learned, without being named) teaches that information more quickly through use and experimentation. basically what I was looking for was "namespacing" I guess, but I would also say it helped organize our code in a more useful way.
That and Simula proceeded Smalltalk, which C++ was inspired by, even if Kay coined OOP.
There were a lot of assertions in this article that rubbed me wrong, but this one was particularly egregious. Handlers are behavior. They handle something.
This feels like the kind of article I would've written early in my career when I was getting really clever with stuff and starting to be able to question the way I understood the world of programming.
So, Handlers are functions that "handle something", but they are also part of the app object's state, not its behavior.
An alternate view might be:
Handlers are functions that are part of the app object's state, which alters the app's behavior.
In the majority (possibly overwhelming majority) of the cases this is true. The purpose of Handlers and similar Publisher/Subscriber patterns is effectively to change application behavior when they are configured.
I find that phrasing very fair.
class App {
public IHandlerFoo Foo; // routed as /app/Foo
public IHandlerBar Bar; // routed as /app/Bar
...
}
...
App.Foo = new HandlerFooInstance(...);
...
Foo, Bar, etc. can evolve under an app's state changes, so the handlers to invoke are states of the app. They implement behaviour too, yes, but the original wording isn't wrong either.I agree with you about service classes. I think one of the things that service classes do well that regular functional code does not is indicate to the consuming developer which parameters are meant to be provided by the environment and are largely static, and which are meant to vary per invocation.
Lisp does this instead with dynamic scope. But dynamically scoped dependencies felt too much like global variables to me. The service class instance has its scope bound to it and it won't change. But dynamically scoped functions always felt like the ground could get ripped out from under me at any time.
Working in functional langs I just do DI with higher order functions.
Service classes are "configurable functions", as I tell my team. With the advantage that you don't have to monkey-patch an import in another module to do a unitary test of the function, but rather you can inject mocks!
One thing I do insist on is that a service class either does something (calculate some value, retrieve some information, etc.) xor it coordinates service classes (parse value, then call the validator and, finally, persist value). This aims to prevent deep call chains where every function in between does a little bit and calls someone else, which can be hard to reason about.
Most (if not all) classes had a single method and implemented BiFunction. The app was wired with Spring... if you had the bad luck of using Spring you can get the idea of how ugly this was. All method invocations were `apply`s and navigating the code was as easy as finding a needle in a haystack in the middle of the night. A good amount of the wonderful features of a tool like IntelliJ couldn't be used.
My rule of thumb is to follow the grain of the language / framework / library as to produce as little surprise to other devs.
This is really it. It's not a game of all-OO, zero-OO or strictly following a pattern you saw on YouTube that one time. It's all about nuance and minimizing the # of moving parts until each is really serving you some value.
I am in the process of ripping out a bunch of class bloat in a product iteration. Our typical pile of model class POCO spam for "one thing":
Entity.cs --The "official" view
EntityController.cs
EntityView.cs --Another common view that became popular over time (???)
EntityTableRow.cs --gotta have that 1:1 sql model, amirite?
EntityViewForTypeA.cs --what was wrong with the common one :(
EntityViewForTypeB.cs --And again...
...
Ultimately, every time we wanted a "view" of the data, we wound up creating another type to enshrine this and then tried to figure out how many holes we could more-or-less hammer it through. Every one of these has some cranky mapping layer too (NxM if you want to get crazy).The alternative pattern I am working with is to directly query my database in the exact place the data is required and to use anonymous / scalar return types like so:
//Some function that produces a HTML partial view for a webapp
//View w/ join that would otherwise require a new POCO type to communicate.
var myEntityView = await sql.QueryFirstAsync(@$"
SELECT e.Id AS Id, ep.Name as Name
FROM Entities e, EntityProperties ep
WHERE e.Id = ep.EntityId
AND e.Id = @Id", ...);
//Direct usage of anonymous result type.
return $@"
<h3>Entity:
<a href='Entities/{myEntityView.Id}'>{myEntityView.Name}</a>
</h3>";
In the above, there are zero POCOs or "messenger" types. Just a raw query right where it needs to be taken, without any extra ceremony or implications for other code sites. I actually don't have a single type in the new codebase that is a POCO, other than schema tracking models (which are still useful for interpolating into queries to enforce magic strings). Effectively, the only domain data types are 1:1 with the SQL schema now.Minimizing the number of moving parts shouldn't be your goal.
Testability of the moving/complicated parts should be.
I don't really care about how you break up your code, but it should be trivially testable, at both the unit, and the integration test level. OOP makes it at least possible for less experienced developers to accomplish both of those things.
It's nice that you got rid of all the indirection and all the nearly-pass-through interfaces in your code sample, but how would you test it? In a unit test? In an integration test? Are you going to need to dial out to a real, live SQL database to do so? For every test? Will you be re-using the database across multiple tests, and thus, require all your tests to avoid object collisions? Paying a startup cost for every test you run? Reusing it for some tests? Who's going to maintain that reused, QA database? Who's going to deal with it when some clown pollutes it with garbage?
These are all solvable problems, they have multiple approaches to solving them, with varying tradeoffs, but they are tradeoffs. 'Simpler-to-write' code is usually not the tradeoff you want to optimize for. You write code once, you test it thousands of times.
> Are you going to need to dial out to a real, live SQL database to do so? For every test?
Yes. This is actually how we do it. And if you think deeply about it, this allows you to skip seamlessly between unit & integration testing, assuming all of the system components are designed against the same database. The only meaningful difference between unit & integration in this context is how much state you allow to accumulate in the database between invocations. You can reset for each method using a known expected initial state for each, or you can stand up an initial state and fly through an entire playlist of method calls.
If all of your functions take a SqlConnection object as their first argument and you've made sure that 100% of application state resides in the database, what can't be tested in this manner?
This is a terrible idea IMO. Creating a POCO per view is not a big deal, you're already implicitly depending on a specific type contract in the view, except now it's implicit and dynamically typed and so subject to all of the problems that follow from that.
If it works for you and your team, it works.
Here's my pattern that I use instead of a MVC/MVVM/MVP/etc/etc: I have a handler function to choose what url and routine I need and it calls some functions and methods and then renders some json / templates out some html.
I think a good rule of OOP is to try to write everything out as functionally pure as possible and refactor into classes when you see data AND functionality blocked together that you can bottle up into an object. To start ranting, I think a lot of the OOP insanity you see in .net/java/etc is from dogmatic approaches to unit testing over there and the common examples of how to do it involving the mocking and interfacing that end up obfuscating the actual production code.
The exceptions are those which were ported (unittest was a clone of java 's framework incl method names).
That means hiding state, and having it spread throughout the app, in places that are hard to find, which is error prone and leads to complexity. And monstrosities like FactoryFactoryFactory [0] and Kingdom of nouns.
[0] https://factoryfactoryfactory.net/
[1] http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom...
Golang has first class support for bundling state and behavior: Receiver-Functions, often called methods. This means exactly 2 things; a) That for a type `Foo` I can do this:
f := Foo{}
f.funcThatUsesFoo()
instead of this: f := Foo()
funcThatTakesFooAsFirstArgument(f)
and b) that Foo implements all interfaces whos method signatures it's receiver functions satisfy.It doesn't mean that I now have to suddenly drown my code in over-architectured patterns just to satisfy some OOP sense of code-aesthetic. It doesn't mean that I have to try and hide state from code in the same module. It doesn't mean that I have to bundle things that could be free-standing functions into some type just because. It doesn't mean I have to make any of the mistakes OOP popularized over the years.
Last pop quiz: what makes Python an object-oriented language?
Ah, hm. It can’t be classes, or I’d tell you you’re wrong. So what is it? [...]
self? No, that’s not a keyword or anything; it’s just the de facto standard name
for the first argument. So what makes self work?
That’s close enough, really. The answer is descriptors, which are basically “the
things that make self work”.
From another blog post on the same site [1]: A descriptor is just an object; there’s nothing inherently special about it.
Like many powerful Python features, they’re surprisingly simple. To get the
descriptor behavior, only three conditions need to be met:
1. You have a new-style class. [...]
It therefore follows logically that Python code using old-style classes is not object-oriented, and that Python was not an object-oriented language prior to the introduction of descriptors.Exposing "descriptors" is a latter thing, but their default implementation was already a thing before they were visible to python users.
A counter example of a good bag of function is every languages Math static class.
Also it is quite viable to have a bag of data and multiple different classes which have algorithm which operate on that data. Those algorithms should not be trivial (but there is no good definition of what trivial is or isn't). If you toss all those algorithms into the bag-o-data class you will wind up with a massive class which changes under too many circumstances and clearly violates separation of concerns.
If you have a three line function that should clearly not go into its own class. If you have a dozen methods that implement an algorithm on a class, and the private methods don't need to be shared amongst other algorithms, and perhaps you have some private state dedicated to just that algorithm that doesn't make sense to have in the data-holding class, then you should probably extract that into a stand alone class. Where is the actual dividing line? I don't know. Wrestling with it all right now with some code that I've been writing for the past several weeks. It doesn't help that I don't yet have a second algorithm that hits this data, so there's no good dividing line for me between what should be private to the algorithm and what should be public hanging off the data.
It is not anywhere near as clear as the author of this blog makes it out to be though.
And the algorithm I'm implementing is a Job, which inherits from BackgroundJob as well. The horrors.
(I do at least have three different BackgroundJobs now which all need common task handling and cancellation/timeout kinds of logic wrapped around them so that's a bit more clear what that abstract base class should look like).
IMO, this is being almost willfully obtuse and pointlessly pedantic.
For one thing, the instance, not the class, is the thing most clearly described as the controller in this case. It's true that there's no need to have instances for this static logic, but it's also true that some routing behaviors have their own dynamic data (like caches or database connections, etc.) So a point of just always using instances is that you don't need to know or care externally or ahead of time whether or not such data exists (and to provide something better than singletons or worse things to manage it when it does.)
I'm not a big fan of this form, BTW, though it's not on OO grounds. It's a code and logic organization thing. When I've seen this in real life it turns into spaghetti code of little objects connected in a tangle. I think it goes down like this: people start by encapsulating little steps of processing, I guess because they image they are independent. At this point it's OK -- a sequence of objects connected in a chain of references matching the processing steps. Then they realize they aren't independent, but instead of unwinding the chain, they add more objects -- to hold bits of common context -- and connect them here and there. The spaghetti is starting to tangle. (Mistakes are always made at this point -- e.g. there's ends up no coherent way to update this common data as things change.) The thing grows 10x and these kinds of things are repeated ten times and you have a big ball of spaghetti. Oh, and someone introduced a DI framework to solve these, which helps a little, but also introduces its own complications.
- Normal function declaration in global and "module"/namespace scope.
- Anonymous functions that can also be assigned to variables (except in a few cases, like object properties)
- Anything extending the Closure class
The last one is obviously a nod at what is going on under the hood, but as far as the developer writing the code is concerned, it doesn't matter.
A library even exists to explicitly provide some functional programming functions under a namespace for easy inclusion with other projects. There is no requirement that all functions must be defined in a namespace, as evidenced by the first 10 years of crappy PHP code littered with functions in the global namespace!
https://github.com/lstrojny/functional-php
Edit: formatting
And what does this mean?
Quick: off the top of your head, what makes JavaScript an object-oriented language?
If you thought “what? it’s not!” then there is no hope for you and you should go back to C++.
Ironically it's the C++ programmers that have pretty much collectively understood that classes are for maintaining invariants, which this person is almost about to understand in this article (I hope they did by now).A sprinkle of clang blocks and Block_copy on top of your structs and you’re on your way!
The key insight of eevee's article is that OO has succeeded mostly because bundling state and behavior together is a really useful way to structure your code and is the thing that you should leverage the most out of object oriented languages.
Macros have a much longer history in purely functional languages (in particular, lisps) than they do in procedural or OO languages. So you could argue that this problem is actually much easier to solve from a functional approach.
And I'm not sure why you think that's unergonomic. If you want a bunch of functions to share the same namespace, just put them in the same file. The name of the file becomes your module name.
I would put C++ and PHP in second and third place, and they have the same flaw.
PHP supports first-class functions.
1. Don't do that. Don't have multiple threads messing with the same data.
2. If you have to do that, use a mutex/semaphore/guarded region.
So far, so conventional. There's nothing about OOP in that.
The difference with OOP is that only member functions can access that data, so you have a strictly limited number of places you have to think about protecting. OOP makes that aspect easier. Each class just protects its own data.
Of course, you still have to worry about "deadly embrace", and by putting the semaphores in the class, it can make that aspect harder to reason about.
> It's only when you commit fully to functional programming and have immutable state that you truly unlock the power of object-oriented programming.
Um, no. Almost all real programs have mutable state, and the state has to live somewhere.
I work in embedded systems. There's often a lot of persistent state, and, worse, shared mutable persistent state. FP isn't going to make that go away; it's just going to distribute it differently. But if I have to reason about the shared mutable state, then a distribution that pretends it isn't there doesn't make that easier. (Pure functions, on the other hand, do help.)
I mean, look, FP is absolutely right that shared mutable state is evil. Avoid it... if you can. (And think harder about whether you can.) But sometimes the very nature of the problem is shared mutable state, and when that happens, you need tools that help you deal with it, not tools that say "don't do that".
The state lives in the process and you can have millions of processes if you'd like. If you want to get some bit of state then just send a message to the process that manages that state and it will give it to you. It's like traditional object oriented programming except the objects are alive.
I've never thought, 'Gee, this game would be so much easier to program if I had to copy every data structure when I wanted to update it, and have programs be impossible to step through because I'd rather write a pure program with no good debugging tools just to sound cool.'
Exactly correct.
See also "Execution in The Kingdom of Nouns"
there are relations between things that can be mapped well with inheritance, but thats a problem, we don't really want code to represent the thing we're coding, code needs to be more dynamic easier to read, change and move around, etc. representation should be between the end result of the code and the thing we're trying to represent
Runnable r = () -> doSomething();
Supplier<String> a = () -> “hello world”;