• 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
OOP and Foundation [was Re: Cocoa's Popularity]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

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.

  • Prev by Date: Re: Shared objects and other data
  • Next by Date: Re: Shared objects and other data
  • Previous by thread: Re: Fadeing in and out
  • Next by thread: Re: Annie
  • Index(es):
    • Date
    • Thread