【iOS】设计模式的六大原则

【iOS】设计模式的六大原则

文章目录

  • 【iOS】设计模式的六大原则
    • 前言
    • 开闭原则——OCP
    • 单一职能原则——SRP
    • 里氏替换原则——LSP
    • 依赖倒置原则——DLP
    • 接口隔离原则——ISP
    • 迪米特法则——LoD
    • 小结

前言

笔者这段时间看了一下有关于设计模式的七大原则,下面代码示例均为OC。

开闭原则——OCP

这原则要求很简单:

  • 该原则要求软件实体(类、模块、函数等)应该对扩展开放,对修改关闭

开闭原则的思想是:当新的需求出现时,应该尽可能地通过增加新的代码来满足这些需求,而不是直接修改现有代码。

正如iOS开发中的分类的思想一样。

下面给出一个例子:


//下面是一个有关于Car的例子
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Car : NSObject
@property (nonatomic, strong) NSString* brand;
@end

NS_ASSUME_NONNULL_END

#import "Car.h"

@implementation Car
- (void) startEngine { // 一个汽车启动的业务
    NSLog(@"the car go to ran");
}
@end

根据我们这个原则的思想来说的话,我们如果想添加一个新的功能,比方说自动泊车,我们应该是添加一个方法,而不是在原先的方法上进行一个修改:

错误案例:

//下面是一个有关于Car的例子
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Car : NSObject
@property (nonatomic, strong) NSString* brand;
@end

NS_ASSUME_NONNULL_END

#import "Car.h"

@implementation Car
- (void) startEngine { // 一个汽车启动的业务
    NSLog(@"the car go to ran");
  	//添加自动泊车
    NSLog(@"the car begin to park automatically");
}
@end

这样很显然不符合我们的开闭原则,下面是一个正确的案例:


#import "Car.h"

NS_ASSUME_NONNULL_BEGIN
//这里创建一个抽象类来实现,这个抽象类表示所有的一个汽车装饰器
@interface CarDecorator : Car
@property (nonatomic, strong) Car* car;
-(instancetype)initWithCar:(Car*)car;
@end

NS_ASSUME_NONNULL_END

#import "CarDecorator.h"
//这里是这个抽象类的一个具体实现
@implementation CarDecorator
- (instancetype)initWithCar:(Car *)car {
    if ([super init]) {
        self.car = car;
    }
    return self;
}
- (void)startEngine {
    [self.car startEngine];
}
@end
//下面是一个具体某一类别的实现:
#import "CarDecorator.h"

NS_ASSUME_NONNULL_BEGIN

@interface ElectricCarDecorator : CarDecorator
-(void)autoParking;
@end

NS_ASSUME_NONNULL_END
#import "ElectricCarDecorator.h"

@implementation ElectricCarDecorator
-(void)autoParking { // 创建一个新的方法用于实现我们的一个自动泊车
    NSLog(@"Auto Parking");
}
- (void)startEngine { //在这里重写父类的方法,让这个方法在实现我们想要的自动泊车功
    [super startEngine];
    [self autoParking];
}
@end

这上面的我们创建了一个新的抽象类来执行他新的一个方法,然后在子类中重写有关于父类的一个方法,既保证了代码不会被修改的同时,实现了一个新的功能,这就体现出了我们的一个开闭原则。

单一职能原则——SRP

该原则要求:

  • 不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,否则就应该把类拆分.

要点:

  1. 一个类只负责一个职能,类的设计应该避免包含过多的一个功能
  2. 高内聚,低耦合
  3. 避免滥用接口

下面是一个错误的示例:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Employee : NSObject // 这是一个工人的基本信息的内容
@property (nonatomic, strong) NSString* name;
@property (nonatomic, assign) NSInteger age;
-(void)calculateSalary:(Employee*)employee; // 计算了工人的薪资
@end

NS_ASSUME_NONNULL_END

#import "Employee.h"

@implementation Employee
-(void)calculateSalary:(Employee *)employee {
    NSLog(@"calcurlatSalry: name: %@, age : %ld", employee.name, employee.age);
}
@end

这个案例我们可以看出我们这里出现了这个类包含了多个职能,这很显然不符合我们的单一职能原则,我们这个工人的类别就应该仅仅包含一个工人的一个基本信息,而不应该涉及计算薪资的一个内容。

这里我们给出一个正确的例子:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Employee : NSObject
@property (nonatomic, strong) NSString* name;
@property (nonatomic, assign) NSInteger age;
@end

NS_ASSUME_NONNULL_END

#import <Foundation/Foundation.h>
@class Employee;
NS_ASSUME_NONNULL_BEGIN

@interface SalaryCaculator : NSObject
-(void)calculateSalary:(Employee*)employee;
@end
  
NS_ASSUME_NONNULL_END
#import "SalaryCaculator.h"
#import "Employee.h"
@implementation SalaryCaculator
-(void)calculateSalary:(Employee *)employee {
    NSLog(@"calcurlatSalry: name: %@, age : %ld", employee.name, employee.age);
}
@end

在这个案例中我们把两个内容分开了,创建了一个新的类来负责他对应的一个工作,从而保证了一个类只负责一个职能,符合我们的一个单一职能原则。

遵循单一职责原则是一个重要的设计原则,可以帮助我们写出更加模块化、可维护和可扩展的代码。

里氏替换原则——LSP

该原则要求:

子类对象可以替换父类对象出现在程序中,而不影响程序的正确性。

这里可能比较难以理解这项原则的一个作用,这里笔者借用学长的一段话来看一下:

  1. 里氏替换原则是实现开放封闭原则的重要方式之一。
  2. 它克服了继承中重写父类造成的可复用性变差的缺点。
  3. 它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。【设计模式】六大原则详解,每个原则提供代码示例

这里我们可以看出这项原则一个核心是为来保证类的一个扩展是不会给已经存在的系统引入新的错误,防止我们采用多态的时候出现一个代码上的问题。

下面给出一个经典的长方形不是正方形的一个例子:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@protocol Shape <NSObject>
- (NSInteger)calculateArea; //定义一个协议方法,也就是一个公共的接口
@end

NS_ASSUME_NONNULL_END

#import <Foundation/Foundation.h>
#import "Shape.h"
NS_ASSUME_NONNULL_BEGIN

@interface Squre : NSObject<Shape> //实现一个正方形类来实现对应的接口的内容
@property (nonatomic, assign) NSInteger length;
@end

NS_ASSUME_NONNULL_END

#import "Squre.h"

@implementation Squre
- (NSInteger)calculateArea {
    return self.length * self.length;
}
@end
  
#import "Shape.h"
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN

@interface Rectangle : NSObject<Shape> //实现长方形类来实现对应接口的内容
@property (nonatomic, assign) NSInteger width;
@property (nonatomic, assign) NSInteger height;
@end

NS_ASSUME_NONNULL_END

#import "Rectangle.h"

@implementation Rectangle
- (NSInteger)calculateArea {
    return self.width * self.height;
}
@end

这里我们把长方形和正方形分成两个不同的类别遵循不同的协议,这样才不会出现里氏替换原则中将正方形替换成长方形,然后设置长宽出现与预期的结果实际不符的一个情况。

依赖倒置原则——DLP

该原则要求:

  • 高层模块不应该依赖低层模块,二者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象

image-20241126194932262

在OC中我认为抽象类是协议,细节是一个类的实现方法,高层模块就是调用端,底层模块就是实现端。

也就是说,模块间依赖是通过抽象发生;实现类之间没有依赖关系,所有的依赖关系通过接口/抽象类产生。【设计模式】六大原则详解,每个原则提供代码示例

下面给出我认为的依赖倒置原则的实现:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@protocol LoggerProtocol <NSObject> // 一个抽象的接口
-(void) log:(NSString*)message;
@end

NS_ASSUME_NONNULL_END

#import <Foundation/Foundation.h>
#import "LoggerProtocol.h"
NS_ASSUME_NONNULL_BEGIN

@interface Logger : NSObject<LoggerProtocol> //这里是我们的一个具体的底层实现端
@end

NS_ASSUME_NONNULL_END

#import "Logger.h"

@implementation Logger
- (void)log:(NSString*)message { // 底层实现一个抽象
    NSLog(@"1234 %@", message);
}
@end

#import <Foundation/Foundation.h>
#import "Logger.h"
NS_ASSUME_NONNULL_BEGIN

@interface MyClass : NSObject // 这里是我们的高层调用端
@property (nonatomic, strong) id<LoggerProtocol> logger; //高层调用这个接口
@end

NS_ASSUME_NONNULL_END

//使用的时候我们通过下面的方法来实现:
//  MyClass.logger = [[Logger alloc] init];在这里进行一个依赖注入
//  [MyClass.logger log:@"123"];

接口隔离原则——ISP

该原则要求:

  • 一个类不应该强迫其它类依赖它们不需要使用的方法,也就是说,一个类对另一个类的依赖应该建立在最小的接口

也就说:一个类应该只提供其它类需要使用的方法,而不应该强迫其它类依赖于它们不需要使用的方法

这里我们举一个例子:假设我们设计一个多功能设备,里面可能包含了打印机和扫描仪,但是一般的设备可能不具备对应的一个扫描的一个功能,但是如果这个类遵循这个协议就会导致对应的一个问题,实现了一个他不具备的一个功能。

下面只介绍正确的案例的样式,设置小而专的接口

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@protocol Printable <NSObject> //设置打印的接口
- (void) printDoucment;
@end

NS_ASSUME_NONNULL_END

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@protocol Scanabel <NSObject> //设置扫描的接口
-(void) scanDoucment;
@end

NS_ASSUME_NONNULL_END
  
#import <Foundation/Foundation.h>
#import "Printable.h"
NS_ASSUME_NONNULL_BEGIN

@interface Printer : NSObject <Printable> //普通打印机的一个实现,如果是多功能则多遵循一个协议就可以了,

@end

NS_ASSUME_NONNULL_END

  
#import "Printer.h"

@implementation Printer
-(void)printDoucment {
    NSLog(!"common Printer");
}
@end

其核心思想在以下的内容:

如果一个接口过于臃肿,就需要将其拆分成多个小的接口,使得每个接口中只包含必要的方法。这样的好处是:

  1. 接口更加具有内聚性:每个接口只需要关注自己的功能,而不需要关注其他接口中的方法,因此能够使接口更加专注和具有内聚性。
  2. 接口之间的耦合度更低:每个接口只依赖于必要的方法,而不依赖于其他不必要的方法,因此能够使接口之间的耦合度更低。
  3. 代码的复用性更高:每个接口只包含必要的方法,因此能够使得代码的复用性更高,也能够提高代码的可读性和可维护性。24种设计模式代码实例学习(一)七大设计原则

迪米特法则——LoD

该原则要求:

  • 一个类对自己依赖的类知道的越少越好。无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。

最少知道原则的另一个表达方式是:只与直接的朋友通信。

类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。

这里我们以购物车为例子:一个人去超市买东西主要分成三个部分,一个是我们的用户,一个是购物车,一个是商品,这里面我们如果要符合这个设计原则的话,这里的人应该和购物车进行交互,然后购物车和商品进行一个交互,这样符合我们的迪米特法则。

下面给出一段代码示例:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Product : NSObject // 这个是商品
@property (nonatomic, assign) CGFloat price;
@property (nonatomic, strong) NSString* name;
@end

NS_ASSUME_NONNULL_END
  
#import <Foundation/Foundation.h>
@class Product;
NS_ASSUME_NONNULL_BEGIN

@interface ShopCart : NSObject // 这个是购物车
@property (nonatomic, strong) NSMutableArray<Product*>* products;
-(void) addProduct:(Product*)product;
@end

NS_ASSUME_NONNULL_END

#import "ShopCart.h"

@implementation ShopCart //与商品发生直接关系
- (void)addProduct:(Product *)product {
    [self.products addObject:product];
}
@end

#import <Foundation/Foundation.h>
@class ShopCart;
@class Product;
NS_ASSUME_NONNULL_BEGIN

@interface User : NSObject //用户
@property (nonatomic, strong) ShopCart* shopCart;
-(void)addToCart:(Product*)product;
@end

NS_ASSUME_NONNULL_END
  
#import "User.h"
#import "ShopCart.h"
@implementation User
- (void)addToCart:(Product *)product {
    [self.shopCart addProduct:product]; // 与购物车发生直接关系
}
@end

在上面的代码示例中,每个类都只和自己直接的朋友进行通信,遵循了最少知道原则。

小结

这里笔者简单介绍了一下六大设计模式的一个内容,笔者对这部分内容也是初次接触,如有纰漏或者还请不吝赐教,笔者会及时改正。

参考博客:

【设计模式】六大原则详解,每个原则提供代码示例

24种设计模式代码实例学习(一)七大设计原则

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一个实用的 Maven localRepository 工具

目录 1 现状2 当前解决3 更好的解决3.1 下载 Maven localRepository 工具包3.2 上传本地 localRepository 包3.3 清理 localRepository 中指定后缀的文件 1 现状 在使用 Maven 时&#xff0c;我们可能会经常与本地仓库和私服仓库打交道。 例如对于本地仓库&#xff0c;因为某…

【linux学习指南】详解Linux进程信号保存

文章目录 &#x1f4dd;保存信号&#x1f320; 信号其他相关常⻅概念&#x1f309;在内核中的表⽰ &#x1f320; sigset_t&#x1f320;信号集操作函数&#x1f309;sigprocmask&#x1f309;sigpending &#x1f6a9;总结 &#x1f4dd;保存信号 &#x1f320; 信号其他相关常…

【C++】 算术操作符与数据类型溢出详解

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;C 算术操作符详解基本算术操作符整数除法与取模行为类型转换在算术运算中的作用自增与自减操作符 &#x1f4af;数值溢出&#xff1a;当值超出类型范围时数据类型的取值范围…

【继承】—— 我与C++的不解之缘(十九)

前言&#xff1a; 面向对象编程语言的三大特性&#xff1a;封装、继承和多态 本篇博客来学习C中的继承&#xff0c;加油&#xff01; 一、什么是继承&#xff1f; ​ 继承(inheritance)机制是⾯向对象程序设计使代码可以复⽤的最重要的⼿段&#xff0c;它允许我们在保持原有类…

使用redis-plus-plus库连接redis

使用redis-plus-plus库连接redis 一、安装redis-plus-plus1.1安装hiredis1.2编译安装redis-plus-plus 二、redis的连接使用2.1创建redis对象2.2向redis中添加元素2.3判断元素是否存在2.4获取元素2.5设置获取过期时间2.6获取类型2.7 删除当前数据库 一、安装redis-plus-plus C …

JMeter 并发策略-针对准点秒杀场景的压测实现

一、场景的压测实现 1&#xff0c;创建线程组&#xff0c;10并发用户执行5次&#xff1b; 2&#xff0c;创建 Synchronizing Timer 元件,用于同步线程&#xff0c;设置同步元件 Synchronizing Timer 3&#xff0c;创建 http 请求4&#xff0c;创建 view results in table 元件…

记一次搞校园网的经历

接教室的校园网&#xff0c;到另一个屋子玩电脑&#xff0c;隔墙想放大一下AP的信号&#xff0c;发现死活不行 这是现状 由于校园网认证的存在&#xff0c;无法用桥接&#xff0c;桥接需要路由器有IP&#xff0c;而这个IP无法用未刷机的路由器来打开校园网页面认证 解决 将一…

打latex公式可以练到像手写一样快吗?

这里分享两个Python Latex工具latexify和handcalcs。 latexify生成LaTeX 数学公式 import math import latexify @latexify.with_latex #调用latexify的装饰器 def solve(a, b, c):return (-b + math.sqrt(b**2 - 4*a*c)) / (2*a)solve 更多例子.......

【Linux】常用命令二

1、cat 用于查看内容较少的纯文本文件。 参数-n可以显示行号。 2、more 用于查看内容较多的纯文本文件。 它会在最下面使用百分比的形式来提示你已经月读了多少内容&#xff0c;你可以使用空格键或回车键向下翻页。 3、head 用于查看纯文本文档的前N行。 4、tail 用于查看…

SolarCube: 高分辨率太阳辐照预测基准数据集

太阳能作为清洁能源在减缓气候变化中的作用日益凸显&#xff0c;其稳定的供应对电网管理至关重要。然而&#xff0c;太阳辐照受云层和天气变化的影响波动较大&#xff0c;给光伏电力的管理带来挑战&#xff0c;尤其是在调度、储能和备用系统管理方面。因此&#xff0c;精确的太…

第三方Express 路由和路由中间件

文章目录 1、Express 应用使用回调函数的参数&#xff1a; request 和 response 对象来处理请求和响应的数据。2、Express路由1.路由方法2.路由路径3.路由处理程序 3. 模块化路由4. Express中间件1.中间件简介2.中间件分类3.自定义中间件 1、Express 应用使用回调函数的参数&am…

nginx+php压测及报错优化

测试环境&#xff1a;虚拟机centos7&#xff0c;nginxphp 压测工具&#xff1a;Apipost 访问的php程序中添加sleep()增加程序执行时长&#xff0c;使用Apipost进行压测&#xff0c;根据服务器配置设置一个大概可能触发报错的并发和轮训次数&#xff0c;若无报错逐渐增加并发和…

探索Python词云库WordCloud的奥秘

文章目录 探索Python词云库WordCloud的奥秘1. 背景介绍&#xff1a;为何选择WordCloud&#xff1f;2. WordCloud库简介3. 安装WordCloud库4. 简单函数使用方法5. 应用场景示例6. 常见Bug及解决方案7. 总结 探索Python词云库WordCloud的奥秘 1. 背景介绍&#xff1a;为何选择Wo…

AOSP的同步问题

repo sync同步时提示出错: error: .repo/manifests/: contains uncommitted changesRepo command failed due to the following UpdateManifestError errors: contains uncommitted changes解决方法&#xff1a; 1、cd 进入.repo/manifests cd .repo/manifests2、执行如下三…

Shell脚本小练习

学习了这么长时间Shell脚本&#xff0c;总得来一次小小的练习吧&#xff0c;那么请看下文&#xff01; 1.用Shell写一个小计算器。 通过read命令获取用户输入的表达式&#xff0c;表达式的格式设定为操作数1 运算符 操作数2&#xff0c;例如53&#xff0c;然后利用设计的脚本…

k8s Init:ImagePullBackOff 的解决方法

kubectl describe po (pod名字) -n kube-system 可查看pod所在的节点信息 例如&#xff1a; kubectl describe po calico-node-2lcxx -n kube-system 执行拉取前先把用到的节点的源换了 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-EOF {"re…

Git 快速入门:全面了解与安装步骤

Git 快速入门&#xff1a;全面了解与安装步骤 一、关于Git 1.1 简介 Git 是一个开源的分布式版本控制系统&#xff0c;由 Linus Torvalds 于 2005 年创建&#xff0c;最初是为了更好地管理 Linux 内核开发而设计。 Git用于跟踪计算机文件的变化&#xff0c;特别是源代码文件…

springboot358智慧社区居家养老健康管理系统(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 智慧社区居家养老健康管理系统设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&…

探索 IntelliJ IDEA 中 Spring Boot 运行配置

前言 IntelliJ IDEA 作为一款功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;为 Spring Boot 应用提供了丰富的运行配置选项&#xff0c;定义了如何在 IntelliJ IDEA 中运行 Spring Boot 应用程序&#xff0c;当从主类文件运行应用程序时&#xff0c;IDE 将创建…

快速讲图片中的公式粘贴到word中

只是个人学习记录&#xff0c;不具备教学意义 目的&#xff1a; 比如下面图片中的公式 我们想把这个公式整到我们的word上&#xff0c;传统的方法是通过安装MathType插件&#xff0c;然后慢慢打。我说这样你就慢了。 解决办法&#xff1a; 前提准备&#xff1a; 我们需要…