views:

150

answers:

3

I have a widget that displays a filesystem hierarchy for convenient browsing (basically a tree control and some associated toolbar buttons, such as "refresh"). Each of these widgets has a set of base directories for it to display (recursively). Assume that the user may instantiate as many of these widgets as they find convenient. Note that these widgets don't correspond to any business data -- they're independent of the model.

Where should the (per-widget) set of base directories live in good MVC design?

When the refresh button is pushed, an event is trapped by the controller, and the event contains the corresponding filesystem-browser widget. The controller determines the base directories for that particular widget (somehow), walks that directory path, and passes the widget some data to render.

Two places I can think to store the base directories:

  1. The easy solution: make the base directories an instance variable on the widget and have the controller manipulate it to retain state for that widget. There's a conceptual issue with this, though: since the widget never looks at that instance variable, you're just projecting one of the responsibilities of the controller onto the widget.
  2. The more (technically, maybe conceptually) complex solution: Keep a {widget: base_directory_set} mapping in the controller with weak key references.

The second way allows for easy expansion of controller responsibilities later on, as putting things in the controller tends to do -- for example, if I decided I later wanted to determine the set of all the base directories for all those widgets.

There may be some piece of MVC knowledge I'm missing that solves this kind of problem well.

+1  A: 

Based on how the MVC methodology operates, I would suggest that you go with a modification the first solution you listed:

make the base directories an instance variable on the widget and have the controller manipulate it to retain state for that widget.

Why? You stated that the widgets are independent of the model, but are they actually referenced from within the models? If you're not binding your widgets to your models, you're straying from the basic concept of MVC.

I don't have any knowledge of wxPython, so I can't speak to how it conforms to MVC, if at all. Even still it seems to me that you should consider integrating the widgets into the models, or treating them as models themselves.

So if we assume that in this context the widgets are effectively part of your model hierarchy, this could possibly not only be the easy solution, as you said, but the right one.

Because one of the core fundamentals of MVC is maintaining loose coupling between each part, you always want to isolate your business logic from data input and presentation. Maintaining widgets that are displayed separately from models breaks this, so putting any kind of informational methods into your controller doesn't fit. You want a model to contain everything it needs to be manipulated or displayed by the controller when presenting data in your views.

Have you considered creating a superclass for all widgets so that there will be a common set of methods they will always inherit?

Example:

import os

WIDGET_PREFIX = '/tmp'
class Widget:
    def __init__(self, name):
        self.name = name
        self.widget_prefix = WIDGET_PREFIX
        self.dirs = os.walk(os.path.join(self.widget_prefix, name))

    def _get_base_directory_set(self):
        return self.dirs
    base_directory_set = property(_get_base_directory_set)

I hope this at least gives you something to think about.

jathanism
My hang-up is that the displayed directories are not "business data" (i.e. it's not serialized or stored away anywhere), it's just metadata about the state of the UI. wxPython is just a library for windowing systems, so the MVC separation is all my own effort towards a maintainable system.
cdleary
Well the per-widget directories are themselves stored on the filesystem, correct? So even if not in database column it's still data that is tied to the widget and therefore is part of it. I still recommend that you modify your widgets to either possess or inherit a method to display this information automatically rather than statically.
jathanism
A: 

The solution I've currently adopted is, "per-widget controllers." (Maybe there's an existing name for it.) It delegates to a "parent" controller for any UI-wide functionality, but exists to control the widget and associate any relevant data on a per-widget basis.

The "per-widget controller" concept avoids projecting any irrelevant properties onto the widget itself. You can extend these controllers to register/unregister the controlled widgets on creation/destruction for those times when you want to perform a many-widget operation, thereby avoiding weakref magic.

For example:

class FSBrowserController(wx.EvtHandler):

    """Widget-specific controller for filesystem browsers.

    :ivar parent: Parent controller -- useful when UI-wide control is
        necessary to respond to an event.
    """

    def __init__(self, parent, frame, tree_ctrl, base_dirs):
        self.parent = parent
        self.frame = frame
        self.tree_ctrl = tree_ctrl
        self.base_dirs = base_dirs
        frame.Bind(EVT_FS_REFRESH, self.refresh)
        frame.Bind(wx.EVT_WINDOW_DESTROY, self._unregister)
        self.refresh()
        self._register()

    def _register(self):
        """Register self with parent controller."""
        self.parent._fsb_controllers.append(self)

    def _unregister(self, event):
        """Unregister self with parent controller."""
        if event.GetEventObject() == self.frame:
            self.parent._fsb_controllers.remove(self)

    def refresh(self, event=None):
        """Refresh the :ivar:`tree_ctrl` using :ivar:`base_dirs`."""
        raise NotImplementedError


class Controller(wx.EvtHandler):

    """Main controller for the application.
    Handles UI-wide behaviors.
    """

    def __init__(self):
        self._fsb_controllers = []
        fsb_frame = FSBrowserFrame(parent=None)
        FSBrowserController(self, fsb_frame, fsb_frame.tree_ctrl,
            initial_base_dirs)
        fsb_frame.Show()

This way, when the FSBrowserFrame is destroyed, the controller and associated data will naturally disappear with it.

cdleary
+1  A: 

The anomaly (from the MVC viewpoint) that makes this design difficult to make MVC-conformant is that you want to display information that, by your conceptualization, "does not live in a model". There is no such thing as "information that does not live in a model" in MVC: its conceptual root is "the models hold all the information, the views just do presentation tasks, the controllers mediate user interaction".

It's quite possible that the information you're displaying doesn't "correspond to any business data", but (in an MVC worldview) this does not mean that info is "independent of the model", because there is no such thing -- it just means you need another model class (beyond whatever you're using to hold "business data"), to hold this "non-business" data!-)

So when the user "instantiates a widget" (creates a directory-display view, presumably by some user action on some master/coordinating view, possibly on another existing widget if "cloning" is one of the ways to instantiate a widget), the controller's responsible for creating both a widget object and an instance of the "directory-display model class", and establish connection between them (normally by setting on the widget a reference to the relevant model instance), as well as telling the model to do its initial loading of information. When the user action on the widget implies an action on the model, the controller retrieves from the widget involved in the event the reference to the model instance, and sends that instance the appropriate request(s) (it's the model's business to let the view[s] interested in it know about changes to information -- typically by some observer pattern; it's definitely not the controller's business to feed the view with information -- that's really a very different approach from MVC!).

Is the architectural investment required by MVC worth it, in your case, compared to a rougher approach where the information flows are less pristine and the model that should be there just doesn't exist? I'm a pragmatist and I definitely don't worship at the altar of MVC, but I think in this case the (relatively small) investment in sound, clear architecture may indeed repay itself in abundance. It's a question of envisioning the likely directions of change -- for example, what functionality that you don't need right now (but may well enter the picture soon afterwards) will be trivial to add if you go the proper MVC route, and would be a nightmare of ad-hoc kludges otherwise (or require a somewhat painful refactoring of the whole architecture)? All sort of likely things, from wanting to display the same directory information in different widgets to having a smarter "directory-information watching" model that can automatically refresh itself when needed (and supply the new info directly to interested views via the usual observer pattern, with no involvement by the controller), are natural and trivially easy with MVC (hey, that's the whole point of MVC, after all, so this is hardly surprising!-), kludgy and fragile with an ad-hoc corner-cutting architecture -- small investment, large potential returns, go for it!

You may notice from the tone of the previous paragraph that I don't worship at the "extreme programming" altar either -- as a pragmatist, I will do a little "design up front" (especially in terms of putting in place a clean, flexible, extensible architecture, from the start, even if it's not indispensable right now) -- exactly because, in my experience, a little forethought and very modest investment, especially on the architectural front, pays back for itself many times over during a project's life (in such varied currencies as scalability, flexibility, extensibility, maintainability, security, and so forth, though not all of them will apply to every project -- e.g., in your case, security and scalability are not really a concern... but the other aspects will likely be!-).

Just for generality, let me point out that this pragmatic attitude of mine does not justify excessive energy and time spent on picking an architecture (by definition of the word "excessive";-) -- being familiar with a few fundamental architectural patterns (and MVC is surely one of those) often reduces the initial investment in terms of time and effort -- once you recognize that such a classic architecture will serve you well, as in this case, it's really easy to see how to embody it (e.g., reject the idea of an "MVC without a M"!-), and it doesn't really take much more code compared to the kludgiest, ad-hoccest shortcuts!-)

Alex Martelli
This makes perfect sense and demonstrates my imperfect understanding of MVC theory -- for data that is "observable" (or shared across widgets), regardless of how "business" it is, the MVC pattern is more useful. Even though the systems I'm targeting have no inotify-like abilities (making the refresh command necessary), I could see retargeting this widget for platforms that did gain through observation. I think I have a much better understanding of where MVC practicality beats purity now -- thanks Alex!
cdleary
@cdleary, you're welcome! Yes, if you're outside of the Windows / Mac OS X / Linux / BSD zone, refresh is indeed necessary -- but a good MVC foundation will let you handle a variety of platforms, including both ones with, and ones without, such capabilities. Yes, practicality does beat purity, but MVC as a foundational architecture can be pretty practical (it's not the only one, but it does good service for quite a variety of useful features... _and_, it's not that hard to implement, either!-).
Alex Martelli