开发者

Specific Class type parameter in Objective-C

开发者 https://www.devze.com 2023-03-02 19:25 出处:网络
I want to accept a Class object as a parameter to a constructor of one of my classes.It needs to do a lot of custom work with it, and I want to abstract that away from the us开发者_高级运维er of the c

I want to accept a Class object as a parameter to a constructor of one of my classes. It needs to do a lot of custom work with it, and I want to abstract that away from the us开发者_高级运维er of the class.

For example, let's say my class is a Dealership and I want to initialize it with any type of Vehicle.

So I have the following hierarchy:

Dealership : NSObject

Vehicle : NSObject
Truck : Vehicle
Van : Vehicle
Car : Vehicle

What I want to do is, inside Dealership, implement the following initializer:

- (id)initWithVehicle:(Class)aVehicle;

Except instead of accepting a generic Class, I'd like to restrict it to only Classes of type "Vehicle" (which would include all my inherited classes). I could just test for this property in the initializer, but it would be great if there was a way to enforce this at compile time instead of waiting for runtime feedback.

It seems you can reference Class to restrict to classes that implement a certain interface, so I could do some hackery there. Is there any way to refer to Class objects of a specific type though?

EDIT - Note that I edited the example classes because the original example was a bit misleading. The example is just for demonstration purposes, not the actual class hierarchy I'm working with.


You can ensure using a protocol check:

- (id)initWithVehicle:(id<VehicleProtocol>)aVehicle;
...
}

Declaring an object as id tells the compiler that you don't care what type the object is, but you do care that it conforms to the specified VehicleProtocol protocol**.


Not at compile-time, but you can release self and return nil if the class is invalid:

- (id)initWithCar: (Class)carClass {
    self = [super init];

    if (self) {
        if (![carClass isSubclassOfClass:[Car class]]) {
            [self release];
            self = nil;
        } else {
            // Normal initialization here.
        }
    }

    return self;
}

That's the closest you'll get to the sort of restriction you want.

But this sort of design suggests you need to rethink your class hierarchy. Rather than passing a subclass of Car, you should have a Manufacturer class. Something like this:

@interface Manufacturer : NSObject
+ (id)manufacturerWithName: (NSString *)name;

- (NSArray *)allCars;
@property (readonly) Car *bestsellingCar;
// etc.
@end

#define kManufacturerVolvo [Manufacturer manufacturerWithName: @"Volvo"]
#define kManufacturerToyota [Manufacturer manufacturerWithName: @"Toyota"]
// etc.

And then this initializer for Dealership:

- (id)initWithManufacturer: (Manufacturer *)aManufacturer;


I'm not sure exactly what kind of solution you're getting at, but perhaps treating Vehicle as a class cluster would enable you to coalesce the common functionality under one umbrella, and have the class cluster manage the different implementations (and mask them from the user)?

For example, NSString does this; underlying instances are usually of type NSCFString, unless they're literals (i.e. @"this is a literal, in code"), in which case they are NSConstantStrings.

Here is an excerpt from a relevant blog post explaining class clusters:

In a nutshell, it’s a design that allows you to incorporate a family of functionally-related of objects into your application while keeping the code interacting with those objects loosely coupled, flexible, and easy to maintain or update.

A class cluster conforms this set of common objects to behave according to a single interface and, furthermore, channels all creation of them through that interface. The construction is made up of two key parts: a) one public, abstract interface serving as the “face” of the cluster which advertises the supported API, and b) many private, concrete subclasses of this interface responsible for actually implementing the advertised behavior of the superclass in their own specific way. The abstract superclass does implement a few methods itself, the most significant being a factory method to vend instances of the private subclasses; other common functionality shared across all subclasses, such as accessors, can also be defined here and shared as well.

Users of the cluster only ever see the one public superclass, are unaware that it is actually abstract, and know nothing about the existence of any private concrete subclasses. The superclass offers a factory creational method which is responsible for determining which subclass is appropriate for any given situation and transparently returning an instance of it. Since this returned object implements and behaves according to the public superclass’ interface, users can simply assume they have obtained a direct instance of this superclass.


If you want to init dealership with a vehicle (I mean an initialized instance) you should do:

- (id)initWithCar:(Vehicle *)carClass
{
    //An extra check (only for DEBUG mode)
    NSAssert([carClass isKindOfClass:[Vehicle class]]);
    ... //carClass is now a Vehicle instance
}
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号