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

设计模式

六大设计原则(知识点盲区)
  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

@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。不要产生循环引用问题。

你可能感兴趣的:(课程笔记:设计模式相关面试问题)