• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
Re: Convenience Methods
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Convenience Methods


  • Subject: Re: Convenience Methods
  • From: Greg Titus <email@hidden>
  • Date: Wed, 26 Sep 2007 21:46:09 -0700


On Sep 26, 2007, at 7:35 PM, Jeff Laing wrote:

From: Greg Titus [mailto:email@hidden]
This is just my opinion, but I think that YES, absolutely,
subclasses should know and depend upon the implementation details of their
superclasses.

At this point, we part ways on the theory but thats fine.

To assume that you don't have to know how your own
superclass works is taking object-oriented theory too far, and just

Um, this strikes me as really hard to defend. Encapsulation is all about
being able to wrap up "how" a class does its thing inside that class, and
just worry about "what" it does.

Yes, but encapsulation is just for _using_ a class. If you are subclassing you have (at least by default) access to all of the superclass's instance variables and non-public methods. There's no encapsulation there. If you ever touch any of them, you need to know the superclass's implementation. If you are overriding existing methods to have new behavior, you need to know the superclass's implementation.


If you don't ever touch the superclass's internal state, why aren't you using a Composite pattern instead? That's the only way to let its implementation change out from under you (as long as its public API remains the same) and still be sure that you won't be broken.

In this particular case, we've ended up with what I'd call "overly defensive
programming" in the SUPERCLASS because it has no idea whether someone will
come along and subclass it, so it complicates up its calls to alloc. Thats
just pushing the problem from the guy who knew he wanted to subclass into
the guy who didn't know.

Well, in the particular example given (+myClassWithFoo:) this is an extremely common pattern to alloc/init/autorelease a new instance. I don't think it complicates anything. If I was the superclass implementor, I would habitually use [self alloc], which would then work fine with subclasses. If I was the subclass implementor and wanted to provide support for creation of my subclass with +myClassWithFoo:, I would need to go look at the superclass implementation, because I need to know that -initWithFoo: is the initializer (I would probably guess that was the case, but I'd verify that). If the superclass author _hadn't_ used [self alloc] and had hardcoded the class name instead, I would change the superclass code to use [self alloc] while I was there.


This brings up the point in another way that I'd previously alluded to: I tend to see subclass/superclass relationships as parts of the same "chunk" of code and modify inheritance subtrees all together. I don't expect to be able to implement a subclass without also possibly modifying the superclass or vice versa. You should always be willing to refactor as you go along.

The exception is with a very few "hub" classes that are usually meant to be subclassed across framework boundaries, like NSView. And there you are explicitly writing your superclass with the expectation that it'll be subclassed by perhaps many people in the future. And you write defensively, and you document. But those examples are quite rare. Most inheritance relationships (especially deeper trees, especially model code) are all within the same domain and chunk of code, and it makes much more sense to just be aware of the implementation of that tree as a whole rather than to try to artificially limit code changes to single classes.

Thats no big deal, except that it becomes mandatory to do "best- practices"
when defining your classes since you can never tell whether you'll be
subclassed a year from now. And the original poster wanted to know what was
"best practice", returning id or myclass*.

I'd always return MyClass * for exactly the reasons you already gave when you originally answered this question. And no, it isn't mandatory to do "best-practices" if you don't think you'll be subclassed. That's exactly the same sort of over defensiveness in the other direction. When someone wants to come along and subclass you, they _make_ you cleanly subclassable _then_. In my world that programmer needs to understand your implementation before they try to subclass you, so they ought to be in your code checking it out. (Again, the exception being those few classes you 'export' as planned for subclassability.)


code. Not to mention the belief that you can just ignore how your
superclass is implemented leads to all sorts of bad code and
complacency. If you know what your superclass is doing, you write

I dont understand how relying on my superclass to honor its interface
contracts results in complacency or bad code. I'd be interested in more
faulty examples here.

My argument is that if all you need is for it to honor its interface contract then you generally don't need to subclass. You just need to instantiate an instance of the superclass and message it using that interface contract.


(By a framework boundary I mean where your superclass is
in a different framework than the subclass. A subclass of NSView in
your own application, for instance.)

which is, of course, why Apple recommend against subclassing NSString, etc.

Right. I would never see the need to subclass NSString. It isn't really meant for that.


But certainly in your own "Animals" framework, you better know how
your Animal superclass works, and you ought to take advantage
of that fact to write as little code as possible.

You are saying that if Dog is part of the Animals framework, I can code it
one way, but if I move it to another framework (which happens solely because
of organisational differences on my development team), I should code it
differently?

Yes, absolutely. Organizational differences matter. Either there is an organizational wall between the two classes or there isn't. And if there is no programmer organizational wall between a super/subclass then I don't think you should bother constructing an artificial wall in your mind. If both are within your domain, you should look at both when you modify either. Continually refactor what code is in which of the classes as you work.


Apple advises using this pattern
rather than subclassing for class clusters:

I don't see Animal as a class cluster here.

No, me either. I was just using that as a pointer to an explanation of the Composite pattern.


Finally, the design of Objective-C and Cocoa tends to lead to fairly
flat and broad inheritance hierarchies. There aren't that many deep
subclasses of subclasses of subclasses like you tend to find in some
other frameworks.

I think I understand what you are saying here, and the conclusion is
primarily that I still need to work on removing the C++isms from my thinking
about Objective-C.

Possibly. One of the reasons C++ hierarchies tend to be deeper is because C++ lacks things like categories. If I need some particular functionality on NSString I don't have to subclass or drop out of OO- land and use functions, I can add a category to add those methods to NSString. I can call all of the existing NSString public API from them, and don't need to be aware of the internal implementation. That's another way where if all you need is to access the existing public contract and don't need access to internals, you don't need to subclass in Objective-C.


I have another pet project which had developed a class hierarchy something
like:


basic_plugin
	file_format_plugin
		format_1_plugin
		format_2_plugin
	processing_plugin
		process_1_plugin
		process_2_plugin

The 'basic_plugin' is a class that I use across all my applications, but it
isn't in a framework, the source code is just copied into each application
(along with the basic_plugin_manager that handles plugin folders etc at
application startup). file_format_plugin and processing_plugin are both
specific to the current application, but neither of them want to concern
themselves with the implementation of the basic_plugin logic, they just want
to populate the standard entry methods and know that the level above them do
the right thing. Similiarly, some instances of format_1_plugin, etc are not
built as part of the application at all, since they expect to be something
developable by a 3rd party. I can definitely see where adding in common
superclasses under 'processing_plugin' for plugins that do similiar things
is also quite likely.


Do those lowest-level plugins really need to know about the internals all
the way up the tree, or just up one level? Or can they rely on the theory
that says "my superclass knows what he's doing and honors his interface
contracts". Which would imply that "if the method says it returns
basic_plugin*, then thats all it returns". Clumsy, not quite what you'd
like, but 100% explicit and predictable.

Okay, the lowest level plugins only need to know about file_format_plugin and processing_plugin. You are exposing those classes to 3rd parties and expect them to be subclassed, therefore you know to write them defensively and document them, there's an organizational wall there. As part of that defensiveness I would expect your file_format_plugin and processing_plugin to make clear what it returns via documentation in each method.


I would argue that when you work on this chunk of code that you should consider basic_plugin, file_format_plugin, and processing_plugin all together, and it should be fine for the two subclasses to know of and take advantage of the basic_plugin internal implementation, and that you should always be refactoring the three together (moving common code up, adding new virtual methods on the superclass to call when the subclasses need to do something differently from each other in some shared situation, et cetera).

In short, lavish your attention on careful interface contracts at organizational boundaries, but not at all on class boundaries within the same organization.

Patterns like delegation keeps the number of
subclasses you need to write yourself to a minimum.

... but are irrelevant to the sorts of problems I'm thinking about.

Sure, ok. But another way that you could have designed your plugin architecture would be to have a file_format_plugin_protocol and a processing_plugin_protocol, and the actual plugins wouldn't have any mandated superclass at all, they'd just need to respond to particular methods. If there was shared behavior or ways of handling the plugins in your application, you'd have a file_format_plugin_helper and processing_plugin_helper which would have the shared code for managing those types of plugins and would just have a pointer to a plugin instance. That would be a composite pattern where the combination of your helper class and the plugin with unknown superclass would together act in the same role as your current plugin class. The helper does the management and flow of control and such and delegates the specific file format work (or whatever) to the plugin.


That kind of structure has its advantages in not forcing the inheritance hierarchy on your 3rd parties. What if there was a 3rd party that had a file_format_plugin and a processing_plugin that shared a bunch of the same code? They would have liked to have used a common superclass for the two, but can't with your inheritance-based design. (At least not in Obj-C, which is only single inheritance. But don't get my ranting on the evils of multiple inheritance.) :-)

of your own code ought to be a single step away from one of a few
"hub" superclasses: NSObject, NSView, NSWindowController. That means

I'm somewhat surprised that most examples that people wheel out to discuss
why Cocoa class hierarchies are shallow tend to be user-interface related.
There are other domains of problems that *do* have deeply nested hierarchies
for application specific reasons that a shallow hierarchy+nesting do not
fit.

The examples are wheeled out in the user-interface domain because that's the only somewhat complicated domain that the Cocoa frameworks themselves cover. :-) To talk about other domains requires a whole lot more explanation because we can't assume that we have classes/ code/domain-knowledge in common.


But yes, object models, for example, tend to have more deeply nested hierarchies. I think in general you're better off trying to use composition and delegation and similar tricks that Objective-C's dynamicism is well-suited for in order to end up with less depth than you might find in a C++ hierarchy in the same problem domain, but still, they'll generally be deeper.

If he reimplements a superclass and doesn't check to see whether any
subclasses were depending upon the previous implementation,
I'd bitch at "the other guy" until he fixes it.

No, he didn't reimplement the superclass. He looked at a class that had a
method of the form


+ fooFromBar:(Bar*)b
{
	Foo *foo = [[[self class] alloc] initWithBar:b];
}

and because some other part of the system told him that we was spending a
significant amount of time doing that particular function (lets not get
distracted onto algorithm design, he's making a mistake, remember), he sees
that that [self class] seems a bit wasteful.

Well, it should just be [[self alloc] initWithBar:b], but yes, he's making a mistake, right...


So he changed it to

+ (Foo*)fooFromBar:(Bar*)b
{
	Foo *foo = [[Foo alloc] initWithBar:b];
}

and then he tested his changes to death in his own test harnesses, none of
which exercised "subclassing Foo". How does he tell that someone else
depended on that fairly subtle behaviour?

If Foo is a class that is 'exposed' at an organizational boundary and is expected and documented to be subclassed, it's his fault for subclassing not being part of his test harness.


If the subclass(es) is/are within the same framework, he should be examining them as he is doing profiling and making code changes and they should be tested at the same time as the superclass.

If the subclass(es) is/are external to his organization, and Foo isn't documented and intended to be subclassed, at that point it isn't his fault. It is the programmer who wrote the subclass's fault. That is the time when the subclass-er needs to be very careful about the contracted API, and avoid subclassing entirely, if possible. This is the situation in which I agree with your original comment on this topic, that the subclass-er shouldn't have counted on +fooFromBar: returning the subclass, and should have written their own +subclassFromBar: method instead, even if it ends up doing exactly the same thing.

The last of those 3 situations is hopefully fairly rare. It means that there's either a mistake in the design of the class hierarchy (that something needed subclassing which wasn't expected to be), or there's a mistake in the management of the development team (that these two classes were split into two different organizations).

Hope this explains more where I'm coming from,
	-- Greg
_______________________________________________

Cocoa-dev mailing list (email@hidden)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden


References: 
 >RE: Convenience Methods (From: Jeff Laing <email@hidden>)

  • Prev by Date: Re: drag and drop images to NSWindow doesn't work
  • Next by Date: Re: Convenience Methods
  • Previous by thread: RE: Convenience Methods
  • Next by thread: Re: Convenience Methods
  • Index(es):
    • Date
    • Thread