OOP and Foundation [was Re: Cocoa's Popularity]
OOP and Foundation [was Re: Cocoa's Popularity]
- Subject: OOP and Foundation [was Re: Cocoa's Popularity]
- From: Bill Bumgarner <email@hidden>
- Date: Tue, 2 Apr 2002 10:43:07 -0500
This is in response to Michael Gersten's somewhat belated response to the
thread "Cocoa's Popularity". It is intended to illuminate design patterns
used throughout the Foundation and Cocoa, not to continue the flamewar.
Not that Michael's response was flamebait-- it obviously had a lot of
thought behind it. I just found it to be at opposition with the design of
the Foundation -- a lot of the comments assumed design patterns that are
simply not the case.
On Tuesday, April 2, 2002, at 05:04 AM, email@hidden
wrote:
Cocoa
and Objective-C are the "right" way to do OOP, if there is such a
thing.
Sorry, that's just not the case. There's the old question of "How do you
design a hiarchy of rectangle and square?", with the intuitive answer of
"Square is a type of rectangle", the programming answer of "No, you can
do things to a rectangle that you can't do to a square, so square is the
parent -- rectangle permits more, is a subclass". And, the overlooked
answer of "Make polygon the parent of both, then right-angled polygon;
square and rectangle are siblings".
[I'll use -> to denote the object hierarchy. I.e. NSObject -> NSArray
indicates that NSArray is a subclass of NSObject.]
The programmer could have just as easily answered: 'A square should be a
subclass of a Rectangle because it *constrains* the behavior of a
rectangle.'. That is, a rectangle may have a -setWidth: and -setHeight:
method while the Square subclass overrides both such that they stay equal.
The squares behavior overrides that of Rectangle to provide a more
specific implementation.
Throwing Polygon into the mix, a perfectly valid/comfortable object model
would be Polygon -> Rectangle -> Square. I.e. Polygon is the most generic
class, Rectangle is more specific, and Square is the most specific.
The Foundation and Cocoa follow a [relatively-- there are exceptions and
this is not to claim that it is perfect] consistent design pattern of 'a
subclass is more specific in purpose than its superclass'. Looking at
Cocoa, you have NSObject -> NSResponder -> NSView -> NSControl -> NSButton
-> NSPopUpButton. Each subclass has a more specific purpose than the
superclass.
This is a relatively common paradigm throughout the OOP realm.
The question of immutable vs. mutable is somewhat outside of this
particular pattern regardless of whether one were to consider NSString ->
NSMutableString or NSMutableString -> NSString. See below...
Collections are handled HORRIBLY in Foundation. I don't think you can do
it right in ObjC.
Consider NSString. It's unmutable, right? If you're given an NSString,
you know it's unchanging, right?
Look at the inheritance hierarchy (ignoring class clusters):
NSObject -- totally generic class (and formal protocol that defines
object behavior)
NSString -- generic immutable String container (adds functionality to
NSObject for the containment and manipulation of a static string)
NSMutableString -- adds the API to NSString to manipulate the strings
contents in place
The key difference between NSString and NSMutableString is-- obviously--
one supports mutability of its contents while the other does not. NeXT
made the choice that NSMutableString would subclass NSString because
NSMutableString *adds* API to NSString to support mutability. This makes
the implementation extremely obvious; for NSString, the developer only
implements the methods needed to support a static String while
NSMutableString adds those methods necessary to support Mutability.
One might first assume that this is in direct violation of the least
specific -> more specific design pattern used throughout the Foundation
and Cocoa. However, reading the Foundation documentation reveals that
NSString is a *Class Cluster*. That is, it is a cluster of classes-- some
public, some private-- that implement String functionality. The
functionality is divided across two public classes within the cluster;
NSString provides the immutable base API and NSMutableString adds
mutability to that base API.
In most situations, it doesn't matter if the string returned is mutable or
not. Where the developer typically asks for a String from some random
method-- say, -pathForResource:ofType: on NSBundle-- it is because the
developer is going to immediately use the string and not store it for
later.
In those cases where the developer needs to store the string for use later,
a simple invocation of -copy will create an immutable copy of either a
mutable or immutable string.
myPermaReference = [someRandomString copy]; // -copy implies -retain
For an immutable string, -copy is a non-operation-- it simply returns the
string.
Why is NSMutableString a subclass of NSString? Sure, you can do more with
it. But you can't count on an NSString being an NSNonmutableString --
that subclass of NSString was never standardized.
Simple; if you attempt to invoke any of the mutable methods on an
immutable string, it raises an exception. If it were the other way
around-- if NSString was a subclass of NSMutableString-- then NSString
would have to have *a lot* of code devoted to ensuring that its contents
were read-only. Code that could easily be overridden, leading to broken
apps and potential security holes.
Similarly with NSSet, NSDictionary, etc -- the assumption of "More
flexibility means a subclass" ignores the idea of "this class guarantees
that you can't do X, use class Y if you want that."
Not true; if a method returns an NSSet, NSDictionary, or NSArray, then
that method is indicating that you *should not attempt* to modify the
returned object. If the method declares that it returns NSArray, but
actually returns NSMutableArray for the sake of efficiency, it should be
none of the developer's business. The method declared (NSArray), treat it
as an NSArray! If you need to store a reference to that object for use
later do this:
myArrayReference = [returnedArray copy]; // right
Instead of:
myArrayReference = [returnedArray retain]; // wrong
Same number of lines of code. If the array really is immutable, the
first line is equivalen to the second line. If it is mutable, the right
thing happens.
(In many cases where a foundation method is returning a mutable instance,
but declaring an immutable return value it is because the internal
implementation used the mutable instance to compose the response, not
because the object is returning a reference to some mutable object it is
storing internally and will later modify. It is unfortunate that this is
an issue at all-- optimization often has such side effects.)
[Some other languages assume that a String is mutable, and you put a
nonMutable filter object on it if you want to prohibit alterations. But
that just means you'll get runtime errors for class mismatches.]
... stuff about collections deleted. it seemed a non-issue in this
context (and incomprehensible, but that may just be me) ...
The second problem of Objective C: Class versus protocol. Unless you know
the details of a class's implementation (either public members, or
'friend'-like of C++), you don't take a class object as an argument, you
take an object that meets the protocol of that class.
If you didn't understand that: Defining a class should define both a
class (which affects code inheritence), and an interface/protocol
corresponding to that class; when you declare something as an incoming
argument (excluding the ** [outbound argument] case), you say that you
want something that fits that protocol, regardless of what class it
actually is. Right now ObjC has that only because it doesn't do enough
checking (or is lied to) -- nothing under NSProxy inherits from anything
under NSObject, but you can give something from NSProxy to a method that
wants an NSObject descendant. The code doesn't notice, because the
compiled code only uses the inherent protocol. But if the compiler knew
what was going on, it would complain; if there was strict type checking,
it wouldn't fly.
First, nothing under NSProxy should inherit from NSObject. The purpose of
NSProxy is to provide a transparent proxy to an instance (or class) that,
often, resides in a different runtime or language. Beyond -isProxy
returning YES, there should be no discernible difference between an
instance of NSProxy and the instance that is proxied. If you want the
compiler to treat the proxied reference as if it were an instance of that
class, typecast it as such and the compiler will do the right thing. This
is not the typical design pattern in that most proxies have an assigned
protocol to indicate that only some subset of the proxied instance's
methods are valid in the context of the proxy.
In any case, NSProxy has nothing to do with the class-as-parameter
issue. That you can't have typedef a parameter to be a particular class
is definitely a shortcoming of the language in absolute terms. In
practice, it is rarely a real issue. Looking at the AppKit, there are 8
methods that take a class object-- all used for the same purpose:
NSBrowser.h:- (void)setMatrixClass:(Class)factoryId;
NSBrowser.h:- (void)setCellClass:(Class)factoryId;
NSControl.h:+ (void)setCellClass:(Class)factoryId;
NSImageRep.h:+ (void)registerImageRepClass:(Class)imageRepClass;
NSImageRep.h:+ (void)unregisterImageRepClass:(Class)imageRepClass;
NSMatrix.h:- (id)initWithFrame:(NSRect)frameRect mode:(int)aMode cellClass:
(Class)factoryId numberOfRows:(int)rowsHigh numberOfColumns:(int)colsWide;
NSMatrix.h:- (void)setCellClass:(Class)factoryId;
NSScrollView.h:+ (void)setRulerViewClass:(Class)rulerViewClass;
The names of the methods make it obvious what the root class should be and
one call to +isKindOfClass: can assert that the passed parameter really is
an instance of that class.
While I agree that the limitation is annoying, the resulting
implementation is both simple and flexible. If the presentation of
Classes were considerable more formal, it would make tasks such as
intra-language bridging and proxying considerably more difficult.
What does this give you? If you have a new class, relatively unrelated to
an existing class, but with one of those old class members as an ivar,
you could give a compiler directive to say "Anything I didn't mention of
protocol classFooProtocol that is sent to me is forwarded to ivar myFoo".
Then, you can have an NSView as an ivar, make sure that it always stays
offscreen, never becomes first responder, and use your class exactly as
an NSView for drawing, even though you are completely unrelated. Call it
"MyOffscreenBitmap", or something like that. Completely unrelated to
NSView _codewise_; fully equivalent for drawing into it and giving it to
other routines that work with NSView.
Objective-C was explicitly designed as a light weight fully dynamic OO
language. This runs counter to the design goals of the language.
Furthermore, ObjC was designed without multiple inheritance for the sake
of simplicity and compactness.
For the functionality described, a cleaner implementation already exists
in one of several forms. An offscreen window can be used-- simply stuff
your view in the window-- or one could use an NSImage as the drawing
target for the NSView. In both cases, the NSView would be entirely
offscreen and would never become first responder (or key) without
requiring any complex extensions to the syntax or features of the language.
The NSView would be treated as normal and would draw as normal -- the
wrapping Window or Image would be used to obtain the bits [or PDF].
This isn't to say that the requested feature is invalid; it is perfectly
valid, it is simply counter to the design goals of ObjC, the Foundation,
and the AppKit.
... operator overloading / C++ discussion deleted -- operator overloading
is a huge win in a lot of contexts, but can often lead to completely
unreadable code; why should +'ing to strings be syntactically the same
as +'ing to numbers when the end result is a complete different operation?
In any case, ObjC is not really an appropriate language to build
hardcore calculation engines with-- use C++ or straight C or something
else ...
... stuff about networking computers deleted -- this is a development
forum... debating the availability of networking and floppies, etc, is not
appropriate ...
b.bum
....lying there snoring,
breath smelling like a 1948 buick.
_______________________________________________
cocoa-dev mailing list | email@hidden
Help/Unsubscribe/Archives:
http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored.