问题 Objective-C中的特定类类型参数


我想接受一个Class对象作为我的一个类的构造函数的参数。它需要用它做很多自定义工作,我想从类的用户那里抽象出来。

例如,假设我的班级是经销商,我想用任何类型的车辆初始化它。

所以我有以下层次结构:

Dealership : NSObject

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

我想要做的是,在经销商内部,实现以下初始化程序:

- (id)initWithVehicle:(Class)aVehicle;

除了不接受泛型类之外,我想将其限制为仅仅类型为“Vehicle”的类(其中包括我所有的继承类)。我可以在初始化程序中测试此属性,但如果有一种方法可以在编译时强制执行此操作而不是等待运行时反馈,那将会很棒。

看来你可以引用Class来限制实现某个接口的类,所以我可以在那里做一些hackery。有没有办法引用特定类型的Class对象?

编辑 - 请注意,我编辑了示例类,因为原始示例有点误导。该示例仅用于演示目的,而不是我正在使用的实际类层次结构。


1195
2018-05-03 18:21


起源

同样,为什么你自己传递类而不是实例?你用它们来制作实例吗? - Jonathan Grynspan
@Jonathan Grynspan - 正确。我不希望这个类的用户必须设置实例来传递给初始化器,因为每次都有很多细节工作是相同的。他们不应该担心这一点。我希望他们能够说“只需使用一辆香精卡车就可以完成所有你需要做的事情”。 - DougW


答案:


不是在编译时,但你可以发布 self 并返回 nil 如果课程无效:

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

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

    return self;
}

这是你最接近你想要的那种限制。

但是这种设计表明你需要重新思考你的类层次结构。而不是传递一个子类 Car,你应该有一个 Manufacturer 类。像这样的东西:

@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.

然后这个经销商的初始化程序:

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

5
2018-05-03 18:30



@Jonathan Grynspan - 是的,那是我所说的运行时解决方案。感谢发布,所以人们知道,应该在原帖中拼写出来。 - DougW
@DougW:看看我建议的替代方法。 Toyota 不应该是的子类 Car,但是的子类 Manufacturer。 Prius 是。的子类 Car,通过 Hatchback <Hybrid>。 ;) - Jonathan Grynspan
@Jonathan Grynspan - Re:重新思考类层次结构。你是对的,我有办法解决这个问题。问题是这已经是一个广泛的类层次结构,并且需要进行大量的返工才能使用接口。也就是说,我认为我所建议的模式不一定存在任何架构错误。 - DougW
@Jonathan Grynspan - 汽车只是一个非常简单的例子来展示我正在使用的问题。真正的问题要广泛得多,继承实际上是与它合作的正确方法。这个问题的细节太多了。 - DougW
@Jonathan Grynspan - 我编辑了原始问题以使用更好的示例类层次结构。你是对的,制造商是继承的坏榜样。 - DougW


您可以确保使用协议检查:

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

将对象声明为id告诉编译器您不关心对象的类型,但您确实知道它符合指定的VehicleProtocol协议**。


6
2018-05-03 19:40



我在OP中提到了这个潜在的解决方法。这是真的,谢谢你花时间提到它。尽管如此,它并没有回答这个问题。 - DougW
声明协议并像这样使用它是解决这个问题的方法 - malhal


我不确定你到底得到了什么样的解决方案,但也许正在对待 Vehicle 作为类集群,您可以在一个保护伞下合并通用功能,并让类集群管理不同的实现(并将其与用户屏蔽)?

例如, NSString 做这个;底层实例通常是类型 NSCFString,除非他们是文字(即 @"this is a literal, in code"),在这种情况下他们是 NSConstantString秒。

这里 摘自相关博客文章,解释类集群:

简而言之,它是一种设计,允许您将一系列功能相关的对象合并到您的应用程序中,同时保持代码与这些对象的交互,松散耦合,灵活,易于维护或更新。

类集群使这组公共对象符合单个接口的行为,并且通过该接口引导它们的所有创建。构造由两个关键部分组成:a)一个公共抽象接口,作为集群的“面”,用于通告支持的API; b)该接口的许多私有,具体子类,负责实际实现广告行为超自然的特定方式。抽象超类本身实现了一些方法,最重要的是一个工厂方法来销售私有子类的实例;所有子类(如访问器)共享的其他常用功能也可以在此处定义并共享。

集群的用户只看到一个公共超类,不知道它实际上是抽象的,并且对任何私有具体子类的存在一无所知。超类提供工厂创建方法,该方法负责确定哪个子类适用于任何给定情况并透明地返回它的实例。由于此返回的对象根据公共超类的接口实现并运行,因此用户可以简单地假设他们已获得此超类的直接实例。


1
2018-05-11 19:23





如果您想使用车辆初始化经销商(我的意思是初始化实例),您应该:

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

0
2018-04-08 22:59