設計模式之策略模式(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這兩個類即可,不必去認識各種算法類,大大降低了耦合度。

 

回頭來反思下策略模式,策略模式是一種定義一系列算法的方法,從概念上看,所有這些算法完成的都是相同的工作,只是實現不同,他可以以相同的方式調用所有的算法,減少了各種算法類和使用各算法類之間的耦合。

策略模式就是用來封裝算法的,但是在實際應用中,我們可以用它來封裝幾乎任何類型的規則,只要在分析過程中聽到需要在不同時間應用不同的業務規則,就可以考慮使用策略模式處理這種變化的可能性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM