美文网首页
OC中几种常用的设计模式以及工厂模式(Java版)

OC中几种常用的设计模式以及工厂模式(Java版)

作者: JasonEVA | 来源:发表于2016-12-08 17:11 被阅读234次

    一、OC中几种常用的设计模式

    1.单例模式(Singleton)

    在iOS开发我们经常碰到只需要某类一个实例的情况,如:

    ● UIApplication类提供了 +sharedAPplication方法创建和获取UIApplication单例

    ● NSBundle类提供了 +mainBunle方法获取NSBundle单例

    ● NSFileManager类提供了 +defaultManager方法创建和获得NSFileManager单例。(PS:有些时候我们得放弃使用单例模式,使用-init方法去实现一个新的实例,比如使用委托时)

    ● NSNotificationCenter提供了 +defaultCenter方法创建和获取NSNotificationCenter单例(PS:该类还遵循了另一个重要的设计模式:观察者模式)

    ● NSUserDefaults类提供了 +defaultUserDefaults方法去创建和获取NSUserDefaults单例

    如何实现单例模式

    首先给大家介绍一下GCD技术,是苹果针对于多核CPU的多任务解决方案。是一组基于C语言开发的API。GCD提供了一个dispatch_once函数,这个函数的作用就是保证block里的语句在整个应用的生命周期里只执行一次。

    实现代码

    #import "JasonDemo.h"
    
    @implementation JasonDemo
    
    
    + (instancetype)sharedInstance {
        static JasonDemo *sharedInstance = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            sharedInstance = [[JasonDemo alloc] init];
        });
        return sharedInstance;
    }
    
    @end
    

    上述代码中有5小步,解释如下:

    1. 声明一个可以新建和获取单个实例对象的方法

    2. 声明一个static类型的类变量

    3. 声明一个只执行一次的任务

    4. 调用dispatch_once执行该任务指定的代码块,在该代码块中实例化上文声明的类变量

    5. 返回在整个应用的生命周期中只会被实例化一次的变量

    单例模式作用

    • 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。

    • 由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决(在Java EE中采用单例模式时需要注意JVM垃圾回收机制)。

    • 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。

    • 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

    举个栗子

    跳转使用单例之前

    跳转管理基类

    #import <Foundation/Foundation.h>
    
    @class CHBaseController;
    
    @interface CCInteractor : NSObject
    {
    }
    
    @property (nonatomic, weak)   CHBaseController*       baseController;
    
    @end
    

    具体业务跳转子类

    @interface CHContactsInteractor : CCInteractor
    
    - (void)goOrganization;
    
    - (void)goMyDepartmentWithDeptID:(NSString *)deptID andDeptName:(NSString *)deptName;
    
    @end
    

    使用

    @property (nonatomic, strong) CHContactsInteractor *contactsInteractor;
    
    self.contactsInteractor = [CHContactsInteractor new];
    
    [self.contactsInteractor goOrganization];
    
    

    使用单例优化以后

    跳转管理基类

    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
    
    @interface ATModuleInteractor : NSObject
    
    + (instancetype)sharedInstance;
    
    // 普通push
    - (void)pushToVC:(UIViewController *)VC;
    
    // 强制跳转rootView再push
    - (void)PushToChatVC:(UIViewController *)VC;
    @end
    
    
    

    具体业务跳转管理分类

    #import "ATModuleInteractor.h"
    
    @interface ATModuleInteractor (CoordinationInteractor)
    
    // 任务列表
    - (void)goTaskList;
    @end
    
    

    使用

        [[ATModuleInteractor sharedInstance] goTaskList];
    
    

    2.观察者模式

    概念:操作对象向被观察者对象投送消息,使得被观察者的状态得以改变,在此之前已经有观察者向被观察对象注册,订阅它的广播,现在被观察对象将自己状态发生改变的消息广播出来,观察者接收到消息各自做出应变。

    苹果的Cocoa Touch框架中使用了观察者模式的有:通知机制和KVO

    2.1 通知(notification)机制

    在通知机制中对某个通知感兴趣的所有对象都可以成为接受者。首先,这些对象需要向通知中心(NSNotificationCenter)发出addObserver:selector:name:object:消息进行注册,在投送对象投送通知送给通知中心时,通知中心就会把通知广播给注册过的接受者。所有的接受者不知道通知是谁投送的,不去关心它的细节。投送对象和接受者是一对多的关系。接受者如果对通知不再关注,会给通知中心发送removeObserver:name:Object:消息解除注册,以后不再接受通知。

    代码尝试

    1.进入后台时,发出通知

    - (void)applicationDidEnterBackground:(UIApplication *)application {
        [[NSNotificationCenter defaultCenter]postNotificationName:@"APPTerminate" object:self];
    }
    
    #import "ViewController.h"
     
    @interface ViewController ()
     
    @end
     
    @implementation ViewController
     
    - (void)viewDidLoad {
        [super viewDidLoad];
        //注意此处的selector有参数,要加冒号
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(doSomething:) name:@"APPTerminate" object:nil];
        // Do any additional setup after loading the view, typically from a nib.
    }
     
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        [[NSNotificationCenter defaultCenter]removeObserver:self];
        // Dispose of any resources that can be recreated.
    }
     
    #pragma mark -处理通知
    -(void)doSomething:(NSNotification*)notification{
        NSLog(@"收到进入后台通知");
    }
     
    @end
    

    在APP退到后台时,发出广播,而viewController因为时观察者,收到广播,执行doSomething方法,打印出收到广播的文字。

    2.2 KVO(Key-Value-Observing)机制

    该机制下观察者的注册是在被观察者的内部进行的,不同于通知机制(由观察者自己注册),需要被观察者和观察者同时实现一个协议:NSKeyValueObserving,被观察者通过addObserver:forKeypath:options:context方法注册观察者,以及要被观察的属性。

    实现代码

    被观察类(由于继承自NSObject,而NSObject已实现了NSKeyValueObserving协议,所以不需要做声明)

    #import <Foundation/Foundation.h>
    
    @interface JWWatchModel : NSObject
    @property (nonatomic, copy) NSString *JWName;
    @property (nonatomic, copy) NSString *JWAge;
    @end
    
    

    观察类

    - (void)viewDidLoad {
        [super viewDidLoad];
        self.JWModel = [JWWatchModel new];
        
        [self.JWModel addObserver:self forKeyPath:@"JWName" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:@"你考了几分"];
        UIBarButtonItem *changeBtn = [[UIBarButtonItem alloc] initWithTitle:@"戳我啊" style:UIBarButtonItemStylePlain target:self action:@selector(changeClick)];
        [self.navigationItem setRightBarButtonItem:changeBtn];
        
        // Do any additional setup after loading the view.
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        if ([keyPath isEqualToString:@"JWName"]) {
            UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:(__bridge NSString *)context message:[self dictionaryToJson:change] preferredStyle:UIAlertControllerStyleAlert];
            UIAlertAction *action = [UIAlertAction actionWithTitle:@"好的" style:UIAlertActionStyleCancel handler:nil];
            [alterVC addAction:action];
            [self presentViewController:alterVC animated:YES completion:nil];
            NSLog(@"%@",change);
        }
    }
    

    3.委托代理模式

    委托模式就像是java中的接口,类可以实现或不实现协议(接口)中的方法。通过此种方式,达到最大的解耦目的,方便项目的扩展。不过你需要设置应用的委托对象,以确定协议中的方法为谁服务。

    拿最常用的UITableViewDelegate UITableViewDataSource来举例:

    实现一个页面有一个UItableView,UItableView的每一栏(cell)的数据由我们指定,那么我们该如何做呢?苹果也自然想到了这一点,于是定义了一个接口,这个接口有许多的方法,只需要我们把要服务的对象传进去,就可以使用这些方法了,这个接口就是委托和协议。而UITableViewDelegate 和 UITableViewDataSource 就是专为UITableView而写的委托和协议。用法如下:

    #pragma mark - UITableView Delegate
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        return self.serviceDetail.payWayList.count;
    }
    
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
        return 1;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
       static NSString *CoustomerTableIdentifier = @"RecipeTableCellTableViewCell";
     
        RecipeTableCellTableViewCell *cell =(RecipeTableCellTableViewCell *) [tableView dequeueReusableCellWithIdentifier:CoustomerTableIdentifier];
     
        if (cell == nil) {
           cell = [[RecipeTableCellTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CoustomerTableIdentifier];
        }
     
        recipe = [recipes objectAtIndex:indexPath.row];
     
        cell.nameLabel.text =  recipe.name;
        cell.prepTimeLabel.text= recipe.prepTime;
        cell.thumbnailImageView.image = [UIImage imageNamed:recipe.image];
     
        return cell;}
    - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
        return 0.0001;
    }
    
    - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
        return 0.0001;
    }
    
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        ServicePayWay *payway = self.serviceDetail.payWayList[indexPath.row];
        if ([self.selectedPayway.payWayCode isEqualToString:payway.payWayCode]) {
            return;
        }
        self.selectedPayway = payway;
        [self.tableView reloadData];
    }
    

    如上所示的方法全部来自于委托和协议。利用委托和协议,你可以把主要精力放到逻辑业务上,将数据绑定和事件处理交给委托和协议去完成。

    二、工厂模式

    女娲造人的故事

    东汉《风俗通》记录了一则神话故事:“开天辟地,未有人民,女娲搏黄土做人”,讲述的内容就是大家非常熟悉的女娲造人的故事。开天辟地之初,大地上并没有生物,只有苍茫大地,纯粹而洁净的自然环境,寂静而又寂寞,于是女娲决定创造一个新物种(即人类)来增加世界的繁荣,怎么制造呢?

    别忘了女娲是神仙,没有办不到的事情,造人的过程是这样的:首先,女娲采集黄土捏成人的形状,然后放到八卦炉中烧制,最后放置到大地上生长,工艺过程是没有错的,但是意外随时都会发生:

    第一次烤泥人,感觉应该熟了,往大地上一放,哇,没烤熟!于是一个白人诞生了!(这也是缺乏经验的最好证明)

    第二次烤泥人,上一次没烤熟,这次多烤一会儿,放到世间一看,嘿,熟过头了,于是黑人诞生了!

    第三次烤泥人,一边烧制一边察看,直到表皮微黄,嘿,真正好,于是黄色人种出现了!

    这个造人过程是比较有意思的,是不是可以通过软件开发来实现这个过程呢?古人云:“三人行,必有我师焉”,在面向对象的思维中,万物皆对象,是对象我们就可以通过软件设计来实现。首先对造人过程进行分析,该过程涉及三个对象:女娲、八卦炉、三种不同肤色的人。女娲可以使用场景类Client来表示,八卦炉类似于一个工厂,负责制造生产产品(即人类),三种不同肤色的人,他们都是同一个接口下的不同实现类,都是人嘛,只是肤色、语言不同,对于八卦炉来说都是它生产出的产品。分析完毕,我们就可以画出如图8-1所示的类图。

    d7260e63844769eae109ed60.jpeg

    图8-1

    类图比较简单,AbstractHumanFactory是一个抽象类,定义了一个八卦炉具有的整体功能,HumanFactory为实现类,完成具体的任务——创建人类;Human接口是人类的总称,其三个实现类分别为三类人种;NvWa类是一个场景类,负责模拟这个场景,执行相关的任务。

    我们定义的每个人种都有两个方法:getColor(获得人的皮肤颜色)和talk(交谈),其源代码如代码清单8-1所示。

    代码清单8-1 人类总称

    public interface Human{
    //每个人种的皮肤都有相应的颜色
    public void getColor();
    //人类会说话
    public void talk();
    }
    

    接口Human是对人类的总称,每个人种都至少具有两个方法,黑色人种、黄色人种、白色人种的代码分别如代码清单8-2、代码清单8-3、代码清单8-4所示。

    代码清单8-2 黑色人种

    public class BlackHuman implements Human{
    public void getColor(){
    System.out.println("黑色人种的皮肤颜色是黑色的!");
    }
    public void talk(){
    System.out.println("黑人会说话,一般人听不懂。");
    }
    }
    

    代码清单8-3黄色人种

    public class YellowHuman implements Human{
    public void getColor(){
    System.out.println("黄色人种的皮肤颜色是黄色的!");
    }
    public void talk(){
    System.out.println("黄色人种会说话,一般说的都是双字节。");
    }
    }
    

    代码清单8-4 白色人种

    public class WhiteHuman implements Human{
    public void getColor(){
    System.out.println("白色人种的皮肤颜色是白色的!");
    }
    public void talk(){
    System.out.println("白色人种会说话,一般都是但是单字节。");
    }
    }
    

    所有的人种定义完毕,下一步就是定义一个八卦炉,然后烧制人类。我们想象一下,女娲最可能给八卦炉下达什么样的生产命令呢?应该是“给我生产出一个黄色人种(YellowHuman类)”,而不会是“给我生产一个会走、会跑、会说话、皮肤是黄色的人种”,因为这样的命令增加了交流的成本,作为一个生产的管理者,只要知道生产什么就可以了,而不需要事物的具体信息。通过分析,我们发现八卦炉生产人类的方法输入参数类型应该是Human接口的实现类,这也解释了为什么类图上的AbstractHumanFactory抽象类中createHuman方法的参数为Class类型。其源代码如代码清单8-5所示。

    代码清单8-5 抽象人类创建工厂

    public abstract class AbstractHumanFactory{
    public abstract<T extends Human>T createHuman(Class<T>c);
    }
    

    注意,我们在这里采用了泛型(Generic),通过定义泛型对createHuman的输入参数产生两层限制:

    • 必须是Class类型;
    • 必须是Human的实现类。

    其中的“?”表示的是,只要实现了Human接口的类都可以作为参数,泛型是JDK 1.5中的一个非常重要的新特性,它减少了对象间的转换,约束其输入参数类型,对Collection集合下的实现类都可以定义泛型。有关泛型的详细知识,请参考相关的Java语法文档。
    目前女娲只有一个八卦炉,其实现生产人类的方法,如代码清单8-6所示。

    代码清单8-6 人类创建工厂

    public class HumanFactory extends AbstractHumanFactory{
    public<T extends Human>T createHuman(Class<T>c){
    //定义一个生产的人种
    Human human=null;
    try{
    //产生一个人种
    human=(Human)Class.forName(c.getName()).newInstance();
    }catch(Exception e){
    System.out.println("人种生成错误!");
    }
    return(T)human;
    }
    }
    

    人种有了,八卦炉也有了,剩下的工作就是女娲采集黄土,然后命令八卦炉开始生产,其过程如代码清单8-7所示。

    代码清单8-7 女娲类

    public class NvWa{
    public static void main(String[]args){
    //声明阴阳八卦炉
    AbstractHumanFactory YinYangLu=new HumanFactory();
    //女娲第一次造人,火候不足,于是白人产生了
    System.out.println("--造出的第一批人是白色人种--");
    Human whiteHuman=YinYangLu.createHuman(WhiteHuman.class);
    whiteHuman.getColor();
    whiteHuman.talk();
    //女娲第二次造人,火候过足,于是黑人产生了
    
    System.out.println("\n--造出的第二批人是黑色人种--");
    Human blackHuman=YinYangLu.createHuman(BlackHuman.class);
    blackHuman.getColor();
    blackHuman.talk();
    //第三次造人,火候刚刚好,于是黄色人种产生了
    System.out.println("\n--造出的第三批人是黄色人种--");
    Human yellowHuman=YinYangLu.createHuman(YellowHuman.class);
    yellowHuman.getColor();
    yellowHuman.talk();
    }
    }
    

    人种有了,八卦炉有了,负责生产的女娲也有了,激动人心的时刻到来了,我们运行一下,结果如下所示。
    --造出的第一批人是白色人种--

    白色人种的皮肤颜色是白色的!

    白色人种会说话,一般都是但是单字节。

    --造出的第二批人是黑色人种--

    黑色人种的皮肤颜色是黑色的!

    黑人会说话,一般人听不懂。

    --造出的第三批人是黄色人种--

    黄色人种的皮肤颜色是黄色的!

    黄色人种会说话,一般说的都是双字节。

    哇,人类的生产过程就展现出来了!这个世界就热闹起来了,黑人、白人、黄人都开始活动了,这也正是我们现在的真实世界。以上就是工厂方法模式

    工厂方法模式的优点

    首先,良好的封装性,代码结构清晰。一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不用知道创建对象的艰辛过程,降低模块间的耦合。
    其次,工厂方法模式的扩展性非常优秀。在增加产品类的情况下,只要适当地修改具体的工厂类或扩展一个工厂类,就可以完成“拥抱变化”。例如在我们的例子中,需要增加一个棕色人种,则只需要增加一个BrownHuman类,工厂类不用任何修改就可完成系统扩展。

    再次,屏蔽产品类。这一特点非常重要,产品类的实现如何变化,调用者都不需要关心,它只需要关心产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化。因为产品类的实例化工作是由工厂类负责的,一个产品对象具体由哪一个产品生成是由工厂类决定的。在数据库开发中,大家应该能够深刻体会到工厂方法模式的好处:如果使用JDBC连接数据库,数据库从MySQL切换到Oracle,需要改动的地方就是切换一下驱动名称(前提条件是SQL语句是标准语句),其他的都不需要修改,这是工厂方法模式灵活性的一个直接案例。

    相关文章

      网友评论

          本文标题:OC中几种常用的设计模式以及工厂模式(Java版)

          本文链接:https://www.haomeiwen.com/subject/rcusmttx.html