• iOS-策略模式


    在实际开发过程中,app需求都是由产品那边给出,往往是他给出第一版功能,我们写好代码后,会相应的给出第二版、第三版功能,而这些功能是在实际使用中,根据用户需求而不断增加的。如果在编码之初,我们并未认识到这一点,并未后续添加的代码做好相应的设计准备,那么无疑,这个项目代码会越来越乱,就会导致这样一个循环:

      1. 产品提需求
      2. 我根据需求写代码
      3. 产品增加需求
      4. 为了在规定时间内完成任务,我根据需要增加的需求增加代码(由于没有思考好相应的设计,使得代码又长又乱)
      5. 产品再增加需求
      6. 我再增加代码,由于前面代码设计不合理,使得即使只增加一个小小的功能,我整个项目各个地方都要添加这样的代码
      7. 产品觉得某个功能不好,要删掉,然后我得在项目中找到各种地方对应功能的代码,删掉,还得撸顺上下的逻辑关系
      8. 这样子,发现工作量好大、没时间看书、没时间学习,总是撸相同质量的代码,技能得不到提升
      9. 如果不从自己身上找原因,就开始骂产品,有事没事总增加需求,觉得呆在这公司没意思,然后辞职......
      10. 下一家公司,又开始这样的循环......

    在知乎上看到这样一个问答:

    是不是程序员都感觉不幸福?过得很不好?因为不管bat公司还是中小公司,各种加班、天天撸代码,没时间谈女朋友.......

    看到这样一个很赞的回答:技术好的,都过得不错,技术差的......

    1. 现在有这样一个任务,做一个商场收银软件,营业员根据客户所购买的商品的单价和数量,向客户收费。

    很简单的一个需求,稍微分析就可以得到下图的界面:

    根据界面,也可以很快的将主要代码写出来:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    #import <Foundation/Foundation.h>
     
    @interface ZYTotalPrices : NSObject
    @property (nonatomic, assign) double singlePrices;
    @property (nonatomic, assign) int number;
     
    - (instancetype)initWithSinglePrices:(NSString *)singlePrices number:(NSString *)number;
    - (double)totalPrices;
    @end
     
     
     
    #import "ZYTotalPrices.h"
     
    @implementation ZYTotalPrices
     
    - (instancetype)initWithSinglePrices:(NSString *)singlePrices number:(NSString *)number
    {
        if (self = [super init]) {
            self.number = [number intValue];
            self.singlePrices = [singlePrices doubleValue];
        }
        return self;
    }
     
    - (double)totalPrices
    {
        return self.number * self.singlePrices;
    }
    @end

     然后产品跑过来和你说,现在有新的需求了,商场促销,所有商品打八折。

    想想,直接在- (double)totalPrices 方法里面,再乘以0.8就ok了......这样是满足了现有的需求,那要是某天商场打七折或者说,打完折回复原价,怎么办?

    2. 增加打折

    其实,只需要增加一个下拉选择框,让收银员选择当前的打折就可以了,主要代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    #import <Foundation/Foundation.h>
     
    @interface ZYTotalPrices : NSObject
    @property (nonatomic, assign) double singlePrices;
    @property (nonatomic, assign) int number;
    /**
     *  下拉菜单中的某个属性字符串
     */
    @property (nonatomiccopyNSString *itemStr;
     
    - (instancetype)initWithSinglePrices:(NSString *)singlePrices number:(NSString *)number;
    - (double)totalPrices;
    @end
     
     
     
     
     
    #import "ZYTotalPrices.h"
     
    @interface ZYTotalPrices ()
    /**
     *  打折语句
     */
    @property (nonatomic, strong) NSArray *discountStrs;
    /**
     *  打折的具体数值
     */
    @property (nonatomic, strong) NSArray *discountNumbers;
     
    /**
     *  打折对应下标
     */
    @property (nonatomic, assign) int index;
    @end
     
    @implementation ZYTotalPrices
     
    - (NSArray *)discountNumbers
    {
        if (!_discountNumbers) {
            _discountNumbers = @[@(1), @(0.8), @(0.5)];
        }
        return _discountNumbers;
    }
     
    - (NSArray *)discountStrs
    {
        if (!_discountStrs) {
            _discountStrs = @[@"正常收费", @"打八折", @"打五折"];
             
            // 默认为正常收费
            self.index = 0;
        }
        return _discountStrs;
    }
     
    - (void)setItemStr:(NSString *)itemStr
    {
        _itemStr = [itemStr copy];
         
        [self.discountStrs enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) {
            if ([obj isEqualToString:itemStr]) {
                self.index = (int)idx;
                *stop = YES;
            }
        }];
    }
     
    - (instancetype)initWithSinglePrices:(NSString *)singlePrices number:(NSString *)number
    {
        if (self = [super init]) {
            self.number = [number intValue];
            self.singlePrices = [singlePrices doubleValue];
        }
        return self;
    }
     
    - (double)totalPrices
    {
        double tempNumber = [self.discountNumbers[self.index] doubleValue];
        return self.number * self.singlePrices * tempNumber;
    }
    @end

     差不多就有了下面这样的界面:(请勿完全对照)

    这时候,产品过来说,需要增加“满300送100”的活动。这完全是可以在类里多增加一个方法,来计算这个的。但是,这种方法并不好,事实上,

    我们完全可以抽出一个抽象基类出来,基类里面有个方法,就是返回总的钱数,而各个不同算法的子类继承自这个基类,实现基类里面的方法,然后用工具类根据不同的需求调用不同的子类,这样的话,分工很明确,如果有什么特殊的需求,找到这个子类增加就ok

    3. 简单工厂模式

    需要注意的是,其实打八折、七折、六折...完全是可以用个变量来表示的,没必要衍生出很多子类,直接就是一个打折子类,内部确定打折的具体折数;同理,满**减**也是如此。

    面向对象编程,并不是类越多越好,类的划分是为了封装,但划分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。

    打多少折,只是形式的不同,抽象分析出来,所有的打折算法是一样的,所有打折算法应该是一个类。同理,满**减**也是如此。

    代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    抽象基类:
     
    #import <Foundation/Foundation.h>
    @interface ZYTotalPrices : NSObject
    @property (nonatomic, assign) double singlePrices;
    @property (nonatomic, assign) int number;
    /**
     *  如此,抽象类里面有一个方法声明,实现交给子类
     *
     */
    - (double)totalPrices;
    @end
     
     
    #import "ZYTotalPrices.h"
    @implementation ZYTotalPrices
    @end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    正常价格:
     
    #import "ZYTotalPrices.h"
     
    @interface ZYPricesNoraml : ZYTotalPrices
     
    @end
     
    #import "ZYPricesNoraml.h"
     
    @implementation ZYPricesNoraml
    /**
     *  正常收费
     *
     */
    - (double)totalPrices
    {
        return self.singlePrices * self.number;
    }
    @end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    打折:
     
    #import "ZYTotalPrices.h"
     
    @interface ZYPricesDiscount : ZYTotalPrices
    /**
     *  打折率,为小数
     */
    @property (nonatomic, assign) double moneyRebate;
     
    - (instancetype)initWithMoneyRebate:(double)moneyRebate;
    @end
     
     
    #import "ZYPricesDiscount.h"
     
    @implementation ZYPricesDiscount
     
    - (instancetype)initWithMoneyRebate:(double)moneyRebate
    {
        if (self = [super init]) {
            self.moneyRebate = moneyRebate;
        }
        return self;
    }
     
    - (double)totalPrices
    {
        return self.number * self.singlePrices * self.moneyRebate;
    }
    @end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    返利:
     
    #import "ZYTotalPrices.h"
     
    @interface ZYPricesReturn : ZYTotalPrices
    /**
     *  返利条件
     */
    @property (nonatomic, assign) double moneyCondition;
     
    /**
     *  返利值
     */
    @property (nonatomic, assign) double moneyReturn;
     
    - (instancetype)initWithMoneyCondition:(double)moneyCondition moneyReturn:(double)moneyReturn;
    @end
     
     
    #import "ZYPricesReturn.h"
     
    @implementation ZYPricesReturn
    - (instancetype)initWithMoneyCondition:(double)moneyCondition moneyReturn:(double)moneyReturn
    {
        if (self = [super init]) {
            self.moneyCondition = moneyCondition;
            self.moneyReturn = moneyReturn;
        }
        return self;
    }
     
    - (double)totalPrices
    {
        double total = self.number * self.singlePrices;
        return total - (int)(total / self.moneyCondition) * self.moneyReturn;
    }
    @end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    工具类:
     
    #import <Foundation/Foundation.h>
     
    @class ZYTotalPrices;
    @interface ZYPricesTool : NSObject
    + (ZYTotalPrices *)createTotalPricesWithItemStr:(NSString *)itemStr;
    @end
     
     
    #import "ZYPricesTool.h"
    #import "ZYPricesNoraml.h"
    #import "ZYPricesDiscount.h"
    #import "ZYPricesReturn.h"
     
    static NSArray *_arrayStrs;
    @implementation ZYPricesTool
    + (NSArray *)arrayStrs
    {
        if (_arrayStrs == nil) {
            _arrayStrs = @[@"正常收费", @"满300返100", @"打八折"];
        }
        return _arrayStrs;
    }
     
    + (ZYTotalPrices *)createTotalPricesWithItemStr:(NSString *)itemStr
    {
        [self arrayStrs];
         
        __block int index = 0;
         
        [_arrayStrs enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) {
            if ([obj isEqualToString:itemStr]) {
                index = (int)idx;
                *stop = YES;
            }
        }];
         
        switch (index) {
            case 0:
                return [[ZYPricesNoraml alloc] init];
                break;
            case 1:
                return [[ZYPricesReturn alloc] initWithMoneyCondition:300 moneyReturn:100];
                break;
            case 2:
                return [[ZYPricesDiscount alloc] initWithMoneyRebate:0.8];
                break;
        }
        return nil;
    }
     
    @end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    控制器内代码:
     
    #import "ViewController.h"
    #import "ZYPricesTool.h"
    #import "ZYPricesNoraml.h"
    #import "ZYPricesDiscount.h"
    #import "ZYPricesReturn.h"
    @interface ViewController ()
     
    @end
     
    @implementation ViewController
     
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
         
        ZYTotalPrices *totalPrices = [ZYPricesTool createTotalPricesWithItemStr:@"打八折"];
         
        totalPrices.number = 5;
        totalPrices.singlePrices = 6;
         
        NSLog(@"%lf",[totalPrices totalPrices]);
    }
    @end

     这次,不论产品跑过要求怎么修改,都可以很简单的实现了。比如说,要“满500返200”,直接在ZYPricesTool里面增加一个就好,算法完全不需要重新写。如果又有一种需求,"满100元返积分10点,一次购物到一定积分,赠送礼物",直接产生一个继承自ZYTotalPrices的子类,在里面写产生积分的算法,然后在工具类里面增加响应处理即可,其他已经处理了的类,完全不需要去改动。

    但是,也是优缺点,虽然简单的工厂设计模式解决了这个问题,但是这个模式只是解决了对象的创建问题,而且由于工厂本身包含了所有的收费方式,商场是有可能经常性的更改打折额度和返利额度,每次维护和扩展收费方式都要更改这个工厂类,以至于代码需要重新编译、部署,那这种处理方式就显得比较糟糕了。面对算法的时常变动,应该有更好的设计方法......

    4. 策略模式

    策略模式定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户。

    商场收银时如何促销,用打折还是返利,其实都是一些算法,用工厂来生成算法对象,这没错,但是算法本身只是一种策略,最主要的是,这些算法本身是可以随时替换的,这就是变化点,而封装变化点是面向对象的一种很重要的思维方式。

    接下来就是策略模式代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #import <Foundation/Foundation.h>
    @interface ZYTotalPrices : NSObject
    @property (nonatomic, assign) double singlePrices;
    @property (nonatomic, assign) int number;
    /**
     *  如此,抽象类里面有一个方法声明,实现交给子类
     *
     */
    - (double)totalPrices;
    @end
     
     
    #import "ZYTotalPrices.h"
    @implementation ZYTotalPrices
    @end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #import "ZYTotalPrices.h"
     
    @interface ZYPricesNoraml : ZYTotalPrices
     
    @end
     
     
    #import "ZYPricesNoraml.h"
     
    @implementation ZYPricesNoraml
    /**
     *  正常收费
     *
     */
    - (double)totalPrices
    {
        return self.singlePrices * self.number;
    }
    @end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #import "ZYTotalPrices.h"
     
    @interface ZYPricesDiscount : ZYTotalPrices
    /**
     *  打折率,为小数
     */
    @property (nonatomic, assign) double moneyRebate;
     
    - (instancetype)initWithMoneyRebate:(double)moneyRebate;
    @end
     
     
    #import "ZYPricesDiscount.h"
     
    @implementation ZYPricesDiscount
     
    - (instancetype)initWithMoneyRebate:(double)moneyRebate
    {
        if (self = [super init]) {
            self.moneyRebate = moneyRebate;
        }
        return self;
    }
     
    - (double)totalPrices
    {
        return self.number * self.singlePrices * self.moneyRebate;
    }
    @end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    #import "ZYTotalPrices.h"
     
    @interface ZYPricesReturn : ZYTotalPrices
    /**
     *  返利条件
     */
    @property (nonatomic, assign) double moneyCondition;
     
    /**
     *  返利值
     */
    @property (nonatomic, assign) double moneyReturn;
     
    - (instancetype)initWithMoneyCondition:(double)moneyCondition moneyReturn:(double)moneyReturn;
    @end
     
     
    #import "ZYPricesReturn.h"
     
    @implementation ZYPricesReturn
    - (instancetype)initWithMoneyCondition:(double)moneyCondition moneyReturn:(double)moneyReturn
    {
        if (self = [super init]) {
            self.moneyCondition = moneyCondition;
            self.moneyReturn = moneyReturn;
        }
        return self;
    }
     
    - (double)totalPrices
    {
        double total = self.number * self.singlePrices;
        return total - (int)(total / self.moneyCondition) * self.moneyReturn;
    }
    @end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    #import <Foundation/Foundation.h>
    @class ZYTotalPrices;
    @interface ZYPricesContent : NSObject
    @property (nonatomic, strong) ZYTotalPrices *totalPrice;
     
    /**
     *  必须根据相应的itemStr字符串创建对应的算法对象
     *
     */
    - (instancetype)initWithItemStr:(NSString *)itemStr;
     
    - (double)getResult;
    @end
     
     
    #import "ZYPricesContent.h"
    #import "ZYTotalPrices.h"
    #import "ZYPricesNoraml.h"
    #import "ZYPricesDiscount.h"
    #import "ZYPricesReturn.h"
     
    @implementation ZYPricesContent
    - (instancetype)initWithItemStr:(NSString *)itemStr
    {
        if (self = [super init]) {
            [self commitInit:itemStr];
        }
        return self;
    }
     
    - (void)commitInit:(NSString *)itemStr
    {
        if ([itemStr isEqualToString:@"正常收费"]) {
            self.totalPrice = [[ZYPricesNoraml alloc] init];
        }
        else if ([itemStr isEqualToString:@"满300返100"]) {
            self.totalPrice = [[ZYPricesReturn alloc] initWithMoneyCondition:300 moneyReturn:100];
        }
        else {
            self.totalPrice = [[ZYPricesDiscount alloc] initWithMoneyRebate:0.8];
        }
    }
     
    - (double)getResult
    {
        return [self.totalPrice totalPrices];
    }
    @end

     仔细看这一次的代码,会发现,和上面用工厂调用的代码基本没什么变化,只是将工具类,改成了ZYPricesReturn类,这个类包含ZYTotalPrices类,持有ZYTotalPrices对象,还有就是原本在工具类里面判断的,具体调用哪个算法对象,也在ZYPricesReturn类里面实现了,可以说,ZYPricesReturn类就是为了ZYTotalPrices服务的。

    来看看viewController里面的代码,就可以看出很明显的区别,也可以了解策略模式的好处在哪:

    先是,调用工具类的viewController代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #import "ViewController.h"
    #import "ZYPricesTool.h"
    #import "ZYPricesNoraml.h"
    #import "ZYPricesDiscount.h"
    #import "ZYPricesReturn.h"
    @interface ViewController ()
     
    @end
     
    @implementation ViewController
     
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
         
        ZYTotalPrices *totalPrices = [ZYPricesTool createTotalPricesWithItemStr:@"打八折"];
         
        totalPrices.number = 5;
        totalPrices.singlePrices = 6;
         
        NSLog(@"%lf",[totalPrices totalPrices]);
         
    }
    @end

     可以很明显的发现,这种方法实现的,viewController想要调用各种算法,那么就得包含各种算法的头文件,创建并持有各种算法对象。

    那么,如果是策略模式实现的代码,viewController里面的代码是怎么样的呢:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #import "ViewController.h"
    #import "ZYPricesContent.h"
    #import "ZYTotalPrices.h"
    @interface ViewController ()
     
    @end
     
    @implementation ViewController
     
    - (void)viewDidLoad {
        [super viewDidLoad];
         
        ZYPricesContent *content = [[ZYPricesContent alloc] initWithItemStr:@"满300返100"];
        content.totalPrice.number = 5;
        content.totalPrice.singlePrices = 120;
         
        NSLog(@"%lf",[content getResult]);
    }
    @end

     可以看到,viewController只需要认识ZYTotalPrices与ZYPricesContent这两个类即可,不必去认识各种算法类,大大降低了耦合度。

    回头来反思下策略模式,策略模式是一种定义一系列算法的方法,从概念上看,所有这些算法完成的都是相同的工作,只是实现不同,他可以以相同的方式调用所有的算法,减少了各种算法类和使用各算法类之间的耦合。

    策略模式就是用来封装算法的,但是在实际应用中,我们可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。

    朋友们,虽然这个世界日益浮躁起来,只要能够为了当时纯粹的梦想和感动坚持努力下去,不管其它人怎么样,我们也能够保持自己的本色走下去。
  • 相关阅读:
    Uva11584 Partitioning by Palindromes
    GYM100741 A Queries
    Uva11400 Lighting System Design
    UVA12563 Jin Ge Jin Qu hao
    Uva116 Unidirectional TSP
    HDU2089 不要62
    BZOJ3670: [Noi2014]动物园
    Uva11384 Help is needed for Dexter
    Uva1347 Tour
    BZOJ1924: [Sdoi2010]所驼门王的宝藏
  • 原文地址:https://www.cnblogs.com/LifeTechnologySupporter/p/5031667.html
Copyright © 2020-2023  润新知