2

I'm making a calculator app to learn Objective-C and maybe improve my OO design skills a bit. In an attempt to do things more MVClike, i have separated the actual do-the-calculator-stuff code from the view controller. For every action, pretty much all the view controller does is tell the "model" to do the operation meant for that action.

Thing is, that gives me a bunch of methods that do basically nothing but forward the action to the model, like this:

- (IBAction)clearAll:(id)sender {
    [self.model clearAll];
}

- (IBAction)clearDisplay:(id)sender {
    [self.model clearDisplay];
}

- (IBAction)clearMemory:(id)sender {
    [self.model clearMemory];
}

- (IBAction)storeMemory:(id)sender {
    [self.model storeMemory];
}

- (IBAction)addMemory:(id)sender {
    [self.model addMemory];
}

- (IBAction) subtractMemory:(id)sender {
    [self.model subtractFromMemory];
}

- (IBAction)recallMemory:(id)sender {
    [self.model recallMemory];
}

Objective-C so far seems outrageously flexible with dynamically forwarding messages, and these methods are alike enough to look rather easily automated away. Do they really have to be there? Or is there a less repetitive way to tell the controller to just pass certain messages through to the model (ideally, while stripping off the sender arg)?

I've been looking a bit and trying some stuff with selectors and NSInvocation, but it seems like that'd mess with Interface Builder by taking away all the (IBAction) markers that let me hook up buttons to actions. (I'd prefer if the view didn't have to know or care that the controller's just forwarding to the model in these cases.)

So, is there a less repetitive and/or hacky way? Or is it not worth the trouble? (Or is it a bad idea in the first place? Or is this trying to make the model do too much? Or...)

cHao
  • 84,970
  • 20
  • 145
  • 172
  • It's trying to make the model do too much. Remember what it stands for: Model View Controller. Only make the model do what it has to do; data management. For example, the model should not be clearing the display; it's the DATA. The data shouldn't even know the view that's going to be cleared EXISTS. The controller gets data from the model and displays it in the view. Ask yourself whether or not the task at hand requires data from the model before you do something; if it doesn't, you probably shouldn't involve the model. – Metabble Dec 31 '12 at 01:32
  • Actually the "fat model, skinny controller" paradigm is very advisable. Refer for instance to this [answer](http://stackoverflow.com/questions/2550376/rails-skinny-controller-vs-fat-model-or-should-i-make-my-controller-anorexic) – Gabriele Petronella Dec 31 '12 at 01:35
  • @Metabble: It's not actually clearing the display; that might just be a bad name. It's clearing the currently-being-entered value, which is a property of the model, and that property has an observer hooked up to it in the controller to forward the value to the view. Other ways i'd thought of would mean the view being too familiar with how a number needs to be formatted, IMO. – cHao Dec 31 '12 at 04:22
  • @GabrielePetronella Not when you start putting logic for changing the GUI in the data model. It completely destroys portability. It's a balancing act, but a class to manage a group of numbers and do math on them shouldn't have anything to do with the user interface. In this case, it was just their method name. :| – Metabble Dec 31 '12 at 04:56
  • @cHao The number the user is currently entering is part of the model? Interesting. I kept the number currently being entered as a property of the controller until the user verified their entry (pressing a sign/enter) and then passed it to the model so that it could perform math on it along with the rest of its stack. I figure that it doesn't need to know about where the number came from/how the user entered it and should only get the number when it needed to use it. That way, my model knew nothing at all about pretty much anything. It seemed more efficient, anyways. Is this RPN or infix? – Metabble Dec 31 '12 at 05:00
  • @Metabble: Infix. It's a standard 4-function calculator for now. RPN would probably be easier to handle, but i went with what i use. :) As for the current number being in the model, i figured the model ultimately knows and cares about its validity; if the view or controller owned it, i'd still be constantly consulting with the model to keep it valid anyway. Putting it in the model seemed to reduce the cross-layer chatter and keep messages flowing in the right direction. Though you might be right about the model being too involved with how data's being entered, which is arguably a view thing. – cHao Dec 31 '12 at 06:24
  • @cHao That's an interesting view point. In this case, I'd say it can go either way; it's an area of gray between the controller and the model. I'd go for keeping it in the controller than passing it to a validation method in the model just to make the model more portable but if what you're doing is working it's hard to say. I did an RPN graphing calculator with BEDMAS at the top for cs193p (iTunes U). The model had to mesh with the graphing part as well which meant it couldn't know anything about the numbers it was handed, just that it needed to use them and give me a result. – Metabble Dec 31 '12 at 22:38

2 Answers2

4

You can use the dynamic features of the language.

From the Objective-C Runtime Programming documentation

When an object can’t respond to a message because it doesn’t have a method matching the selector in the message, the runtime system informs the object by sending it a forwardInvocation.

So in your case you can implement the forward invocation method as follows

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([self.model respondsToSelector:[anInvocation selector]])
        [anInvocation invokeWithTarget:self.model];
    else
        [super forwardInvocation:anInvocation];
}

Note You have also have to uniform your methods signatures. Either remove the sender parameter or add it to the model's method, otherwise respondsToSelector will return NO and the method won't be called.

In this case forwardInvocation will act as a dispatcher and it will try to send every message not implemented by your controller to the self.model object. If this is not responding to a selector it will call super, very likely resulting in an unrecognized selector exception.

I personally find it very elegant, even though you'd better know exactly what you are doing and definitely not to overuse such feature.

Gabriele Petronella
  • 106,943
  • 21
  • 217
  • 235
  • 1
    I'd looked at something like this...but does this kill the ability to wire up actions in Interface Builder? (Without the `(IBAction)` methods, how do i tell IB that the controller has a way to handle them? Or do i just have to wire them up with code?) – cHao Dec 31 '12 at 01:10
  • I'd create a stub signature, link the `IBAction`, then remove it from the controller class. The link will remain and everything should work as expected. – Gabriele Petronella Dec 31 '12 at 01:12
  • And the link will stay once the stub's gone? Kinda spiffy if it works :) – cHao Dec 31 '12 at 01:13
  • Yep. It's usually a source of errors, at least for me. If you link an `IBAction` (or `IBOutlet`), removing the declaration/implementation from the controller class won't remove the link. So at runtime it generates an exception. As an example try to remove the `sender` parameter from your `clearAll:` method. It will try to call `clearAll:` anyway, while you have implemented `clearAll`. It will crash and in this very special case is a good news ;) – Gabriele Petronella Dec 31 '12 at 01:16
  • 2
    You *can* do that, but don't do that. This kind of dynamism to work around a design problem is a Very Bad Idea. – bbum Dec 31 '12 at 01:25
  • Well the design looks good enough to me. He has a view, managed by a controller. The controller is dispatching actions to the model, as it should be. What he needs is a dispatcher, and it's a nice example where `forwardInvocation` comes in handy. – Gabriele Petronella Dec 31 '12 at 01:27
  • 1
    I've seen the technique mentioned by Gabriele used well in at least 1 instance (EGORefreshTableHeaderView). For the interceptor object the following example was used: http://stackoverflow.com/questions/3498158/intercept-obj-c-delegate-messages-within-a-subclass Yet when I tried to use the same technique today for a UITextView, I could not get it to work; this main issue was how Apple's UITextView class refers to itself internally. Not all classes are designed the same internally and occasionally you will encounter problems with this design - use with caution. – Wolfgang Schreurs Dec 31 '12 at 01:37
4

You can do what Gabriele suggested and it is certainly an example of how dynamic ObjC can be, but you are likely better off avoiding it. As Gabriele said, you'd better know exactly what you are doing and definitely not to overuse such feature. And that often indicates that such feature is likely more trouble than it is worth.

The reality is that your calculator application is a quite contrived for the purposes of driving home the separation inherent to the Model-View-Controller pattern. It is a learning app, as you state.

In reality, no application is ever that simple. You will rarely, if ever, have a field of buttons where that the control layer blindly forwards said functionality on to the model.

Instead, there will be all manners of business logic in that control layer that will may do everything from automating various actions to validation (potentially by querying the model) to updating UI state in response to various actions.

Likely this code will be present from very early in the project, thus that generic forwarding mechanism will quickly become completely unused.

As well, such forwarding mechanisms become funnels full of pain when it comes to debugging. you no longer have a concrete spot to drop a breakpoint, but now have to add conditions. Nor do you have an easy means of finding all the places that might invoke or implement a particular method. As well, it makes following the control flow more difficult.

If you do find yourself with lots of repetitive boiler-plate code, it is more of a sign that your architecture is likely flawed than a sign that you need to inject a spiffy dynamic mechanism to reduce the repetitiveness.

As well, if you were to continue to flesh out your calculator app, how much of your coding time would have been spent doing those repetitive methods vs. all other features in your app? Likely, very very little and, because of their simplicity and convenience to debugging, it is unlikely that said repetitive methods are ever going to incur any significant maintenance cost whereas a spiffy-dynamic bit of trickery (which is very cool and I encourage you to explore that in other contexts) is pretty much guaranteed to require a "Huh. What was I thinking here?!" moment later on.

bbum
  • 162,346
  • 23
  • 271
  • 359
  • 2
    It's a good point. If the code is the one presented by the OP I still think a dispatched is fairly appropriate solution. Obviously if things get more complicated it may cause more trouble than good, but in such case the repetitiveness problem that inspired this question won't probably be there anymore. – Gabriele Petronella Dec 31 '12 at 01:37