And then, 10, 20 years after the fact, people will start attacking popular implementations that differ from the original using some "new canonic interpretation" that is either extremely recent, or an interpretation that is old but was lost in time.
This is especially common around Smalltalk and OOP for some reason. Smalltalk's OOP is nothing like what existed either before or after, but since Alan Kay invented the term, Smalltalk is weaponised against C++/Java-style OOP. Not that C++/Java OOP is the bees knees, but at least their definition is teachable and copyable.
Design patterns suffer because in most explanations the context is completely missing. Patterns are totally useless outside very specific contexts. "Why the hell do I need a factory when I can use new"? Well, the whole point is that in some frameworks you don't want to use new Whatever, you dummy. If only this was more than a two-sentence blurb in the DDD book (and the original patterns book totally glosses over this, for almost all patterns).
And monads became the comical case, because they are totally okay in Haskell, but once it gets "explained" and migrated to other languages they become this convoluted mostly useless abstraction at best, footgun at worst (thinking of the Ruby one here).
I used to say things like this. M and V were always pretty unambiguous, but “controller” was kind of like “Christianity”, everyone talks like it’s a unifying thing, but then ends up having their very own thoughts about what exactly it is, and they’re wildly divergent.
One of the early ParcPlace engineers lamented that while MVC was cool, you always needed this thing at the top, where it all “came together” and the rules/distinctions got squishy. He called it the GluePuppy object. Every Ux kit I’ve played with over the years regardless of the currently in vogue lets-use-the-call-tree-to-mirror-the-view-tree, yesteryears MVVM, MVC, MVP, etc, always ends up with GluePuppy entities in them.
While on the subject, it would be remiss to not hat tip James Depseys MVC song at WWDC
https://youtu.be/kYJmTUPrVuI?feature=shared
“Mad props to the Smalltalk Crew” at 4:18, even though he’d just sung about a controller layer in cocoa that was what the dependency/events layers did in various smalltalks.
Another major aspect of the original "true" MVC is multiple simultaneous views on to the same model, e.g., a CAD program with two completely live views on the same object, instantly reflecting changes made in one view in the other. In this case MVC is less "good idea" than "table stakes".
I agree that MVC has fuzzed out into a useless term, but if the original is to be recovered and turned into something useful, the key is not to focus on the solution so much as the problem. Are you writing something like a CAD program with rich simultaneous editing? Then you probably have MVC whether you like it or realize it or not. The farther you get from that, the less you probably have it... and most supposed uses of it I see are pretty far from that.
Ultimately we want a nice set of reusable UI components that can be used in many different situations. We also want a nice set of business logic components that don't have any kind of coupling with the way they get represented.
In order to make this work, we're going to need some code to connect the two, whether it's a 'controller' or a 'view model' or some other piece of code or architecture.
However we choose to achieve this task, it's going to feel ugly. It's necessarily the interface between two entirely different worlds, and it's going to have to delve into the specifics of the different sides. It's not going to be the nice clean reusable code that developers like to write. It's going to be ugly plumbing, coupled code that we are trying to sweep into one part of the codebase so that we can keep the rest of it beautiful.
In contrast, pure model components tend to evolve slowly, which justifies the investment of a comprehensive test suite which verifies things like data constraints, business logic, persistence. If automated testing were seen as a priority, this would be a no-brainer for any serious app. However, testing tends to be underappreciated in app development. This goes some way to explaining why frameworks carelessly fold in M, V, C to the same component.
What would make more sense to me is to simply define the controller as an intermediary between model and view for updates in both directions. The controller would simply represent whatever needs to be specific to the particular combination of model and view in the particular application context. Depending on context, you might use a no-op controller when no customization is needed, as in the example of the article where a checkbox (view) is bound to a boolean property (model).
Like, why do we even need any of that stuff? I blogged about it [1] and spoke about it [2] and the post even got some HN love [3].
The opening parable concludes with this...
Multitudes of sworn "Rails developer"s, "Laravel developer"s, "Django
developer"s, "Next.js developer"s and suchlike throng the universe…
Why?
...
...
Once upon a time, there was one.
WebObjects.
Now they are numberless.
The occasional email and DM gives me succour that I am not alone in my confusion. Even people who've "grown up" using traditional MVC frameworks took a minute to self-check and felt "huh, looks like I can look harder at this thing that I do".Clojuring the web application stack: Meditation One
[1] blogged: https://www.evalapply.org/posts/clojure-web-app-from-scratch...
[2] talked: https://www.youtube.com/watch?v=YEHVEId-utY&list=PLG4-zNACPC...
deck: https://www.evalapply.org/posts/clojure-web-app-from-scratch...
source: https://github.com/adityaathalye/clojure-multiproject-exampl...
[3] discussed: https://news.ycombinator.com/item?id=44041255
165 points by adityaathalye 3 months ago | 39 comments
Also, a while back it was way less common for UIs to have backend services. Nowadays those have taken away most of the "model" side in a lot of apps.
With Observers:
We have hidden control-flow and lost intent. They subvert the readability of the developer's intention, in some cases they make you lose the callstack, and it has you searching the project on what code modifies a variable which is a lot harder than searching for a function call. Don't get me started on dealing with handling errors and out of order events. And oh man, is it easy to just avoid using encapsulation and creating a good interface/api for your piece of code.
Most of your code isn't re-usuable as you think:
A lot of things are naturally and forever tied together. Your UI is a reflection of _some_ model, The actions it can perform is based on it's current context, and if your UI changes then your business logic and model probably changes as well. This die hard need of separation and modularity only increases the complexity of the code with the majority of times the code not even being reused.
The only case that I've found somewhat reasonable to use observers is the database. What caused the database to change and effect it has is already pretty far removed from each other when a piece of UI needs to reflect the database.
Granted, It's possible to work around some of these issues, but please please I'm tired of debugging why a menu only opens 50% of the time because there is a piece of code several classes away from the context that doesn't fire correctly and looks like if (child.preferred.child.model.somethingElse.isFinished) { child.menu.child.openMenu = true }
This is necessary for zero trust in application design. Traditionalists really seem to struggle with this shift in mentality that, when you are designing a system, trust is where the problems come from.
Just as an example, collecting date inputs from a user might be three different fields in the view model and only one field in the data model, and be completely different data types (int vs datetime). If you are working with a client side application then you may not want to pass the entire object to the client because you don’t trust them with all the information, and you cannot trust them to maintain state, so you only transfer the date value in a data transfer object.
These are all models with wildly different intents. If you can’t understand the intent of this separation of concerns then you are designing insecure systems.
For example, what if you have two widgets that need to be side by side? And the user needs the ability to use the keyboard to switch between them? What if now you have a third widget below them that is also tabbed?
At this point you need a state machine to track the state and where the user is currently at. It's easy if this is done for you but pretty difficult otherwise in either architecture.
If you’re learning MVC, Scott’s OdeToCode/Pluralsight material still nails the fundamentals and the why behind them.
I haven't written Objective-C in a decade or so, but isn't this a pretty big mischaracterization of the language? NSInteger is a typedef to a C type IIRC, while there's NSNumber for the cases you want an object and/or are deserializing -- and which has observable propeties?
So like in React, you'd have your Redux store as the Model, React components (with useState etc) as the View, and then your Controller is the reducer which is called from UI code and updates the Model.
Maybe that's incorrect definitionally, but it makes sense to me.
This is in contrast to the 1990s model of web programming where you wrote an HTML page with a <form>, pointed the action to some URL, and that URL was a cgi-script that couldn't redraw the form so error handling was difficult.
In a lot of cases you could say the data fetching is a dependency of the view and not the other way around, for instance if it is a blog post you might have a model object for the actual blog post but then want to put arbitrary widgets into the view which in turn requires fetching whatever model objects are necessary to draw the widgets. From the viewpoint of a CMS user, for instance, they want to drop the widget into the template and have it "just work" (have the framework figure out the fetching.)
The first exposure a lot of people had to this paradigm was Ruby-on-Rails and since it had a rich model system people thought the model system was the important bit but I'd say the router is the most important bit and how you fetch the data and format it is secondary, in fact it's totally fair to use different fetching and templating paradigms for different pages that live under the same router.
UIKit is explicitly designed for MVC. If you want to write the most concise, performant, maintainable, UIKit code, you do so, using MVC, and classic OOP. I have tried other models, but they end up as messy kludges.
SwiftUI was designed to be more flexible, and can employ other patterns. I find that OOP is sometimes useful (especially for things like observable models), but there’s no reason not to do it, using other methods. It doesn’t force you to use anything in particular.
The main issue with SwiftUI, is that it’s still quite “unripe,” and we are limited in what we can do with it. I am looking forward to this changing, over time (it’s already improved, quite a bit). Time will tell, whether or not it can completely replace UIKit. I haven’t really been able to use it for any of my shipping projects, yet. I know of a number of apps that have, but I’ve been unwilling to make the compromises necessary, to do it, myself.
Some tools were designed to be used in certain ways, and coercing them into methodologies for which they weren’t designed, can result in a mess.
If I want to bang nails into a board, a hammer is the best tool. I have banged nails in the past, by flipping a screwdriver around, and using the handle, but that damages the screwdriver, and doesn’t work especially well.
But maybe a nail isn’t the best way to join the boards. If I use screws, then the join will be much better. In that case, the proper tool is a screwdriver. I guess I could still use a hammer, but the results are unlikely to be satisfactory.
Instead a model is one or more collaborating objects.
I'd like to add a couple of points to TFA.
Yes, it is absolutely paramount to understand what the model is. It is the abstract representation of the domain. The rest of the architecture serves the model and should be as minimal and transparent as possible. Particularly Apple-space code tends to get this very wrong by having very thin models and all the logic in the Massive View Controllers.
It is also important to understand that MVC is not about specific objects, but rather about roles and communication patterns. Different objects can have those roles, and they can actually be usefully combined at times (though do keep the model separate, please).
One crucial part of the communication patterns that TFA duly notes is that models do not know about views. That means that views only ever pull data from models, models never push data towards views. It also means that in an update, the model just says "I have changed". It does not send the data that changed. The "the model changed" notifications is also the only reason a view gets updated.
No, the controller doesn't poke the correct updated data into the view after it has notified the model, that leads to update chains and cycles. IIRC, that was one of the problems that React was trying to solve with "MVC", except it turns out that actual MVC never had those problems in the first place. Mis-application of MVC does.
Having the view always update itself from the model means that view state is always consistent, even if you miss or skip intermediate updates. Skipping intermediate view updates is important, because the model can potentially change a lot faster than the view can update, never mind the user processing those view updates.
Also, one common misunderstanding (that also leads to Massive View Controllers) is the mistaken belief that views must not edit models, that you must have a controller to edit it. That is actually not in the original MVC paper[3]. In the original paper, views can edit models and that makes things a lot more sensible.
Controllers are a bit of a catch-all for stuff that didn't fit anywhere else. The Views in Cocoa actually take over some of those roles, and that works absolutely fine. (Imagine my confusion when ViewControllers were introduced...)
[1] https://blog.metaobject.com/2015/04/model-widget-controller-...
[2] https://blog.metaobject.com/2017/03/concept-shadowing-and-ca...
[3] https://web.archive.org/web/20090424042645/http://heim.ifi.u...
A hundred people with their own clearly self-evident truth as to whether models are thin or fat.
Absolute burning certainty as to where validation logic lives.
And maybe 3 or even as many as 4 mad hermits who claim to understand what a Controller is.
Apple is to blame, as they give absolutely O clue on that part (only demo apps with structures that don’t scale)
Basically, the thinking was to let the programmer design the view and then implement the code-behind. I'll spare you from my rants about this, but it was popular.
Nowadays, with vibe coding, there is no need to use obtuse design patterns for the sake of RAD. Sensible architectures can easily by used by LLMs without sacrificing engineer or designer agility.