One of the promises of object-oriented programming is the promise of reusability: if you build your software the right way, it should be easy to take large elements of your application and drop them into another application and–so long as the interfaces are honored, the code should run unchanged in another application.
There are a number of design paradigms which help support this reusability.
For example, interfaces allow the abstraction of the API–the application programming interface–used by a class. By allowing the API to a class or set of classes to be abstracted, we can divorce the implementation from the functionality–from the promise of the API contract. So long as the calls behave the same way it doesn’t matter how the object is implemented–or how the caller calls the object.
This requires the “separation of concerns”: we separate out each component of the software into well defined discrete components which can then be plugged together without regard to what’s going on “under the hood.”
And part of this “separation of concerns” requires, to some extent, that each object take responsibility for its own behavior, rather than for other components in the software having knowledge as to the specific object it is dealing with. In other words, if you have a choice between having a container know what is inside, or having a container which calls well-defined interfaces to determine the behavior of its contents–pick the later.
Here’s a concrete example of this.
Suppose you’re writing a UIView which allows tap events inside, and that UIView will live inside a scroll view. Because on the iPhone the scroll view has no idea if a tap event is the user trying to scroll the contents, or trying to manipulate the contents inside a view, the scroll view class needs a mechanism to determine if the intent of a finger tap is scrolling or something else.
So how does the UIScrollView class determine this?
Because a scroll view has no scroll bars, it must know whether a touch signals an intent to scroll versus an intent to track a subview in the content. To make this determination, it temporarily intercepts a touch-down event by starting a timer and, before the timer fires, seeing if the touching finger makes any movement. If the timer fires without a significant change in position, the scroll view sends tracking events to the touched subview of the content view. If the user then drags their finger far enough before the timer elapses, the scroll view cancels any tracking in the subview and performs the scrolling itself. Subclasses can override the touchesShouldBegin:withEvent:inContentView:, pagingEnabled, and touchesShouldCancelInContentView: methods (which are called by the scroll view) to affect how the scroll view handles scrolling gestures.
In other words, Apple took door number 1: they require the UIScrollView class to have “supernatural knowledge” of the views inside of it in order to determine the behavior of the scroll view.
And notice the default implementation of these methods rely on the type of class inside the scroll view:
Return Value:
YES to cancel further touch messages to view, NO to have view continue to receive those messages. The default returned value is YES if view is not a UIControl object; otherwise, it returns NO.
In other words, somewhere deep in the code inside the default implementation of UIScrollView someone wrote something like this:
- (BOOL)touchesShouldCancelInContentView:(UIView *)view { return ![view isKindOfClass:[UIControl class]]; }
This is bad.
Anytime you find yourself writing code like this–anytime you find yourself figuring out the type of a class in order to alter behavior of your code–consider the possibility that you are doing something wrong.
That’s because you’re creating “supernatural knowledge”: you’re making an object which relies on the type hierarchy of other objects in your system.
Consider instead that perhaps the knowledge of the object’s behavior should not come from the type of the class, but from an interface method inside the class object being tested. In this case, consider changing your code to look like:
- (BOOL)touchesShouldCancelInContentView:(UIView *)view { if ([view respondsToSelector:@selector(touchShouldCancel)]) { return [view touchShouldCancel]; } else { return NO; } }
By doing this you won’t force a developer to override a class unrelated to the class he’s working on in order to get the functionality he desires.