【iOS】—— 工厂设计模式

news2024/11/14 15:00:36

工厂设计模式

文章目录

  • 工厂设计模式
    • 设计模式概念
    • 设计模式七大准则
      • 开闭原则
      • 单⼀职责原则
      • 里氏替换原则
      • 依赖倒转原则
      • 接口隔离原则
      • 迪米特法则
      • 合成复用原则
    • 类族模式
    • 简单工厂模式
      • 优点
      • 缺点
      • 主要作用
      • 示例
        • 文件分类
        • 实现效果:
    • 工厂方法模式
      • 优点
      • 缺点
      • 主要作用:
      • 示例:
        • 文件分类
        • 实现效果:
    • 抽象工厂方法
      • 缺点
      • 主要作用:
      • 示例:
        • 文件分类
        • 实现效果
    • git链接

设计模式概念

所谓设计模式(Design pattern) 是解决软件开发某些特定问题而提出的一些解决方案也可以理解成解决问题的一些思路。通过设计模式可以帮助我们增强代码的可重用性、可扩充性、 可维护性、灵活性好。我们使用设计模式最终的目的是实现代码的 高内聚 和 低耦合。可以这么说,计算机中设计模式指的是一套广为人知、被反复使用、经过分类编目的代码设计经验。使用设计模式是为了可重用代码,让代码更容易被他人理解,最重要的是保证代码可靠性。

设计模式七大准则

开闭原则

开闭原则的核心是:对扩展开放,对修改关闭。在程序需要进⾏拓展的时候,不能去修改原有的代码,⽽是要扩展原有代码,实现⼀个热插拔的效果。所以⼀句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使⽤接⼝和抽象类等,大部分具体设计模式中反复应用这一原则。

单⼀职责原则

一个类只做一件事。每个类应该实现单⼀的职责,如若不然,就应该把类拆分。
比如:UIView负责事件的传递、响应,CALayer负责视图的显示、动画,他们各自都有自己的单一职责。

里氏替换原则

里氏替换原则的主要内容:任何基类可以出现的地方,子类⼀定可以出现。该原则是继承复⽤的基⽯,只有当衍⽣类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。原则上,⼦类对父类的方法尽量不要重写和重载,因为⽗类代表了定义好的结构,通过这个规范的接⼝与外界交互。除非万不得已,⼦类不应该随便破坏它。

依赖倒转原则

面向接口编程,依赖于抽象而不依赖于具体。高层模块不应该依赖底层模块,二者都应该依赖其抽象;我们可以依赖抽象类也可以依赖接口,但是不要依赖具体的子类或者实现类。依赖倒转原则是开闭原则的基础。

接口隔离原则

每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使⽤多个隔离的接口,⽐使用单个接口(多个接口⽅法集合到⼀个的接口)要好。

迪米特法则

迪米特法则的核心为:⼀个类对自己依赖的类知道的越少越好。也就是说⽆论被依赖的类多么复杂,都应该将逻辑封装在⽅法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
最少知道原则的另⼀个表达⽅式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现成员变量、⽅法参数、⽅法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。

合成复用原则

合成复用原则是尽量⾸先使用合成/聚合的⽅式,而不是使用继承。此原则和里氏替换原则氏相辅相成的,两者都是详细实现"开-闭"原则的规范。我们先看什么是合成和聚合:
合成
合成是指一个总体对依托他而存在的关系,如一个人对他的房子和家具。该关系依赖性不强,比如人没了,这个关系就自然消失了。
聚合
聚合是比合成关系更强的一种依赖关系,如有一台汽车,汽车对引擎、轮胎的关系就是聚合关系。这些关系就是带有聚合性质的。车没了,该车的引擎和轮胎自然也没了。在我们的设计中,这种关系不应该频繁出现,因为这样会增大设计的耦合度。

明确了合成和聚合关系,再来理解合成复用原则应该就清楚了:我们要尽量找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

类族模式

在之前学习小蓝书的时候我们提到过类族模式,在学习工厂模式之前我们先来看看类族模式,具体关于类族模式可以看看我之前博客:
【Effective Objective - C】—— 读书笔记(二)

其中类族模式最重要的一点作用是:把实现细节隐藏在一套简单的公共接口后面,在学习完工厂模式之后,我们会发现,工厂模式和类族模式在目的上和实现细节上有很大的相同点。

简单工厂模式

在这里插入图片描述

专门定义一个类(工厂类)来负责创建其他类的实例。可以根据创建方法的参数来返回不同类的实例,被创建的实例通常具有共同的父类。(总结来说就是把一大堆if-else判断由业务层放到工厂类里面)。

优点

  • 根据约定好的参数就可以获取所需要的对象,而不需要知道其创建的细节。减少了系统的耦合度。
  • 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,减少开发者的记忆成本。

缺点

  • 如果业务上添加新产品的话,就需要修改工厂类原有的判断逻辑,这其实是违背了开闭原则的。
  • 在产品类型较多时,有可能造成工厂逻辑过于复杂。所以简单工厂模式比较适合产品种类比较少而且增多的概率很低的情况。

主要作用

通过引入工厂类,使对象的创建和使用分离了。这样的好处是它们可以独立的变化,易维护和扩展。
客户端依赖抽象基类(接口),而不是具体的类,降低了耦合度。

  • 有一组相似的对象,需要集中统一创建时。
  • 创建对象的过程较为复杂时。
  • 对象很多,并且有扩展需求时。
  • 客户端不需要知道创建对象的过程时。
  • 客户端使用的对象存在变动的可能,或者根本不知道使用哪一个具体对象时。

示例

工厂类中核心代码
通过传递进来的字符串来确定生成的类:

//  PhoneFactory.m

NSArray *array = @[@"vivo", @"oppo", @"xiaomi", @"apple"];
+ (id)createPhone:(NSString*)phoneType {
    switch ([array indexOfObject:phoneType]) {
        case 0:
            return [[Vivo alloc] init];
            break;
        case 1:
            return [[Oppo alloc] init];
            break;
        case 2:
            return [[Xiaomi alloc] init];
            break;
        case 3:
            return [[Apple alloc] init];
            break;
        default:
            break;
    }
    return nil;
}

其中通过工厂类返回的各种类(各种型号手机)必须遵守以下协议:

@protocol PhoneDelegate <NSObject>

- (void)phoneWays;

@end

在各类中实现此方法

#import "Vivo.h"

@implementation Vivo
- (void)phoneWays {
    NSLog(@"vivo");
}
@end

文件分类

在这里插入图片描述

实现效果:

请添加图片描述
在这里插入图片描述

分析一下,简单工厂方法和类族模式主要区别就是,类族模式实现各子类方法通过继承去重写父类方法,而简单工厂方法中生成的各类和工厂类并不是父子类关系,通过协议来完成各方法。

工厂方法模式

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使得一个类的实例化延迟到了子类。就像Cocoa Touch中的NSNumber的numberWithBool和numberWithInt方法,他们传入不同类型的参数,获得NSNumber实例。
在这里插入图片描述

优点

  • 和直接创建具体对象相比,使用工厂方法创建对象算是最佳的做法。
  • 根据所需产品找对应工厂进行生产,不关心产品细节,也不需要知道产品类的类名。
  • 当系统中加入新产品时,不需要修改抽象工厂和抽象产品提供的接口,也无须修改客户端和其他的具体工厂和具体产品,而只要添加一个具体工厂和与其对应的具体产品就可以了,符合了“开-闭”原则。

缺点

当系统中加入新产品时,除了需要提供新的产品类之外,还要提供与其对应的具体工厂类。因此系统中类的个数将成对增加,增加了系统的复杂度。

主要作用:

  • 编译时无法准确预期需要创建对象的类。
  • 类想要其子类决定在运行时创建什么类型的实例。
  • 类有若干辅助类为其子类,而你想将返回哪个子类这种信息局部化。

示例:

这个较之前的相比,工厂类中并没有类方法,只有一些方法,简单工厂方法是通过协议的方法去令其他类完成方法,而工厂方法模式是通过继承,令其他类继承工厂类,去重写父类的这几个方法,来看看代码:

工厂类:

//  PhoneCenter.m
@implementation PhoneCenter
- (void)beginProductionPhone {
    NSLog(@"begin");
}
- (void)succeedProductionPhone {
    NSLog(@"succeed");
}
@end

子类:

@interface Vivo : PhoneCenter

@end

@implementation Vivo
- (void)beginProductionPhone {
    NSLog(@"begin vivo");
}
- (void)succeedProductionPhone {
    NSLog(@"succeed vivo");
}
@end

在viewController中初始化时以父类编译,子类运行

        PhoneCenter *a = [[Vivo alloc] init];
        [a beginProductionPhone];
        [a succeedProductionPhone];

文件分类

在这里插入图片描述

实现效果:

请添加图片描述
在这里插入图片描述

抽象工厂方法

工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个抽象产品类。抽象工厂模式里每个工厂都会生产多种产品,但不同工厂生产的产品属于不同的系列。抽象工厂模式可以用来解决多产品族的问题。请添加图片描述

缺点

  • 增加新的产品种类困难,它需要修改抽象工厂的接口。
  • 代码结构比较复杂。

主要作用:

  • 类想让其子类决定在运行时创建什么,无法在编译时准确确定
  • 类有若干个辅助类为其子类,而你想将返回某个子类这一信息局部化

示例:

再下面例子中,我们先来看看文件分类:

文件分类

在这里插入图片描述

这次的分类就较之前复杂了很多,我们来一步步看:
首先Manager相当于最大的工厂类,通过这个函数来确定是哪个工厂,apple厂还是google厂:

//  FactoryManager.m

+ (BaseFactory *)factoryWithType:(KFactoryType)factoryType {
    if (factoryType == KApple) {
        return [[AppleFactory alloc] init];
    } else if (factoryType == KGoogle) {
        return [[GoogleFactory alloc] init];;
    }
    return nil;
}

接下来到了工厂这步:
首先有一个作为两家工厂的父类,两家工厂继承于此类,并重写此基础类的各方法,以此来展示不同的效果:

//  BaseFactory.h
@interface BaseFactory : NSObject
- (BasePhone*)createPhone;
- (BaseWatch*)createWatch;
@end

//  BaseFactory.m
@implementation BaseFactory
- (BasePhone*)createPhone {
    return nil;
}
- (BaseWatch*)createWatch {
    return nil;
}
@end
//  AppleFactory.h

@interface AppleFactory : BaseFactory

@end



//  AppleFactory.m

@implementation AppleFactory
- (BasePhone *)createPhone {
    return [[ApplePhone alloc] init];
}

- (BaseWatch *)createWatch {
    return [[AppleWatch alloc] init];
}

在下面的一步我们让apple和google厂分别可以生产手机和手表两种产品,这时候和上一步一样,有一个基础手机类和基础手表类:

//  BasePhone.h

@interface BasePhone : NSObject
- (void)phoneCell;
@end


//  BasePhone.m
@implementation BasePhone
- (void)phoneCell {
    NSLog(@"This is a Phone");
}
@end

它的子类,除了继承并重写父类的方法,还添加了自己的方法:

//  ApplePhone.h

@interface ApplePhone : BasePhone
- (void)applePhoneWays;
@end


//  ApplePhone.m
@implementation ApplePhone
- (void)phoneCell {
    NSLog(@"This an apple phone");
}

- (void)applePhoneWays {
    NSLog(@"We can eat apple (phone)");
}
@end

其他类也和此相似
在ViewController中初始化,并执行各个方法:

    GooglePhone *googlePhone = (GooglePhone *)[googleFactory createPhone];
    //执行方法(1)
    [googlePhone phoneCell];
    [googlePhone googlePhoneWays];
    //确定商品(2)
    GoogleWatch *googleWatch = (GoogleWatch *)[googleFactory createWatch];
    //执行方法(2)
    [googleWatch watchCell];
    [googleWatch googleWatchWays];
    
    NSLog(@"-------------------------------------");
    
    //确定工厂
    BaseFactory *appleFactory = [FactoryManager factoryWithType:KApple];
    //确定商品(1)
    ApplePhone *applePhone = (ApplePhone *)[appleFactory createPhone];
    //执行方法(1)
    [applePhone phoneCell];
    [applePhone applePhoneWays];
    //确定商品(2)
    AppleWatch *appleWatch = (AppleWatch *)[appleFactory createWatch];
    //执行方法(2)
    [appleWatch watchCell];
    [appleWatch appleWatchWays];

实现效果

在这里插入图片描述

git链接

类族模式
工厂模式

这一年最后一篇博客了,希望在下一年里继续努力,早日实现梦想。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/174146.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ceres 目标函数(pose_graph_3d使用之)构建学习笔记

问题说明 ceres-solver库是google的非线性优化库&#xff0c;可以对slam问题&#xff0c;机器人位姿进行优化&#xff0c;使其建图的效果得到改善。pose_graph_3d是官方给出的二维平面上机器人位姿优化问题&#xff0c;需要读取一个g2o文件&#xff0c;运行程序后返回一个pose…

Android 课设之个人音乐播放器

第一章 绪论1.1选题背景由于时代快速发展&#xff0c;各种各样的音乐播放器层出不穷&#xff0c;此时需要一个可以根据个人爱好来播放的音乐播放器就尤为重要&#xff0c;因此我特意制作了一个根据自己喜好的音乐播放器&#xff0c;只需要把音乐文件放进制定的目录下即可。1.2开…

C++语法小笔记:内联函数,auto关键字,nullptr

目录 一.内联函数 1.回顾c语言中的“宏函数” 2.内联函数 3.内联函数的特性 二.C auto 关键字 1.auto的基本概念 2.auto使用的注意事项 3.auto不能使用的地方 三. C11中的 nullptr 一.内联函数 1.回顾c语言中的“宏函数” 先给出一段简单的代码&#xff1a; int Add(in…

plt设置柱状图标注

1、plt.text方法 在matplotlib 3.4.0之前的版本中&#xff0c;一般使用plt.text方法绘制数据标签。顾名思义&#xff0c;plt.text可以在图像的任何地方绘制指定的文本。基于此&#xff0c;我们只需要在相应数据点的坐标位置绘制相应的值&#xff0c;即可显示数据标签。 2、plt.…

react初始高阶组件

首先 我们要了解什么是高阶组件 第一 高阶组件必须是一个函数 第二 高阶组件接收一个参数&#xff0c;这个参数也必须是一个组件 第三 他的返回值 也是一个组件 至于高阶组件的作用 我们后续会讲解 本文只是带大家认识一下高阶组件 并手把手带大家创建一个 下面我们来创建一个…

微服务调用组件Feign学习笔记

目录 JAVA 项目中如何实现接口调用&#xff1f; 1. 什么是Feign 2. Spring Cloud Alibaba快速整合OpenFeign 3. Spring Cloud Feign的自定义配置及使用 4.自定义拦截器 5.超时时间配置 JAVA 项目中如何实现接口调用&#xff1f; 1&#xff09;Httpclient HttpClient 是 …

数据结构(模式匹配及相关算法)

目录 模式匹配 BF算法 算法实现 算法分析 KMP算法 问题的引入&#xff08;一&#xff09; 问题的引入&#xff08;二&#xff09; 问题的引入&#xff08;三&#xff09; 相关概念 计算失配函数的算法 算法思路 算法优点 模式匹配 函数int find(const sstring &am…

机器学习(三):人工智能主要分支

文章目录 人工智能主要分支 一、计算机视觉 二、语音识别 三、文本挖掘/分类 四、机器翻译 五、机器人 人工智能主要分支 通讯、感知与行动是现代人工智能的三个关键能力&#xff0c;在这里我们将根据这些能力/应用对这三个技术领域进行介绍&#xff1a; 计算机视觉(CV…

WiFi monitor模式的配置和运行检查(Ubuntu系统)

WiFi monitor模式的配置和运行检查1. WiFi monitor模式介绍2. WiFi monitor模式查看Step1&#xff1a;确保计算机上有安装硬件WiFi无线网卡Step2&#xff1a;安装必要的工具Step 3&#xff1a;iw list查看无线网卡是否支持monitor模式Step 4&#xff1a;配置WiFi monitor模式St…

有了独自开,我们离自己开发一套系统还会远吗

目录 一、结识独自开 二、独自开的介绍 三、独自开的需求 四、独自开注册流程 五、神仙公司独自开 一、结识独自开 算是机缘巧合&#xff0c;我被C站白佬拉入了他的聊天群&#xff0c;群内均是来自于CSDN的不同领域的优质作者&#xff0c;其中不乏相关领域工作多年的老工程…

“任性”华为 | 七十八岁老人的“四渡赤水”(二)

导读华为是一家“任性”的企业——因为任正非将自己的性格赋予了华为——在企业前进的每一个路口&#xff0c;都会看见这种性格的印记。2022年12月&#xff0c;当美国总统拜登出现在凤凰城出席台积电工厂迁机仪式上&#xff0c;苹果公司首席执行官库克等约900名政商界人士前往捧…

c#检测网络连接信息

用手机全屏看B站视频时可以看到右上角标识有WIFI&#xff0c;比较好奇如何检测当前网络连接是wifi还是数据网络什么的。于是百度相关信息&#xff0c;找到参考文献1-2&#xff0c;其中介绍采用Xamarin.Essentials检测网络连接性&#xff0c;其中的Connectivity类可用于监视设备…

【MATLAB】三维旋转的实现

1 三维旋转的表达方式 三维空间中常用的表示旋转的方式有&#xff1a; **[1]旋转矩阵(rotation matrix) [2]旋转向量(rotation vector&#xff09;/角轴&#xff08;轴角&#xff09;(axis angle) [3]欧拉角(euler angles) [4]四元数(quaternion)**主动旋转和被动旋转&#x…

现场工程师出手-PCAPHub与云IP实现异地LAN工业联测

在去年&#xff0c;因为众所周知的因素影响&#xff0c;项目的甲方主动提出延缓设备的交付。作为乙方&#xff0c;尽管项目延缓是甲方提出的&#xff0c;但依旧希望按期交付&#xff0c;这样才能回款&#xff0c;熬过一年。其实&#xff0c;2022年初&#xff0c;几类传感器、压…

Visual Studio 17.5 拼写检查器预览版现已推出,来说说你的看法吧

写在前面&#xff1a; Visual Studio17.5版本已添加拼写检查器功能&#xff0c;Visual Studio 中的许多功能旨在帮助你编写所需的代码。Visual Studio帮助你确保代码的编译&#xff0c;甚至可以帮助代码样式。现在它甚至可以确保您的拼写准确。Visual Studio 17.5 preview 3 引…

Linux常用命令——talk命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) talk 让用户和其他用户聊天 补充说明 talk命令是talk服务器的客户端工具&#xff0c;通过talk命令可以让用户和其他用户聊天。linux中talk命令参数程序的使用很简单&#xff0c;只要知道交谈对象的地址&#x…

plt自定义主要刻度值和次要刻度值

使用set_xticks方法和set_xticklabels方法即可 1、set_xticks方法 参数说明&#xff1a; set_xticks(self, ticks, minorFalse)参数1&#xff1a;ticks&#xff1a;指定刻度出现的位置参数2&#xff1a;minor&#xff1a;指定是否是次要刻度返回值1&#xff1a;包含XTick实例…

【数学建模】华为杯研究生数学建模备赛的一些建议

文章目录前言一、建模题目介绍1.1、题目数量1.2、题目种类1.3、题目难度1.4、题目选择二、笔者的备赛过程2.1.简单的题目&#xff08;本科比赛&#xff0c;学科大作业&#xff09;2.2.真题三、编程的备赛建议3.1.matlab和python的基础语法3.2.数据预处理3.3.常用的机器学习算法…

win10系统新增的几款非常有用的基础快捷键

win就是键盘的这个键winq或wins 打开搜索winw 打开白板、全屏截图wint 配合左右箭头←→&#xff0c;来回切换最小化窗口在任务栏的缩略图winu 设置显示器wini 打开设置winp 屏幕投影、扩展屏幕wina 查看最近消息通知winh 语音输入法wink 查找设备&#xff08;如&#xff1a;打…

线程池默认大小为CPU核数的2倍

1、前言 有位工作5年的小伙伴问我说&#xff0c;为什么Netty线程池默认大小为CPU核数的2倍&#xff0c;今天&#xff0c;我花2分钟时间给大家专门分享一下我对这个问题的理解。 另外&#xff0c;我花了1个多星期把往期的面试题解析配套文档准备好了&#xff0c;想获取的小伙伴…