课程笔记:设计模式相关面试问题

设计模式

六大设计原则(知识点盲区)
  1. 单一职责原则
    一个类只负责一件事
    例如:UIView和CALayer的关系,UIView只负责事件传递、事件响应,而CALayer专门负责动画以及视图的展示和显示

  2. 开闭原则
    对修改关闭,对扩展开放
    例如:定义好一个类,尽量减少对它的修改,同时把扩展打开

  3. 接口隔离原则
    使用多个专门的协议,而不是一个庞大臃肿的协议
    协议中的方法应当尽量少
    比如:UITableView,提供了UITableViewDataSource和UITableViewDelegate两个协议,UITableViewDelegate专门处理代理事件,UITableViewDataSource用来获取UITableView的数据源

  4. 依赖倒置原则
    抽象不应该依赖于具体实现,具体实现可以依赖于抽象,我们在定义一些关于数据访问,比如增删改查接口的时候所有上层的业务调用都应该依赖于你所定义的抽象的接口,而至于接口内部具体实现用数据库也好,还是用文件plist对上层业务来说应该是感知不到的,这就体现出了通过抽象接口来去反转依赖,那么对上层业务来说它只依赖于我们做好的接口定义,比如增删改查,而对于里面内部的具体是采用哪种数据存储方案,上层是不关注的,也没必要把一些具体的数据存储方案的变量也好、参数也好暴露给使用方,这就体现到了具体实现可以依赖于抽象,而抽象不应该依赖于具体实现

  5. 里式替换原则
    KVO机制就运用到了里式替换原则,父类可以被子类无缝替换,且原有功能不受任何影响
    例如,在KVO监听中,系统重写了setter方法,然后将isa指针指向其子类,悄无声息的子类替换掉父类。

  6. 迪米特法则
    一个对象应当对其他对象有尽可能少的了解
    从而达到高内聚、低耦合

责任链设计模式
image.png
image.png

一个类A,其有一个成员变量B,该成员变量的类型跟A类型一样,从而构成责任链

image.png

责任链模式的应用:响应链模式

  • (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
  • (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;

注意:

责任链模式与链式编程的概念区别

问:什么是链式(函数式)编程?

通过高阶函数以点为连接将多个函数连接在一起完成参数传递和复杂的操作!
例如在Masonry中的这样的代码
make.right.equalTo(self.right).insets(kPadding);

桥接设计模式

问:什么是桥接?

或者,直接问一道需要用桥接模式解决问题的业务题

image.png

一个列表,有三套并存的数据,使用哪一套,后台控制
也可能不是三套,而是30套,如何解决?

解决方法:建立一个抽象父类,所有的网络数据处理继承抽象父类,从而调用哪一套直接调用。

image.png

使用上面方法,建立两个抽象类,且通过抽象类B是抽象类A的一个成员变量的方法,将两个抽象类建立连接。
从而可以使得两边的子类A和子类B相互关联。
上述方法,有9种组合

适配器设计模式
一个现有类需要使用变化的问题
假如有一个类A,是在N年前写的,很多地方都要用,但成熟且很久没人去修改里面的代码。现有一个新功能,需要修改原有类A,怎么做?
直接修改类A,那么其他用到类A的地方有可能出错,因此,我们不直接修改类A,才有适配器模式进行修改。

适配器有两种方法:
对象适配器
类适配器

下面叙述对象适配器

image.png

被适配对象就是原有对象A
适配对象就是需要新建的对象,假如是对象B
那么,新建对象B里有一个成员变量,是被适配对象A,从而将两者建立联系。
在新建对象B里:

image.png

单例模式

import <Foundation/Foundation.h>

@interface Mooc : NSObject

  • (id)sharedInstance;
    @end

import "Mooc.h"

@implementation Mooc

  • (id)sharedInstance
    {
    // 静态局部变量
    static Mooc *instance = nil;

    // 通过dispatch_once方式 确保instance在多线程环境下只被创建一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    // 创建实例
    instance = [[super allocWithZone:NULL] init];
    });
    return instance;
    }

// 重写方法【必不可少】

  • (id)allocWithZone:(struct _NSZone *)zone{
    return [self sharedInstance];
    }

// 重写方法【必不可少】

  • (id)copyWithZone:(nullable NSZone *)zone{
    return self;
    }
    @end

需要注意的地方:
+sharedInstance方法中,
instance = [[super allocWithZone:NULL] init];
而不是
instance = [[self allocWithZone:NULL] init];

这是因为,如果用户第一次创建单例是通过[Mooc allocWithZone:nil];创建的,那么会是一个死循环

image.png


instance = [[super alloc] init];也不可以

image.png

结果:

image.png

源码分析:

  • (id) alloc
    {
    return [self allocWithZone: NSDefaultMallocZone()];
    }
    1
    2
    3
    4
    可以看出,alloc内部调用了 allocWithZone: NSDefaultMallocZone()

使用Clang命令进行编译:
instance = [[super alloc] init];
编译后:

(*instance) = ((Mooc ()(id, SEL))(void *)objc_msgSend)((id)((Mooc ()(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)(&(__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getMetaClass(“Mooc”))}, sel_registerName(“alloc”)), sel_registerName(“init”));

objc_msgSend(objc_msgSendSuper({self, [Mooc super]}, @selector(alloc)), @selector(init));

instance = [[super allocWithZone:NULL] init];
编译后:

(*instance) = ((Mooc ()(id, SEL))(void *)objc_msgSend)((id)((Mooc ()(__rw_objc_super *, SEL, struct _NSZone *))(void *)objc_msgSendSuper)(&(__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getMetaClass(“Mooc”))}, sel_registerName(“allocWithZone:”), (struct _NSZone *)__null), sel_registerName(“init”));

objc_msgSend(objc_msgSendSuper({self, [Mooc super]}, @selector(allocWithZone)), @selector(init));

感觉,看不出什么

继续分析:

  • (id)sharedInstance
    {
    // 静态局部变量
    static Mooc *instance = nil;

    // 通过dispatch_once方式 确保instance在多线程环境下只被创建一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    // 创建实例
    NSLog(@"--1--");
    instance = [[super allocWithZone:NULL] init];
    });
    return instance;
    }

// 重写方法【必不可少】

  • (id)allocWithZone:(struct _NSZone *)zone{
    NSLog(@"--2--");
    return [self sharedInstance];
    }

调用:
Mooc *mooc1 = [Mooc allocWithZone:nil];
NSLog(@"%p", mooc1);

打印结果:
--2--
--1--

而使用

instance = [[super alloc] init];
打印结果:
--2--
--1--
--2--

崩溃

首先,我们要知道:

  • (id)allocWithZone:(struct _NSZone *)zone
    {
    return [self sharedInstance];
    }

是重写的自己的allocWithZone:
并没有重写父类的allocWithZone:方法。

为了观察Mooc的父类allocWithZone和alloc的重写情况,我们给Mooc创建了一个父类MoocFather

import "MoocFather.h"

@implementation MoocFather

  • (instancetype)alloc
    {
    NSLog(@"%s", func);
    return [super alloc];
    }

  • (id)allocWithZone:(struct _NSZone *)zone{
    NSLog(@"%s", func);
    return [super allocWithZone:zone];
    }
    @end

在使用[[super allocWithZone:NULL] init];的时候

image.png

有进入其父类的allocWithZone:方法,调用成功

在使用[[super allocWithZone:NULL] init];的时候,直接调用成功,没有再进入allocWithZone:方法

而在使用[[super alloc] init];的时候

image.png

[super alloc]进入其父类的+alloc方法,再进去是NSObject的+alloc方法。
消息接收者是Mooc,也就是最后的调用者还是Mooc。也就是最后调用的是[Mooc alloc];
而[Mooc alloc];调用的是[Mooc allocWithZone:NSDefaultMallocZone()];
由于我们重写了allocWithZone:,然后进入的是[self sharedInstance];从而造成了循环引用。

也就是:
在使用[[super alloc] init];的时候,再次进入allocWithZone:方法,从而再次调用sharedInstance方法,又进入allocWithZone:调用instance = [[super alloc] init];,从而形成循环。

网上还有其他单例的写法,我们调几个进行验证:
方法二:

import "Singleton1.h"

static Singleton1 *_instance = nil;

@implementation Singleton1

  • (id)sharedInstance
    {
    return [[self alloc] init];
    或者
    return [[self allocWithZone:NULL] init];
    或者
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    _instance = [[self alloc] init];
    });
    return _instance;
    }

  • (instancetype)allocWithZone:(struct _NSZone *)zone
    {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    _instance = [super allocWithZone:zone];
    });
    return _instance;
    }

  • (id)copyWithZone:(nullable NSZone *)zone{
    return _instance;
    }
    @end

不同点1:
方法一将static Mooc *instance = nil;写在+ (id)sharedInstance方法里面,instance是静态局部变量。
方法二将static Singleton1 *_instance = nil;写在全局,_instance是静态全局变量。

不同点2:

方法一:

  • (id)copyWithZone:(nullable NSZone *)zone{
    return self;
    }

方法二:

  • (id)copyWithZone:(nullable NSZone *)zone{
    return _instance;
    }

由于copyWithZone:是对象方法,首先,要创建对象才能调用copyWithZone:
创建对象的方法有两种:
sharedInstance和alloc
而alloc内部调用的是allocWithZone:
所以,在创建对象的时候,不管使用的是sharedInstance或者allocWithZone:,其实已经是单例对象了。

因此,方法copyWithZone:中,return self,其实就是谁调用这个方法,return谁
而调用这个方法的创建对象无法是sharedInstance或者allocWithZone:
这两个方法的返回值都是return _instance;,也就是[_instance copyWithZone:]
在copyWithZone:中,self 就是 _instance
也就是,return _instance 和 return self一样的。

self,谁调用就是谁
在类方法里调用self,就是类对象
在对象方法里调用self,就是对象。

同理:

  • (id)copyWithZone:(nullable NSZone *)zone{
    return [Singleton1 shareInstance];
    }
    1
    2
    3
    这种写法,与前两种写法等价。

方法二中

  • (id)sharedInstance
    {
    return [[self alloc] init];
    或者
    return [[self allocWithZone:NULL] init];
    或者
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    _instance = [[self alloc] init];
    });
    return _instance;
    }

三种方法都可以,调用sharedInstance等于调用到了alloc,alloc内部调用了allocWithZone:,在allocWithZone:里面进行了仅一次创建。

总结:

  • (id)copyWithZone:(nullable NSZone *)zone{
    三种写法都可以
    return self;
    return [Singleton1 shareInstance];
    return _instance;
    }
  • (id)sharedInstance
    {
    }

  • (instancetype)allocWithZone:(struct _NSZone *)zone
    {
    }

两个方法,有一个进行一次dispatch_once_t即可,另外一个可以调取另外一个dispatch_once_t后的方法。

需要注意的是,一个是self,一个super。不要产生循环引用问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容