设计模式之策略模式(iOS开发,代码用Objective-C展示)

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

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

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

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

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

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

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

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

#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. 增加打折

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

#import <Foundation/Foundation.h>

@interface ZYTotalPrices : NSObject
@property (nonatomic, assign) double singlePrices;
@property (nonatomic, assign) int number;
/**
 *  下拉菜单中的某个属性字符串
 */
@property (nonatomic, copy) NSString *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. 简单工厂模式

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

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

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

代码如下:

抽象基类:

#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
正常价格:

#import "ZYTotalPrices.h"

@interface ZYPricesNoraml : ZYTotalPrices

@end

#import "ZYPricesNoraml.h"

@implementation ZYPricesNoraml
/**
 *  正常收费
 *
 */
- (double)totalPrices
{
    return self.singlePrices * self.number;
}
@end
打折:

#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
返利:

#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
工具类:

#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
控制器内代码:

#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. 策略模式

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

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

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

#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
#import "ZYTotalPrices.h"

@interface ZYPricesNoraml : ZYTotalPrices

@end

#import "ZYPricesNoraml.h"

@implementation ZYPricesNoraml
/**
 *  正常收费
 *
 */
- (double)totalPrices
{
    return self.singlePrices * self.number;
}
@end
#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
#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
#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代码:

#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里面的代码是怎么样的呢:

#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这两个类即可,不必去认识各种算法类,大大降低了耦合度。

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

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

时间: 2024-10-12 10:10:36

设计模式之策略模式(iOS开发,代码用Objective-C展示)的相关文章

[设计模式-04]策略模式-对开发和测试的意义

面向对象和面向过程最大的区别在于复用性和扩展性.这里的复用性不仅仅指代码级别的复用性,更重要的是模块级别的复用性.良好的面向对象设计面向需求的变更只需要变更模块运行的流程即可,更复杂的需求变更也只需要增加具体的执行模块即可.if...else在代码级别确实做到了一些复用性,因为避免了代码拷贝的问题,但是在面对需要变更执行流程或者增加执行模块时却需要修改代码,违背了五大原则中的"面向修改封闭,面向扩张开放"的原则. 如果是TDD开发,对于if...else,写单元测试的时候,有多少个if

大话设计模式_策略模式(Java代码)

策略模式:定义算法家族,分别封装,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户 简单描述:一个父类,多个子类实现具体方法.一个Context类持有父类的引用(使用子类实例化此引用),客户端代码只需要与此Context类交互即可 大话设计模式中的截图: 例子代码: 策略类: 1 package com.longsheng.strategy; 2 3 public abstract class Strategy { 4 5 public abstract double getR

iOS 开发代码规范有哪些

对于刚刚入门ios的同学来说,iOS 开发代码规范是很重要的知识的,这里就给大家简单总结了一下. 一.工程规范 1.功能分类 根据所做功能的不同,分为不同的功能模块,比如登录模块,首页模块,个人模块等,根据不同的功能,代码必须要放在不同功能的文件夹下. 2.代码文件分类 不管是MVC模式,MVVM模式,或是其他设计模式,在不同的功能模块下,视图控制器(Controllers),视图(Views),模型类(Models),也必须要分别存放. 3.第三方库分类 工程中会经常使用第三方库,在引入第三方

[design-patterns]设计模式之一策略模式

设计模式 从今天开始开启设计模式专栏,我会系统的分析和总结每一个设计模式以及应用场景.那么首先,什么是设计模式呢,作为一个软件开发人员,程序人人都会写,但是写出一款逻辑清晰,扩展性强,可维护的程序就不是那么容易做到了.现实世界的问题复杂多样,如何将显示问题映射到我们编写的程序中本就是困难重重.另一方面,软件开发中一个不变的真理就是"一切都在变化之中",这种变化可能来自于程序本身的复杂度,也可能来自于客户不断变化的需求,这就要求我们在编写程序中一定要考虑变化的因素,将变化的因素抽离出来,

设计模式之策略模式(Strategy)摘录

23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象.创建型模式有两个不断出现的主旋律.第一,它们都将关于该系统使用哪些具体的类的信息封装起来.第二,它们隐藏了这些类的实例是如何被创建和放在一起的.整个系统关于这些对象所知道的是由抽象类所定义的接口.因此,创建型模式在什么被创建,谁创建它,它是怎样被创建的,以

Head First 设计模式之一 策略模式

策略模式 定义 策略模式定义了算法族,分别封装起来,让他们之间可以相互转换,此模式让算法的变化独立于使用算法的客户. 实例 上面的定义看起来说的不太清楚,记定义无意义,理解策略模式还是要看书中的鸭子例子.假设设计一个模拟鸭子的游戏,鸭子的种类有很多,有红头鸭.绿头鸭等等,鸭子可以划水,可以呱嘎叫.在这个模拟游戏的实现上,自然会想到用继承的方法,定义一个鸭子基类,具体的鸭子类型继承自鸭子基类.如下图所示 所有鸭子都会飞.呱呱叫和游泳,这些功能由基类来实现,display函数用来输出鸭子实例的类型,

JavaScript设计模式之策略模式(学习笔记)

在网上搜索“为什么MVC不是一种设计模式呢?”其中有解答:MVC其实是三个经典设计模式的演变:观察者模式(Observer).策略模式(Strategy).组合模式(Composite).所以我今天选择学习策略模式. 策略模式:定义了一系列家族算法,并对每一种算法单独封装起来,让算法之间可以相互替换,独立于使用算法的客户. 通常我并不会记得“牛顿第一定律”的具体内容,所以我也难保证我会对这个定义记得多久……用FE经常见到的东西来举个例子说明一下: $("div").animation(

设计模式之策略模式20170720

行为型设计模式之策略模式: 一.含义 策略模式是一种比较简单的模式,也叫做政策模式,其定义如下: 定义一组算法(可抽象出接口),将每个算法都封装起来,并且使它们之间可以互换(定义一个类实现封装与算法切换) 二.代码说明 1.主要有两个角色 1)Context封装角色 它也叫做上下文角色,起承上启下封装作用,屏蔽高层模块对策略,算法的直接访问,封装可能存在的变化. 2)策略角色 该类含有具体的算法 2.在用C实现过程中也是参考这种思想,以压缩,解压算法举例,具体实现如下: 1)策略模式使用场景

&lt;二&gt;读&lt;&lt;大话设计模式&gt;&gt;之策略模式

又和大家见面了,能够坚持写出第二篇文章真不错,好好加油. <<大话设计模式>>讲解策略模式是以商场收银软件程序开头的,那么问题来了,哪家商场收银软件强,开玩笑了.读过上篇文章<<简单工厂模式>>的知道,它有两个缺点:1.客户端依赖两个类,耦合性高:2.如果算法过多则需要写很多类.解决上面问题的就是策略模式了. 策略模式:它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户. 商场收银软件:单价*打折算法=售价.