0

我不确定最雄辩的方式来说明这一点,但我会尽力而为。我创建了一个自定义类,它是具有一些属性的通用对象。我创建了几个子类来扩展它并使它们比超类更具体。所以为了举例,我将抛出一些通用的示例代码,这些代码可能是也可能不是正确的语法,只是为了说明我想要完成的事情。

@interface Vehicle : NSObject

@property (nonatomic) int wheels;

- (id)initWithNumberOfWheels:(int)wheels;

@end

从那里我为相同的“汽车”和“卡车”创建了一些子类,为课程提供了更多细节。

@interface Car : Vehicle

@property (nonatomic) BOOL convertible;
@property etc...

@end

和...

@interface Truck : Vehicle

@property (nonatomic) BOOL is4x4;
@property (nonatomic) int numberOfDoors;

@end

所以这就是有趣的地方。我想创建另一个分配这些对象的类,但我希望在 init 方法中确定车辆的“类型”,但使用相同的 @property 变量。例如(再一次,这都是垃圾代码,只是为了提供视觉表示)

路.h

#import "Car.h"
#import "Truck.h"

@interface Road : NSObject

@property (strong, nonatomic) NotSureWhatToUseHereToMakeThisWork *myRide;
// doesn't work: @property (strong, nonatomic) id myRide; 
// doesn't work: @property (strong, nonatomic) Vehicle *myRide; 


- (id)initWithCars;
- (id)initWithTrucks;

@end

路.m

@implementation Road

- (id)initWithCars
{
//standard init code...

    myRide = [[Car alloc] initWithNumberOfWheels:4];
    myRide.convertable = NO;
}

- (id)initWithTrucks
{
//standard init code...

    myRide = [[Truck alloc] initWithNumberOfWheels:6]; 
//yes, some trucks have more than 4 wheels

    myRide.is4x4 = YES;
}

@end

底线是如果我在@property 中使用超类,它显然不会获得子类属性。基本上我想让所有这些尽可能通用和可重用。从那以后就没有为汽车和卡车制作一个特殊的“道路”课程。路终究是路。有什么可以做我所追求的吗?有没有更好的方法来做这样的事情?主要目的是让对象仅在特定情况下继承特定属性。我不想制作额外的@properties 的原因是,如果它们不适用于这种情况,我不希望它们可见。

编辑:我添加了一些额外的片段来显示我在发布这个不起作用的问题之前尝试过的内容。

答案:如果有人好奇,正确的“答案”位于 CRD 在“附录”中的回复中。这样做的原因是“id”类型只能调用方法而不能继承属性。因此,解决方法(我是这样说的,因为我正在研究这个,得出的结论是这不是好的编程,如果可能的话应该避免)是使用访问器方法来获取/设置属性。

id mySomethingObject = [[SomeClass alloc] init...];
[mySomethingObject setPropertyMethod]...; //sets value
[mySomethingObject propertyMethod]...; //gets value

而不是试图使用...

mySomethingObject.property = ; //set
mySomethingObject.property; //get

如正确答案中所述,如果您分配“id”的类不响应该方法,您的程序将崩溃。

4

5 回答 5

5

您似乎混淆了许多问题。

首先是实例的类型与保存对实例的引用的变量的类型。创建对象时,它是否具有某种特定类型,并且该类型不会改变[*]。变量也有一个类型,这也不会改变。子类型化/继承允许您将对某种类型的对象的引用存储T在某种其他类型的变量中S,前提S是 是 的超类型T

其次是静态与动态类型。虽然 Objective-C 使用动态类型,在某些操作中使用的对象的实际类型是在运行时确定的,但编译器本身使用静态类型,其中类型是在编译期间确定的,以帮助编写正确的程序。有时编译器静态检查只会产生警告,但在其他情况下,编译器会拒绝编译某些东西。特别是属性引用是基于静态类型编译的。

在您的示例中,这意味着您不能直接Car引用由类型变量引用的对象的属性Vehicle *,即使您知道引用的对象是 a Car- 因为在编译时只知道它是 a Vehicle

解决方法是先测试被引用对象的实际类型,然后引入更精确类型的局部变量,或者使用大量的强制转换。例如:

// (a) create an object of type Car (for a Reliant Robin ;-))
// (b) create a variable of type Car and store in it a reference to the created Car
Car *myCar = [[Car alloc] initWithNumberOfWheels:3];

// Create a variable of type Vehicle and store in it the reference stored in myCar
// The created instance is *still* a Car
Vehicle *myRide = myCar;

// See if myRide is a Car and then do something
if ([myRide isKindOfClass:Car.class])
{
    // create a variable of type Car to avoid having to continually cast myRide
    Car *myCarRide = (Car *)myRide; // due to if above we know this cast is valid

    if (myCarRide.isConvertible) ...

要在没有中间变量的情况下执行此操作,请使用强制转换:

...
// See if myRide is a Car and then do something
if ([myRide isKindOfClass:Car.class])
{
    if (((Car *)myCarRide).isConvertible) ...

这说明了为什么中间变量方法更好!

作为最后一个示例,您可以initWithTrucks这样编写方法:

- (id)initWithTrucks
{
   //standard init code...

   Truck *myTruck = [[Truck alloc] initWithNumberOfWheels:6]; 
   //yes, some trucks have more than 4 wheels
   myTruck.is4x4 = YES;

   // Store the reference to the created Truck in myRide
   myRide = myTruck;
}

高温高压

附录

从您的评论看来,您可能正在寻找动态类型,并且不希望编译器执行任何静态类型。这是(部分)支持的,但不使用属性的点表示法 - 您必须直接使用 getter 和 setter 方法。

首先,在Road您声明myRide为 type id

@interface Road : NSObject

@property id myRide;

类型意味着两id件事(a)任何对象引用和(b)不静态检查对象上存在的方法。但是编译器必须知道某个对象上存在被调用的方法,并且它仍然会对方法的参数执行静态类型检查 - 因此它不是完整的动态类型(但是您可以传递id类型化表达式或声明您的方法以获取参数当然类型id...)。

其次,您对属性的所有引用都直接使用 getter 或 setter 方法,而不使用点表示法(对于非属性方法,您只需照常调用它们)。例如:

- (id)initWithTrucks
{
   //standard init code...

   myRide = [[Truck alloc] initWithNumberOfWheels:6]; 
   //yes, some trucks have more than 4 wheels
   [myRide setIs4x4:YES];
}

如果您进行诸如[myRide setIs4x4:YES]并且myRide正在引用Car对象的调用,那么您将收到运行时错误。

一般的建议是尽可能坚持编译器的静态类型检查。

[*] 我们将忽略任何运行时魔法,有龙。在普通代码中,对象永远不会改变类型。

于 2013-09-06T23:17:01.120 回答
3

您必须使用“Vehicle”类型,然后使用“Truck”或“Car”投射您的对象以获得特定属性

于 2013-09-06T18:47:25.767 回答
3

最通用的架构是创建一个VehicleProtocol任何类都可以实现的 . 你仍然可以有一个实现协议的 Vehicle 类和它的子类(类似于实现 NSObject 协议的 NSObject),或者让独立的类实现它。这条路会有财产@property (strong) id<VehicleProtocol> myRide


后期架构的完整示例:没有车辆超类,但都是一个 VehicleProtocol

#import <Foundation/Foundation.h>


@protocol VehicleProtocol <NSObject>
@property (nonatomic) NSUInteger wheels;
@end

@interface Car : NSObject <VehicleProtocol>
@property (nonatomic) BOOL convertible;
@property (nonatomic) NSUInteger wheels;
-(id)initWithNumberOfWheels:(NSUInteger) numberOfWheels;
@end

@implementation Car
-(id)initWithNumberOfWheels:(NSUInteger) numberOfWheels
{

    if (self = [super init]) {
        _wheels = numberOfWheels;
    }
    return self;
}
@end


@interface Truck : NSObject <VehicleProtocol>

@property (nonatomic) BOOL is4x4;
@property (nonatomic) int numberOfDoors;
@property (nonatomic) NSUInteger wheels;
-(id)initWithNumberOfWheels:(NSUInteger) numberOfWheels;

@end

@implementation Truck
-(id)initWithNumberOfWheels:(NSUInteger) numberOfWheels
{

    if (self = [super init]) {
        _wheels = numberOfWheels;
    }
    return self;
}
@end




@interface Road : NSObject
@property (strong) id<VehicleProtocol> myRide;
@end

@implementation Road
@end

int main(int argc, const char * argv[])
{

    @autoreleasepool {

        NSArray *vehicles =  @[[[Car alloc] initWithNumberOfWheels:4], [[Car alloc] initWithNumberOfWheels:3], [[Truck alloc] initWithNumberOfWheels:10]] ;

        for (id v in vehicles) {
            if ([v isKindOfClass:[Truck class]]) {
                [v setIs4x4:YES];
            }
        }


        Road *road = [[Road alloc] init];
        road.myRide = vehicles[0];
        NSLog(@"%@", road.myRide);


        road.myRide = vehicles[2];
        NSLog(@"%@", road.myRide);

        NSObject *obj = [[NSObject alloc] init];
        road.myRide = obj; // warning in this line
        NSLog(@"%@", road.myRide);


    }
    return 0;
}

当然,它可能比“经典子类化”有更多的代码行,但依赖关系更少。相反,班级同意履行合同。这里的合约只要求对象有任意数量的轮子。

请注意,我创建了一条道路并首先分配了一辆汽车,然后分配了一辆卡车(我还展示了如何通过 识别汽车和卡车-isKindOfClass:),两者都可以在没有任何警告或错误的情况下工作,因为 Car 和 Truck 完全履行了合同。比我分配一个普通的 NSObject。编译器在这里发出警告,因为他认识到 NSObject 没有实现该协议。虽然这不是编译器错误,但编译器不知道您是否会对该对象使用任何协议特定的方法。

如果将普通 NSObject 分配给 myRide,则此行

NSLog(@"%@ %ld", road.myRide, (unsigned long)road.myRide.wheels);

将导致运行时崩溃(因为 NSObject 实例不响应 wheel)——但在编译时它甚至不会触发警告。

于 2013-09-06T19:02:08.023 回答
1

将 Car 存储在类型变量中Vehicle *不会删除对象的 Car 属性——Car 仍然能够正常访问其所有状态——它只是意味着您不能通过该变量访问属性。

但这就是这里的基本思想,不是吗?您希望此类能够处理所有车辆,这就是它正在做的事情——它只是向您展示所有车辆可用的功能。因此,您可以在 Vehicle 类中拥有与通用 Vehicle 交互所需的接口,并在子类中实现方法以在调用时执行与类相应的行为,一切都会正常工作。

如果问题是专门创建特定类的新实例,您希望从那时起对其进行通用处理,则可以使用静态类型的局部变量,并在设置后分配给通用类型的变量。

例如,假设我们有一个游戏,其中有人类玩家和 AI 玩家,人类玩家可以通过让 AI 玩家受到更多伤害来给自己带来优势。我们可以这样做:

@interface Combatant : NSObject
@property(nonatomic, strong) NSString *name;
@property(nonatomic) int hitPoints;

- (void)takeDamage:(int)damageAmount;
@end

@implementation Combatant
- (void)takeDamage:(int)damageAmount {
    self.hitPoints -= damageAmount;
}
@end

@interface HumanCombatant : Combatant
@property(nonatomic, strong) UserID *userID;
- (id)initWithUserID:(UserID *)theID;
@end

@implementation HumanCombatant
- (id)initWithUserID:(UserID *)theID {
    if ((self = [super init])) {
        _userID = [theID retain];
    }
}

- (void)takeDamage:(int)damageAmount {
    [super takeDamage: damageAmount];
    NSLog(@"Human took %d damage, has %d health remaining", damageAmount, self.hitPoints);
}
@end

@interface AICombatant : Combatant
@property(nonatomic) double damageMultiplier;
@end

@implementation AICombatant
- (void)takeDamage:(int)damageAmount {
    int modifiedDamage = damageAmount * self.damageMultiplier;
    [super takeDamage: modifiedDamage];
    NSLog("AI took %d damage, has %d health remaining", modifiedDamage, self.hitPoints);
}
@end

然后,在我们的大部分游戏代码中,我们可以使用类型为 a 的变量Combatant *,当你发送它时takeDamage:,它会根据战斗员的类型做正确的事情。我们调用对象 a 的外部代码Combatant *将无法直接访问 AICombatant 的damageMultiplier属性,因为其他代码不知道 Combatant 是否是 AICombatant,但该对象仍将具有该属性,并且会在其类中正确运行。

于 2013-09-06T19:00:54.307 回答
0

将其设为 aVehicle*并使每个类实现type以返回一个指示该类类型的常量。

@property (nonatomic, strong) Vehicle* yourRide;
...
if (yourRide.type == VehicleConstant_Truck) {
  Truck* yourTruck = (Truck*) yourRide;
  NSLog(@"This truck %s a 4x4", yourTruck.is4x4 ? "is" : "isn't");
}

为了让@vikingosegundo 开心,另一种方法是:

if ([yourRide isKindOfClass:[Truck class]]) {

而不是if上面的陈述。

于 2013-09-06T19:32:39.833 回答