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

news/2024/12/5 0:19:06/

【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;
@endNS_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;
@endNS_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;
@endNS_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;
@endNS_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; // 计算了工人的薪资
@endNS_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;
@endNS_ASSUME_NONNULL_END#import <Foundation/Foundation.h>
@class Employee;
NS_ASSUME_NONNULL_BEGIN@interface SalaryCaculator : NSObject
-(void)calculateSalary:(Employee*)employee;
@endNS_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; //定义一个协议方法,也就是一个公共的接口
@endNS_ASSUME_NONNULL_END#import <Foundation/Foundation.h>
#import "Shape.h"
NS_ASSUME_NONNULL_BEGIN@interface Squre : NSObject<Shape> //实现一个正方形类来实现对应的接口的内容
@property (nonatomic, assign) NSInteger length;
@endNS_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;
@endNS_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;
@endNS_ASSUME_NONNULL_END#import <Foundation/Foundation.h>
#import "LoggerProtocol.h"
NS_ASSUME_NONNULL_BEGIN@interface Logger : NSObject<LoggerProtocol> //这里是我们的一个具体的底层实现端
@endNS_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; //高层调用这个接口
@endNS_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;
@endNS_ASSUME_NONNULL_END#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@protocol Scanabel <NSObject> //设置扫描的接口
-(void) scanDoucment;
@endNS_ASSUME_NONNULL_END#import <Foundation/Foundation.h>
#import "Printable.h"
NS_ASSUME_NONNULL_BEGIN@interface Printer : NSObject <Printable> //普通打印机的一个实现,如果是多功能则多遵循一个协议就可以了,@endNS_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;
@endNS_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;
@endNS_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;
@endNS_ASSUME_NONNULL_END#import "User.h"
#import "ShopCart.h"
@implementation User
- (void)addToCart:(Product *)product {[self.shopCart addProduct:product]; // 与购物车发生直接关系
}
@end

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

小结

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

参考博客:

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

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


http://www.ppmy.cn/news/1552419.html

相关文章

【Oracle11g SQL详解】INSERT INTO 的用法及插入数据注意事项

INSERT INTO 的用法及插入数据注意事项 在 Oracle 11g 中&#xff0c;INSERT INTO 语句用于向表中插入数据&#xff0c;是数据写入操作中最常用的 SQL 语句之一。本文将详细介绍 INSERT INTO 的基本语法、常见场景、注意事项及常见错误处理。 一、INSERT INTO 的基本语法 INS…

详解Vue设计模式

详解 vue 设计模式 ​ Vue.js 作为一个流行的前端框架&#xff0c;拥有许多设计模式&#xff0c;这些设计模式帮助开发者更好地组织和管理代码&#xff0c;提升代码的可维护性、可扩展性和可读性。Vue 设计模式主要体现在以下几个方面&#xff1a; 1. 组件化设计模式 (Compon…

MATLAB不动点迭代法求单变量非线性方程的根程序加实例

不动点迭代法用于单变量线性方程近似根&#xff0c;首先确定一个方程根附近的近似初始值&#xff0c;采用逐次逼近的方法&#xff0c;使用迭代公式不断地更新这个初始值&#xff0c;使这个初始值不断趋近于准确值。 1.不动点迭代法自定义函数 fixed_point.m是一个MATLAB函数&a…

redis都有哪些用法

1. 缓存&#xff08;Caching&#xff09;&#xff1a; • Redis常被用作缓存层&#xff0c;存储那些频繁访问但更新不频繁的数据&#xff0c;以减少数据库的访问压力&#xff0c;提高数据读取速度。 • LRU&#xff08;Least Recently Used&#xff09;淘汰策略&#xff1a;Red…

【SpringBoot】整合篇

1、log4j2 第一步&#xff0c;导入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions><!-- 去掉springboot默认配置 --> <exclusion> <…

113. UE5 GAS RPG 实现传送点切换地图

接着我们优化体验相关&#xff0c;首先实现检查点可以在选中点击后&#xff0c;可以自动移动到目标检查点。然后增加一个可以提示玩家已经探索过相关区域的光柱&#xff0c;最后增加一个传送点&#xff0c;可以通过传送点传送到其它关卡。 实现点击自动移动到检查点 先实现点…

milvus 通俗易懂原理

向量值如何生成的 Milvus 是一个开源的向量数据库&#xff0c;专门用于处理高维向量的存储、搜索和分析。向量值本身通常来自于某些机器学习或深度学习模型的输出&#xff0c;尤其是在自然语言处理&#xff08;NLP&#xff09;、计算机视觉&#xff08;CV&#xff09;、推荐系…

数据结构有哪些?

常见的数据结构包括&#xff1a;数组、链表、栈、队列、树、堆、散列表&#xff08;哈希表&#xff09;等。‌ ‌数组‌&#xff1a;按照索引查询元素的速度很快&#xff0c;但大小固定&#xff0c;添加和删除元素需要移动其他元素&#xff0c;且只能存储一种类型的数据。‌链…