如何在objective-c中比较两个具有很多属性的对象

Posted

技术标签:

【中文标题】如何在objective-c中比较两个具有很多属性的对象【英文标题】:How compare two objects with a lot of attributes in objective-c 【发布时间】:2014-04-04 09:09:56 【问题描述】:

我想测试我的 Basket 类型的对象篮的反序列化,其中包含AProduct 类型的对象数组。

我所有的产品类都继承自AProduct,这是一个抽象类。

渐渐地,我将拥有越来越多的产品类别,每个类别都有不同的属性。

目前,这就是我测试我的篮子反序列化的方式:

Basket *oldBasket = BasketInstance;

[BasketInstance resetBasket]; // BasketInstance is a macro which points on the current Basket in singleton.

[BasketInstance deserializeItself:[self getBasketSerializedForTest]]; // getBasketSerializedForTest return a dictionnary of the same basket serialized.

XCTAssertTrue([BasketInstance isEqual:oldBasket], @"Basket deserialization is no correct");

这个实现的主要问题是我必须重写Basket 类中的isEqual 方法,并且对于每个AProduct 对象,重写isEqual 方法来检查所有属性。我认为这是非常危险的,因为如果我或其他开发人员添加了一个新属性并忘记在 isEqual 方法上检查此属性的值,那么篮子的单元测试可能会成功,但反序列化失败。

有没有办法避免这种风险?虽然我解析了类的所有属性,并为每个属性运行isEqualmethod,但我希望有更好的解决方案。

谢谢

【问题讨论】:

您知道您可以覆盖-isEqual: 并访问子类的实现吗?在类层次结构中,每个子类只会检查它定义的属性。 是的,我知道我可以覆盖 isEqual 方法。我在原来的帖子中谈到了这个,看看。 但是你知道你可以调用超级实现吗? 是的,但我不明白你的回答是什么意思。 我扩展了我的答案。实际上是您没有正确执行 TDD。 【参考方案1】:

标准的 OOP 方法是每个子类测试自己的属性。所以每个属性都会被检查一次。我认为这是直截了当且容易的任务。完全没有危险,而且比在运行时杂耍更简单。

@interface AProduct : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *stockIdentifier;
@end

@implementation AProduct
-(BOOL)isEqual:(id)object

    BOOL equal= [object isKindOfClass:[self class]]
                && [[(AProduct *)object name] isEqualToString:self.name]
                && [[(AProduct *)object stockIdentifier] isEqualToString:self.stockIdentifier];
    return equal;

@end


@interface Shirt : AProduct
@property (nonatomic, copy) NSString *size;
@property (nonatomic, copy) NSString *color;
@end


@implementation Shirt
-(BOOL)isEqual:(id)object

    BOOL equal = [super isEqual:object];
    if (equal) 
        equal = [[(Shirt *)object size] isEqualToString:self.size]
                && [[(Shirt *)object color] isEqualToString:self.color];
    

    return equal;

并且由于您担心单元测试:您应该编写一个我认为也会失败的相等测试。如果红色和蓝色衬衫确实评估为相等,则此测试失败并且您知道您必须查看-isEqual:


可以说我只是将 AProduct 子类化为 Shirt。 AProduct 的 -isEqual: 工作正常,但我还没有在 Shirt 中覆盖和扩展它。

现在我编写以下测试方法:

- (void)testShirtInEquality

    Shirt *redShirt = [[Shirt alloc] init];
    redShirt.name =@"Shirt";
    redShirt.stockIdentifier =@"shirt";
    redShirt.size = @"XXL";
    redShirt.color = @"red";

    Shirt *blueShirt = [[Shirt alloc] init];
    blueShirt.name =@"Shirt";
    blueShirt.stockIdentifier =@"shirt";
    blueShirt.size = @"XXL";
    blueShirt.color = @"blue";
    XCTAssertNotEqualObjects(redShirt, blueShirt, @"not equal!");

它将失败,因为-isEqual: 的 AProduct 实现将触发并发现一切都相同。现在我知道我必须编写代码才能使这个测试通过,我添加了

-(BOOL)isEqual:(id)object

    BOOL equal = [super isEqual:object];
    if (equal) 
        equal = [[(Shirt *)object size] isEqualToString:self.size]
        && [[(Shirt *)object color] isEqualToString:self.color];
    

    return equal;

测试通过。

所以实际上答案不是写一个动态的-isEqual: 方法,而是紧紧地坚持测试驱动开发。


回顾一下:

TDD 圆舞曲

编写一个失败的测试 编写最简单的代码以通过测试 重构生产和测试代码 然后重复

【讨论】:

是的,谢谢,我同意你的看法。但是,在我添加到类中的每个属性中添加 isEqual 方法(或执行相同操作的自定义方法)中的比较测试是一项艰巨的任务。但是当我看到你所有的答案时,我想没有别的办法了。 为什么禁止给-isEqual:添加对比测试? 我们很容易忘记添加比较,然后,单元测试将不会完全检查两个对象之间的相等性。 这就是你进行单元测试的原因:得到提醒。这也是为什么您应该首先编写将失败的测试,然后通过添加正确的代码将它们变为绿色。 (我认为@JeanLbr 的意思是“不祥之兆”。)【参考方案2】:

如果您想为单元测试目的使用自动比较方法,并且此方法应该能够抵抗属性列表的更改,那么您别无选择,您必须使用元数据。 我不会在生产代码中使用isEqual: 方法,因为这种自动方法可能会使某人感到困惑并导致意想不到的问题。所以比较器应该只用于单元测试目的。

BOOL areEqual(AProduct *a, AProduct *b) 
    if (![a isMemberOfClass: [b class])
        return NO;

    unsigned int count = 0, i;
    objc_property_t *props = class_copyPropertyList([b class], &count)
    for (i=0; i<count; ++i) 
        objc_property_t property = props[i];
        NSString *name = [NSString stringWithUTF8String:property_getName(property)];

        id aValue = [a valueForKey: name];
        id bValue = [b valueForKey: name];

        if ([aValue isKindOfClass: [AProduct class]) 
            if (!areEqual((AProduct *)aValue, (AProduct *)bValue))
                return NO;
         else 
            if (![aValue isEqual: bValue])
                return NO;
        
    
    return YES;

可能我忘记了某些事情,但您应该大致了解如何做到这一点。

【讨论】:

感谢您的回答!我认为这种方法非常危险,因为如果开发人员在没有在 areEqual 方法中检查的情况下将属性添加到类中,那么篮子的单元测试将不再有用。但是如果没有其他方法,我会这样做。

以上是关于如何在objective-c中比较两个具有很多属性的对象的主要内容,如果未能解决你的问题,请参考以下文章

IOS/objective-c/core-data:如何从相关实体获取属性

如何比较两个没有顺序的具有相同属性的JSON?

如何快速比较核心数据中同一实体的两个属性值

在 Objective-C 中获取对象属性列表

比较两个文件夹,里面有很多文件

比较具有布尔属性的两个通用项,如果其中任何一个返回true,则返回结果通用列表中的true