简体   繁体   中英

Strategy pattern in Objective-C

I wrote a sample code strategy design pattern.

@protocol MyProtocol
- (void)execute1;
@end


@interface BaseClass : NSObject
@property(nonatomic, assign) NSInteger commonValue;
- (void)commonCalculator;
@end


@interface DogClass : BaseClass <MyProtocol>
@end

@interface CatClass : BaseClass <MyProtocol>
@end

In addition, I want to create a BaseClass to implement a common logic.
But, there is no way to access BaseClass from MyProtocol type.

For example

- (void)foo {
    NSInteger type = 0;

    id<MyProtocol> obj = [self simpleFactory:type];
    [obj execute1]; // It works!!

    // I'd like the following code. However compile error occurs.
    [obj commonCalculator]; // error
    obj.commonValue = 10; // error

    // I don't want the following code.
    if (type == 0 ) {
        [(DogClass *)obj commonCalculator];
        ((DogClass *)obj).commonValue = 10;
    } else {
        [(CatClass *)obj commonCalculator];
        ((CatClass *)obj).commonValue = 10;
    }
}

- (id<MyProtocol>)simpleFactory:(NSInteger)type {
    if (type == 0) {
        return [[DogClass alloc] init];
    } else {
        return [[CatClass alloc] init];
    }
}

Is there a way to use a common code in BaseClass while using a strategy pattern?

If BaseClass implements default behavior for <MyProtocol> , then BaseClass should adopt and implement <MyProtocol> .

@interface BaseClass : NSObject <MyProtocol>
@property(nonatomic, assign) NSInteger commonValue;
- (void)commonCalculator;
@end

The subclasses will then inherit that protocol:

@interface DogClass : BaseClass
...
@interface CatClass : BaseClass
...

The good news is the subclasses can call [super execute1] and the compiler won't complain if you try to use or pass an instance of BaseClass as id<MyProtocol> .

Now if, for some unexplained reason, you must to separate the code for the superclass implementation of BaseClass <MyProtocol> into its own module, it's possible to do that by creating a category of BaseClass that adopts and implements your default implementation there:

@interface BaseClass (MyProtocolDefaults) <MyProtocol>
@end

...

@implementation BaseClass (MyProtocolDefaults)
- (void)execute1
{
    ...
}
@end

If you do this, I'd still suggest that you still not re-adopt the protocol in your subclasses (even though it's perfectly legal), but instead "pick up" the protocol by importing the BaseClass category:

#import "BaseClass.h"
#import "BaseClass+MyProtocolDefaults.h"

@interface DogClass : BaseClass
// this class adopts <MyProtocol> indirectly through the BaseClass category

As far as I understand you correctly, I'm gonna try to explain:

1) I agree that you'd rather subscribe your base class to your protocol instead of subscribing each child. 2) If you gonna use polymorphism by protocol you should bring the:

- (void)commonCalculator;

method to the protocol, not to base class. Then you will be able to implement needed logic in needed place, like in base class. Just implement this method there.

3) Also I would like to give an advise: Apple engineers like to use Class Cluster pattern, it's a particular case of factory pattern. So, in the .h file you will have something like this:

@protocol MyProtocol<NSObject>

@property (assign, nonatomic, readonly) NSInteger commonValue;
- (void)commonCalculator;

- (void)execute;

@end

typedef NS_ENUM(NSUInteger, BaseClassType) {
    BaseClassTypeA,
    BaseClassTypeB,
    BaseClassTypeC
};

@interface BaseClass: NSObject<MyProtocol>

- (instancetype)initWithType:(BaseClassType)type;

@end

@interface SubclassA: BaseClass
@end

@interface SubclassB: BaseClass
@end

@interface SubclassC: BaseClass
@end

And through over your code you will be working only with instances of base class, but under the hood it will be instances of concrete subclass. So, the implementation of your .m file will look like:

@implementation BaseClass

- (instancetype)initWithType:(BaseClassType)type {
    switch (type) {
        case BaseClassTypeA: {
            self = [[SubclassA alloc] init];
        }
        case BaseClassTypeB: {
            self = [[SubclassB alloc] init];
        }
        case BaseClassTypeC: {
            self = [[SubclassC alloc] init];
        }
    }
    return self;
}

@end

As you can see, this illustrates an example of how to use strategy pattern, (because that is it, we have several strategies and relative to which one is instantiated the needed method will be executed, so, each strategy incapsulates some algorithm under the hood, but it hides behind common interface) in combination with Class Cluster pattern (which is the particular case of Factory pattern). You should remember that there are no any strict standards of how many methods you will bring into the strategy protocol as far as it harmoniously integrates with your app design. This approach makes you app design very graceful and understandable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM