requires modeling the model, rather than modeling the problem instance.
Modeling the model means that one must then use data
to construct the application. Thus the overall application becomes a pair: a process that can construct and maintain the application, and the configuration data used by that process. The data typically goes into a database (for robust management) or a pile of XML files (for the PowerOfPlainText
- users may edit these). It really doesn't matter where the data is stored unless one wants other cool features irrelevant to this page (like the ability to monitor these for updates and push updates back to them, live, while the application is running...).
This separation has many flexibility advantages. It readily overcomes problems of fragility and rigidity associated with 'pure' OOP approaches.
In this context, I describe a problem I'll call MirrorModelContagion
is when you need some sort of app-specific feature in an app-specific form. E.g. a particular operation upon opening or clicking a button in form_X. And, instead of making this app-specific feature available to the configuration file, you "hook it together" inside the application language by use of a special constructor-procedure or specialized 'form_X_model' class. This might occur as follows:
- you create a FunctorObject for your app-specific on-click action
- now, you need to assign this action to a button in form_X, so you create a form_X_constructor that creates the button, creates the FunctorObject, and attaches them together.
At this point, you've established precedence of what to do: you hook application-specific things together inside the application. So, now you need a button to open form_X. Let's say this button is form_S. Here's what you do:
- You create a FunctorObject for your app-specific form_S button that will call the form_X constructor.
- Now, you need to assign this action to a button in form_S, so you create a form_S_constructor that will create the button, create the FunctorObject, and attach them together.
Nice pattern we have going, isn't it? But then the problem gets bigger. If form_X had some other buttons... stuff that could have been described by simple scripts, perhaps, even those
need to be defined in application! Now the form_X_constructor needs to create some other buttons, describe them (stylistic options and all), build the FunctorObject
s, and attach them together. Same goes for form_S.
By induction, here is what happens:
- The entire application from form_X back to the 'root' form become a MirrorModel of the requirements.
- Then, induction the other direction, the entire application from the root back to the leaves become a MirrorModel of the requirements.
- The required support for external data is now eliminated entirely.
- The total amount of application embedded into the 'hard layer' is very high, which (according to most) is painful to implement correctly and easy to mess up in bad ways.
- The application is now rigid, inflexible, and nearly petrified as far as the user is concerned.
This isn't just a slippery slope. It's a cliff. And it's a real one.
It also happens to be core to Microsoft's "old" model for building applications (before WindowsPresentationFoundation
) and it is so well known that programmers often follow this approach without even wondering
whether there are better, more flexible, less painful
design options. Or perhaps they think of WebApplication
s as the only other option. Either way, they're stuck in that pitiful mental rut where it seems like things are the way they are because that's how it must be.
It's actually really easy to avoid the problem. Just one small change in the pattern (and you have choices, including):
- alternatively, provide an AbstractConstructor for FunctorObjects in general such that one can support an arbitrary number of scripting languages (ScriptingLanguageAgnosticSystem). Maybe use PluginArchitecture+AbstractFactory to add make the application extensible (so users can add entirely unanticipated functions to the application). Perhaps use PolicyInjection to select between competing implementations. This approach has a higher initial complexity to implement the frameworks, but also reduces the need to create new applications in the future (just use a new plugin for app-specific operations + new data configuration, and voila - new application).
The latter is considerably more flexible with only a slight increase in initial
implementation complexity. And, in addition to scripted events on input, it may naturally be extended to AbstractConstructor
s for other features, such as video-windows, clocks, fonts, data-sources and subscriptions for PublishSubscribeModel
rendering, GUI Engine selection, etc. With some care, you'll be darn well set for building some sweet, high-performance, runtime-reconfigurable, persistent, sharable applications. This is the marginally-OOP-mixed-DataflowProgramming
design I've been using very successfully in my work, and that's with only having bits and pieces of it implemented. The PluginArchitecture
is nice... it allows users to add features, and keeps the application tiny since it only loads what it needs.
Anyhow, upon choosing this alternative solution, you can simply access the desired feature from the 'soft layer' configuration data. You're back to describing models-of-models. The system is flexible again. It's also much less painful to create, since there is much less redundant effort essentially describing 'initial data' in a language designed for describing application components, and it is easy to tweak data then see a change (albeit perhaps after restarting if you don't have end-to-end DataflowProgramming
is a real problem with a simple fix. It's one of those things professional OOPers tend to learn by reinvention after a decade or so (depending on experience) then forget to tell the younger crowds. Hell, it probably has a different name - I'm one of those idiots that learned it by reinvention, so I'm just making a name up. Or perhaps it's one of those things that just 'click' at some point that tutorials don't help with, like recursion and monads.
is pretty simple to resolve if you are starting
with the PolicyInjection
approach. But there is another point to consider: if the application is instead starting
with the MirrorModel
application, and you want to refactor it to be more data-driven, the application will generally require a significant overhaul. You can simplify it a bit by turning some of the forms into 'primitives' (i.e. a "form_X" primitive available to the AbstractConstructor
). But significant overhaul is still necessary wherever the forms or form-constructors used to directly reference one another.
This topic would be greatly helped with example code, case studies, etc.
What would you show? The end result? just look at any old Microsoft application where every menu, form, button-command, etc. is hard-coded. That was already mentioned, and hardly needs code here. Would you show each step? Well, that might work, but you'd need to start with a data-driven application that constructs the app against a model-of-model framework (e.g. example code in PolicyInjection
), and show how it devolves from there into the MirrorModel
if 'app-specific features' are unavailable to the data-driven construction. I cannot imagine that being done in a reasonable amount of space.
If you have a good idea how to use example code to demonstrate MirrorModelContagion
, and you have TimeToMakeItShort
, then feel free to contribute.