Model — Data only. Get methods, set methods, etc. It is isolated — it knows nothing about the View, nor the Controller.
View — UI only. Dumb or “humble”. Only shows what you tell it to, and never performs any transformation or validation logic — e.g., it always forwards input via an event/callback system. It is isolated – it knows nothing about the Model, nor the Controller.
Controller — Sits between Model and View. Does any data transformation (business logic) that is necessary to get the data from the Model to the View. Does most data validation on input that comes back from the View. It “knows” about both the View and the Model.
The essential ingredient in MVC is the Observer pattern. Take that out and all you have left is IPO (Input-Process-Output). The Observer is what maintains encapsulation and keeps MVC strongly object-oriented. Without the Observer, you need setters and “this object here fiddling inside that object there” – decidedly non-OO.
With this definition it becomes much easier to divvy up work between engineers. If someone is a database engineer, they probably don’t know UI. But they can write the Model to great effect, and hey maybe they can stub out a crappy-and-simple View. Maybe it’s simply a text dump of the data, that’s fine. At least the code-level interface is in place. Then the UI developers can come and write the Controller and View. Then when the UX designers change what they want the UI to look like, you can either change the View or write a new one. Or if you want to use different UI technologies — WinForms on XP, WPF on Vista, or Cocoa on MacOS, then just have different View implementations for each. Things like skinning are then easily isolated to the View pillar.
The other useful pattern to employ here is an Inversion of Control (IoC) container/scope object. None of the classes participating in MVC need to know about the specific implementation of each other, just the interface/contract. This makes it trivial to unit test the Controller with faked out View and Model implementations.
For example, let’s say you have a program that reads data from a database and produces a text report. Something a bit more complex than simple SQL statements. Now, imagine what would happen if your data source was changed. What if the database was completely refactored? How much of your program would you have to change? If you kept your data (model) separate from your business logic (code), the damage would be very limited.
What if you were told that you’d now have to produce not just the report, but an XML file? Again, if your output (view) was separate from your logic and data, generating the XML output should be very straight forward.
What MVC tells you is that your data, logic, and how you present the two to the user should not be tied so tightly together that changing one means changing everything else in your program.
Remember, MVC is just a good idea, and not the law. My criteria is: Does following whatever model help clarify what you’re programming and makes it easier to maintain? If not, I’ll break that particular model — whether OOP or MVC. The whole purpose of a model should be to make your program better, easier to understand, and easier to maintain.
Then again, you shouldn’t break a model just because you’re in a hurry or lazy. Whatever programming you’re doing has to be maintainable. One day, you’ll have to modify the logic, change the way the data is handled, or change the way it is presented.