• 【iOS】—— 六大原则和工厂模式


    一、六大原则

    1.单一职责原则(Single Responsibility Principle)

    单一职责,从字面上就能看出,各自有各自的职责,可以这样理解:
    CALayer:动画和视图的显示。
    UIView:为CALaler提供显示的内容,负责事件传递、事件响应。

    他们每个类都有各自的职责,负责相应的功能。

    例如:在开发过程中,我们自定义对象,每个类/对象,只负责一个功能,不要一个对象处理很多逻辑。

    优点:

    • 类的复杂度降低、可读性提高、可维护性提高、扩展性提高、降低了变更引起的风险。

    需注意:

    • 单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可以度量的,因项目和环境而异。

    2.开闭原则(Open Close Principle)

    开:开放出接口供外部使用
    闭:不能修改,想要修改,采用继承的方式

    我们要尽量通过扩展软件实体来解决需求变化,而不是通过修改已有的代码来完成变化。

    例如:封装出一个公用类,提供可使用接口。如果不能满足需求,不要直接修改类代码,最好采用继承/组合的方式,来拓展以满足需求。就像我们OC中提供的基础类一样,想要对其功能进行扩展就只能使用分类扩展的方法。就是category(分类)。

    好处:

    • 保证了APP的稳定性及可拓展性。
    • 可以在不改动原有代码的前提下给程序扩展功能。增加了程序的可扩展性,同时也降低了程序的维护成本。

    3.里氏替换原则(Liskov Substitution Principle)

    在继承体系中,子类中可以增加自己特有的方法,也可以实现父类的抽象方法,但是不能重写父类的非抽象方法,否则该继承关系就不是一个正确的继承关系。

    注:可以拓展父类方法,不要复写父类方法。
    就是说,父类某些非抽象的,具体实现的方法,我们不能修改,就像父类中有一个只输出一句固定的消息的方法,你在子类中重写了这个方法,输出了另一个消息,那么这个方法在子类中的定义就和父类没有任何关系了,就不满足该原则了。

    例如:

    //父类
    - (void)carName {
        NSLog(@"五菱宏光");
    }
    
    //子类
    //正确的
    - (void)carName {
        NSLog(@"五菱宏光");
    }
    
    - (void)carColor {
        NSLog(@"白色的");
    }
    
    //错误的,他影响到了父类原有的功能,父类只是想使用这个输出车的名字而已,它却又添加了颜色
    - (void)carName {
        NSLog(@"白色的五菱宏光");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    好处:

    • 增强程序的健壮性,即使增加了子类,原有的类还可以继续运行。
    • 可以检验继承使用的正确性,约束继承在使用上的泛滥。

    需注意:

    • 如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系 采用依赖、聚合、组合等关系代替继承。

    4.接口隔离原则(Interface Segregation Principle)

    设计接口时,应当各个功能隔离开,应更具体,更详细。不要拥挤到一起,做很多用不到的功能。

    就是说对每个对应的功能我们都要书写其相应的接口,不要在同一个接口中实现该接口非必要的功能代码。像我们之前学习的使用分类创建一个类不同功能类型的接口、使用多个专门的协议、而不是一个庞大臃肿的协议(tableview的delegate和datasource类似这样)。

    例如:

    //错误的
    //行驶的时候没必要非要开空调
    - (void)useCar {
        NSLog(@"行驶");
        NSLog(@"开空调");
    }
    
    //正确的
    - (void)useCar {
        NSLog(@"行驶");
    }
    
    - (void)openTheAircondition {
        NSLog(@"开空调");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    好处:

    • 避免同一个接口里面包含不同类职责的方法,接口责任划分更加明确,符合高内聚低耦合的思想。

    需注意:

    • 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
    • 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
    • 为依赖接口的类定制服务。只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。

    5.依赖倒置原则(Dependence Inversion Principle)

    抽象不应该依赖于具体实现,实现应依赖于抽象。 如接口的封装,调用接口不关心内部的实现。

    我们应该在代码书写的时候将其抽象化,就像我们使用网络请求一样的,大体的网络请求结构是相同的,其中不同的只是具体的某个数值,那么我们就可以定义一个属性来获取不同的数值,进而做到不同的网络请求,这么我们就只需要每次该这个属性的值就行了。而不是对于每个不同的网络请求都有与之对应的方法,那么要定义的方法就太多了,也不符合该原则。

    例如:

    //错误的
    - (void)getPhotoNetFirst:(NSString *)photosType {
    	NSString *tempString = [[NSString alloc] initWithFormat:@"http://47.116.14.251:8888/info/First"];
    }
    
    - (void)getPhotoNetSecond:(NSString *)photosType {
    	NSString *tempString = [[NSString alloc] initWithFormat:@"http://47.116.14.251:8888/info/Second"];
    }
    
    //正确的
    - (void)getPhotoNet:(NSString *)photosType {
    	NSString *tempString = [[NSString alloc] initWithFormat:@"http://47.116.14.251:8888/info/%@", self.transPhotosType];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    好处:

    • 通过抽象来搭建框架,建立类和类的关联,以减少类间的耦合性。而且以抽象搭建的系统要比以具体实现搭建的系统更加稳定,扩展性更高,同时也便于维护。

    6.迪米特原则(Law Of Demeter)

    迪米特法则也叫做最少知道原则(Least Know Principle),一个对象应当对其他对象尽可能少的了解,实现高聚合、低耦合。对象之间互相不要知道太多具体实现,知道的越多耦合性越强,修改一个对象对另一个影响越大。

    类与类之间不应该知道对方太多的具体实现,况且有的类不一定会让你知道其实现,这时候我们就通过其已经给出的对象来进行判断就行了,就好比我们C++经常写代码的栈判空处理,我们只需要其是判空就行了,不用关心它是如何通过某些代码判断的。以及我们经常用的设计模式MVC,MVVM,MVP都是应用的迪米特法则。

    低耦合,高内聚是我们软件编程的总的原则。

    例如:
    5345345

    好处:

    • 实现迪米特法则可以良好地降低类与类之间的耦合,减少类与类之间的关联程度,让类与类之间的协作更加直接。

    概括

    一句话概括六大原则: 单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。

    总结

    对这六个原则的遵守并不是是和否的问题,而是多和少的问题,也就是说,我们一般不会说有没有遵守,而是说遵守程度的多少。任何事都是过犹不及,设计模式的六个设计原则也是一样,制定这六个原则的目的并不是要我们刻板的遵守他们,而需要根据实际情况灵活运用。对他们的遵守程度只要在一个合理的范围内,就算是良好的设计。我们用一幅图来说明一下。

    在这里插入345345描述
    图中的每一条维度各代表一项原则,我们依据对这项原则的遵守程度在维度上画一个点,则如果对这项原则遵守的合理的话,这个点应该落在红色的同心圆内部;如果遵守的差,点将会在小圆内部;如果过度遵守,点将会落在大圆外部。一个良好的设计体现在图中,应该是六个顶点都在同心圆中的六边形。
    5345345

    在上图中,设计1、设计2属于良好的设计,他们对六项原则的遵守程度都在合理的范围内;设计3、设计4设计虽然有些不足,但也基本可以接受;设计5则严重不足,对各项原则都没有很好的遵守;而设计6则遵守过渡了,设计5和设计6都是迫切需要重构的设计。

    二、工厂模式

    在基类中定义创建对象的一个接口,让子类决定实例化哪个类。工厂方法让一个类的实例化延迟到子类中进行。工厂方法要解决的问题是对象的创建时机,它提供了一种扩展的策略,很好地符合了开放封闭原则。工厂方法也叫做虚构造器(Virtual Constructor)

    1.简单工厂模式(Simple Factory Pattern)

    专门定义一个类(工厂类)来负责创建其他类的实例。可以根据创建方法的参数来返回不同类的实例,被创建的实例通常具有共同的父类。就是通过你传入的参数来判断具体要实现的实例。
    4234234

    通俗的讲: 简单工厂模式就是一个工厂可以生产多种产品,只需要用户说明自己想生产的产品工厂就可以生产对应的产品。

    优点:

    • 将对象的创建和对象本身业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。
    • 根据约定好的参数就可以获取所需要的对象,而不需要知道其创建的细节。减少了系统的耦合度。
    • 在调用工厂类的工厂方法时,由于工厂方法是静态方法,使用起来很方便,可通过类名直接调用,而且只需要传入一个简单的参数即可,修改参数时无须修改任何源代码
    • 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,减少开发者的记忆成本。

    缺点:

    • 简单工厂模式最大的问题在于工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则是相违背的。
    • 在产品类型较多时,有可能造成工厂逻辑过于复杂。所以简单工厂模式比较适合产品种类比较少而且增多的概率很低的情况。

    代码例子:

    设置两个button,控制不同的产品:

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        self.mouseButton = [UIButton buttonWithType:UIButtonTypeSystem];
        [self.mouseButton setTitle:@"Mouse" forState:UIControlStateNormal];
        [self.view addSubview:self.mouseButton];
        [self.mouseButton setFrame:CGRectMake(50, 200, 100, 100)];
        [self.mouseButton addTarget:self action:@selector(pressButton:) forControlEvents:UIControlEventTouchUpInside];
        
        self.keyboardButton = [UIButton buttonWithType:UIButtonTypeSystem];
        [self.keyboardButton setTitle:@"Keyboard" forState:UIControlStateNormal];
        [self.view addSubview:self.keyboardButton];
        [self.keyboardButton setFrame:CGRectMake(150, 200, 100, 100)];
        [self.keyboardButton addTarget:self action:@selector(pressButton:) forControlEvents:UIControlEventTouchUpInside];
    }
    
    - (void)pressButton:(UIButton *)button {
        id whichProduct = [SimpleFactory createProduct:button.titleLabel.text];
        [whichProduct productType];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    4234234
    并且创建相应的产品:

    #import "keyboardProduct.h"
    
    @implementation keyboardProduct
    
    - (void)productType {
        NSLog(@"键盘");
    }
    
    @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    通过传递的信息来告诉工厂你想加工的产品:

    + (id)createProduct:(NSString *)productType {
        NSArray *productArray = @[@"Mouse", @"Keyboard"];
        switch ([productArray indexOfObject:productType]) {
            case 0:
                return [[MouseProduct alloc] init];
                break;
            case 1:
                return [[keyboardProduct alloc] init];
                break;
        }
        return NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    5345345

    2.工厂方法模式(Factory Method Pattern)

    工厂方法模式(Factory Method Pattern)又称为工厂模式,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,即通过不同的工厂子类来创建不同的产品对象。就像Cocoa Touch中的NSNumbernumberWithBoolnumberWithInt方法,他们传入不同类型的参数,获得NSNumber实例。
    4234234

    通俗的讲: 工厂方法模式就是在简单工厂模式的前提下,扩展了工厂,他不再是通过一个工厂来完成多个产品的生产,而是通过一个统一的抽象工厂,分配任务给底下相应生产专一产品的工厂让他们来生产产品。

    优点:

    • 根据所需产品找对应工厂进行生产,不关心产品细节,也不需要知道产品类的类名。
    • 当系统中加入新产品时,不需要修改抽象工厂和抽象产品提供的接口,也无须修改客户端和其他的具体工厂和具体产品,而只要添加一个具体工厂和与其对应的具体产品就可以了,符合了开闭原则。
    • 降低了工厂类的内聚,满足了类之间的层次关系,又很好的符合了面向对象设计中的单一职责原则,这样有利于程序的拓展。

    缺点:

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

    与简单工厂的区别:

    工厂方法和简单工厂有一些区别,简单工厂是由一个代工厂生产不同的产品,而工厂方法是对工厂进行抽象化,不同产品都由专门的具体工厂来生产。
    即:多个工厂对应多个品牌厂商进行一对一生产。

    代码例子:

    创建两个button,控制不同工厂生产产品:

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        self.mouseButton = [UIButton buttonWithType:UIButtonTypeSystem];
        [self.mouseButton setTitle:@"Mouse" forState:UIControlStateNormal];
        [self.view addSubview:self.mouseButton];
        [self.mouseButton setFrame:CGRectMake(50, 200, 100, 100)];
        [self.mouseButton addTarget:self action:@selector(pressButton:) forControlEvents:UIControlEventTouchUpInside];
        
        self.keyboardButton = [UIButton buttonWithType:UIButtonTypeSystem];
        [self.keyboardButton setTitle:@"Keyboard" forState:UIControlStateNormal];
        [self.view addSubview:self.keyboardButton];
        [self.keyboardButton setFrame:CGRectMake(150, 200, 100, 100)];
        [self.keyboardButton addTarget:self action:@selector(pressButton:) forControlEvents:UIControlEventTouchUpInside];
    }
    
    - (void)pressButton:(UIButton *)button {
        id whichFactory = [MethodFactory allocatingTask:button.titleLabel.text];
        id whichProduct = [whichFactory creatProduct];
        [whichProduct productType];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    5345345

    定义工厂基类,其中包含子类工厂要实现的方法:

    #import <Foundation/Foundation.h>
    #import "ProductProtocol.h"
    
    @interface BaseFactory : NSObject<ProductProtocol>
    
    - (id)creatProduct;
    
    @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    子类去实现该基类的方法:

    //.h文件
    #import "BaseFactory.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface KeyboardFactory : BaseFactory
    
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    
    //.m文件
    #import "KeyboardFactory.h"
    #import "KeyboardProduct.h"
    
    
    @implementation KeyboardFactory
    
    - (id)creatProduct {
        NSLog(@"生产键盘");
        return [[KeyboardProduct alloc] init];
    }
    
    @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    同时实现工厂产品类:

    #import "KeyboardProduct.h"
    
    @implementation KeyboardProduct
    
    - (void)productType {
        NSLog(@"开始生产键盘");
    }
    
    @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    5345345

    3.抽象工厂模式(Abstract Factory Pattern)

    提供一个创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类。一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象。
    5345345

    通俗的讲: 抽象工厂就是在工厂方法模式的基础上,扩展了产品,之前通过抽象工厂给各实例工厂说明自己要生产的产品,但是一个工厂就只能生产一种产品,但是抽象工厂就是它的升级版,现在的一个实例工厂就可以生产多个产品,相当于一个工厂就是一个品牌,他可以生产手机、键盘、电脑等等。

    优点:

    • 不需要生产产品细节,只需要知道产品属于那个工厂就行,当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式。
    • 让你感觉不到内部差异性(cocoa框架里到处都是使用了这种设计,比如NSString、NSNumber)。

    缺点:

    • 如果你修改了抽象类的方法,那么所有的子类都要跟着一起修改。

    抽象工厂模式和工厂模式的比较:

    • 工厂方法模式:每个抽象产品派生多个具体产品类,每个抽象工厂类派生多个具体工厂类,每个具体工厂类负责一个具体产品的实例创建;
    • 抽象工厂模式:每个抽象工厂派生多个具体工厂类,每个具体工厂负责多个(一系列)具体产品的实例创建。

    代码例子:

    定义两个button,控制不同的工厂厂家:

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        
        self.hpButton = [UIButton buttonWithType:UIButtonTypeSystem];
        [self.hpButton setTitle:@"Hp" forState:UIControlStateNormal];
        [self.view addSubview:self.hpButton];
        [self.hpButton setFrame:CGRectMake(50, 200, 100, 100)];
        [self.hpButton addTarget:self action:@selector(pressButton:) forControlEvents:UIControlEventTouchUpInside];
        
        self.dellButton = [UIButton buttonWithType:UIButtonTypeSystem];
        [self.dellButton setTitle:@"Dell" forState:UIControlStateNormal];
        [self.view addSubview:self.dellButton];
        [self.dellButton setFrame:CGRectMake(150, 200, 100, 100)];
        [self.dellButton addTarget:self action:@selector(pressButton:) forControlEvents:UIControlEventTouchUpInside];
    }
    
    - (void)pressButton:(UIButton *)button {
        id whichFactory = [AbstractFactory allocatingTask:button.titleLabel.text];
        id whichProduct = [whichFactory createMouse];
        [whichProduct productType];
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    再定义两个基类,一个工厂基类,一个产品基类:

    //工厂基类
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface BaseFactory : NSObject
    
    - (id)createKeyboard;
    - (id)createMouse;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    //产品基类
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface BaseProduct : NSObject
    
    - (void)productType;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在定义上述两个基类的子类,并实现其方法:

    //工厂子类,剩下的等同
    //.h文件
    #import "BaseFactory.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface HpFactory : BaseFactory
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    //.m文件
    #import "HpFactory.h"
    #import "KeyboardHpProduct.h"
    #import "MouseHpProduct.h"
    
    @implementation HpFactory
    
    - (id)createKeyboard {
        NSLog(@"Hp Factory Create Keyboard");
        return [[KeyboardHpProduct alloc] init];
    }
    - (id)createMouse {
        NSLog(@"Hp Factory Create Mouse");
        return [[MouseHpProduct alloc] init];
    }
    
    @end
    
    
    //产品子类,剩下的等同
    //.h文件
    #import "BaseProduct.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface KeyboardDellProduct : BaseProduct
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    //.m文件
    #import "KeyboardDellProduct.h"
    
    @implementation KeyboardDellProduct
    
    - (void)productType {
        NSLog(@"Dell keyboard");
    }
    
    @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    53434534

    可能只这么看看没有什么体会,可以看看我的Demo:工厂模式

  • 相关阅读:
    2022技能大赛训练题:交换机snmp配置
    矩阵论理论知识(三)特殊的线性空间
    JavaScript之正则表达式
    【计算机视觉40例】案例34:勾勒五官轮廓
    C++设计模式——单例模式
    leetcodetop100(29) K 个一组翻转链表
    y45.第三章 Kubernetes从入门到精通 -- k8s中运行web服务(十八)
    React从脚手架开始搭建项目
    Util应用框架快速入门(4) - 集成测试开发入门
    【设计模式】23种设计模式笔记
  • 原文地址:https://blog.csdn.net/m0_55124878/article/details/125988426