iOS 24种设计模式 - Swift

设计模式是一种被广泛应用于软件工程的解决问题的方法。

它们可以帮助开发人员提高代码的可复用性、可维护性和可扩展性。设计模式的使用可以让开发人员更加专注于解决实际的问题而不是去考虑如何实现它们。这些设计模式可以分为三种类型,分别是:

1. 创建型模式(Creational Patterns)

2. 结构型模式(Structural Patterns)

3. 行为型模式(Behavioral Patterns)

同时设计模式有七大原则:

1 总原则:开闭原则(Open/Closed Principle,OCP)

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

通过增加新的代码,可以保证现有代码的稳定性,同时也可以提高代码的可维护性和可扩展性

例子

在上述代码中,我们采用了装饰器模式来实现开闭原则。首先,我们定义了一个抽象类CarDecorator,用于表示所有的汽车装饰器。然后,我们定义了一个具体装饰器AutoDriveDecorator,用于添加自动驾驶功能。在AutoDriveDecorator中,我们重写了startEngine方法,并在其中添加了自动驾驶功能。最后,我们可以通过创建一个AutoDriveDecorator对象来给汽车添加自动驾驶功能,而不需要修改原有的Car类。具体地,我们可以通过以下代码来实现:

通过扩展现有代码来满足新的需求,而不需要修改原有的代码。这种方式可以保证原有代码的结构和稳定性,同时也使得代码更加易于扩展和维护。

2 单一职责原则(Single responsibility principle, SRP)

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

单一职责原则可以帮助我们更好地组织代码,使得代码易于维护和扩展。通过遵循单一职责原则,我们可以使代码更加模块化,每个模块只负责一个职责,易于测试和重用。同时,当需求变化时,我们也可以更容易地对代码进行修改,而不会影响到其他模块。

上面这个示例中,我们将 Employee 类的两个职责分别拆分为 SalaryCalculator 类和 TaskPerformer类。这样每个类只负责单一职责,易于维护和扩展,符合单一职责原则。

使用:

在实际开发中,我们可以通过以下几个方面来遵循单一职责原则:

分离职责:将一个类中的不同职责拆分成独立的类或模块,使每个类或模块只负责单一职责。

抽象接口:使用抽象接口或协议来定义类或模块之间的交互方式,避免直接依赖具体的实现。

限制类的大小:尽可能限制类的大小,避免过于复杂的类,可以通过拆分、继承、组合等方式来实现。

定期重构:定期地对代码进行重构,去除重复代码,将代码按照职责进行组织,使得代码更易于维护和扩展。

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

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

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

这个原则可以提高程序的灵活性和可维护性,使程序更容易扩展和修改。

例子

假设有一个Animal 类和一个Dog类,Dog 类是Animal 类的子类。Animal 类有一个run 方法,它可以让动物奔跑;Dog 类继承了Animal 类,并重写了run 方法。

根据里氏替换原则,我们可以将Dog 对象赋值给Animal 对象,而程序仍然可以正常运行。例如:

4 依赖倒转原则(Dependency Inversion Principle)

官方解释是:高层模块不应该依赖低层模块,二者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象。

其实核心就是面向接口编程,而不是面向实现编程

这样的好处就是通过抽象接口,可以减少模块间的依赖关系,提高系统的灵活性和可维护性。

例子

实现:

5 接口隔离原则(Interface Segregation Principle)

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

核心思想是:一个类应该只提供其它类需要使用的方法,而不应该强迫其它类依赖于它们不需要使用的方法。

通过遵守接口隔离原则,可以提高系统的灵活性、可维护性和可扩展性

例子

实现:

接口隔离原则的核心是为了让接口具备高内聚性低耦合性

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

接口更加具有内聚性:每个接口只需要关注自己的功能,而不需要关注其他接口中的方法,因此能够使接口更加专注和具有内聚性。

接口之间的耦合度更低:每个接口只依赖于必要的方法,而不依赖于其他不必要的方法,因此能够使接口之间的耦合度更低。

代码的复用性更高:每个接口只包含必要的方法,因此能够使得代码的复用性更高,也能够提高代码的可读性和可维护性。

6 迪米特法则(最少知道原则)(The Law of Demeter, LoD)

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

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

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

例子

在上面的代码示例中,每个类都只和自己直接的朋友进行通信,遵循了最少知道原则。User 类只依赖于 ShoppingCart 类,而不直接依赖于 Goods 类。ShoppingCart 类只依赖于Goods 类。这样,当 Goods 类发生变化时,只会对 ShoppingCart 类产生影响,而不会对 User 类产生影响。 以下为实现代码:

7 合成复用原则(Composite Reuse Principle, CRP)

当我们需要在一个类中使用另一个类的功能时,有两种方式:继承和合成/聚合。

继承是指子类继承父类的属性和方法,可以重写父类的方法,但也存在耦合性较高的问题,当父类修改时,子类也需要相应地修改。

合成/聚合是指一个类作为另一个类的成员变量,通过调用该成员变量的方法来实现该类的功能

组合和继承都可以用来实现代码的复用,但是它们之间有着不同的优缺点。在使用继承时,我们需要注意代码的耦合度,避免代码的重复和臃肿。在使用组合时,我们需要将代码分解为更小的组件,并注意组件之间的接口设计。

对设计模式七大原则的理解,学习第一种模式:创建型模式。

在面向对象编程中,创建型模式是用于实例化对象的设计模式。它们可以帮助我们有效地创建对象,而不会使代码变得复杂或难以维护。

在实际的开发过程中,正确使用创建型模式可以带来许多好处:

降低耦合性:创建型模式可以将对象的创建过程与使用过程分离,从而降低对象之间的耦合性。这使得代码更加灵活和易于维护。

提高复用性:创建型模式可以将对象的创建过程抽象出来,并使其可复用。这使得我们可以在整个应用程序中共享对象的创建逻辑,从而提高了代码的复用性。

隐藏对象的创建细节:使用创建型模式可以将对象的创建细节隐藏在模式内部,使得客户端无需知道对象创建的具体细节。这使得代码更加简洁、清晰,并提高了代码的可读性。

管理对象的生命周期:某些创建型模式(如工厂方法模式)可以管理对象的生命周期,从而确保对象的正确创建、使用和销毁。

改善代码的可测试性:使用创建型模式可以使代码更加易于测试。由于对象的创建过程被抽象出来并且与使用过程分离,因此可以更容易地编写测试用例

常见的创建型模式:

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

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

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

4.单例模式(Singleton Pattern)

5.原形模式(Prototype Pattern)

6.建造者模式(Builder Pattern)

一、简单工厂模式(Simple Factory Pattern)

在工厂模式中,我们在创建对象时,通过使用一个共同的接口来创建不同的对象,而无需暴露对象的创建逻辑。

在简单工厂模式中,我们定义一个工厂类,该工厂类负责创建各种对象。我们只需调用工厂类的方法即可获取需要的对象,而无需直接使用 new 关键字创建对象。这样可以使代码更加灵活和易于维护,因为如果我们想要更改创建对象的逻辑,只需要更改工厂类中的代码即可。

假设我们有一个 Animals 类和两个子类 Dogs 和 Cats,我们想要使用工厂模式来创建它们的实例:

在上面的代码中,我们定义了一个 Animals类和两个子类 Dogs 和 Cats。我们还定义了一个 AnimalFactory 工厂类,其中有一个 createAnimalWithType方法,该方法根据传入的 type 参数来创建不同类型的动物对象。当我们需要创建动物对象时,只需调用 AnimalFactory 的 createAnimalWithType方法,传入所需的类型,即可获取相应的对象。

通过这种方式,我们可以在不暴露对象创建逻辑的情况下创建不同类型的对象,并且可以轻松地添加新类型的动物,只需在 AnimalFactory 中添加相应的创建逻辑即可。

二、工厂方法模式(Factory Method Pattern)

工厂方法模式和简单工厂模式类似。

简单工厂模式和工厂方法模式的区别在于创建对象的方式

简单工厂模式使用一个共享的工厂类来创建所有类型的对象,客户端通过传递参数来指定要创建的对象类型。

而工厂方法模式将每种具体产品的创建逻辑委托给对应的工厂类,客户端通过选择特定的工厂类来创建特定类型的对象

简单工厂模式适用于对象类型较少且创建逻辑相对简单的情况。工厂方法模式适用于对象类型较多或创建逻辑较为复杂的情况,它更符合开闭原则,允许新增具体产品和工厂类,而无需修改现有代码。

三、抽象工厂模式(Abstract Factory Pattern)

它提供一个接口用于创建一系列相关或依赖对象的家族,而不需要明确指定它们的具体类。

在抽象工厂模式中,抽象工厂接口定义了一组方法,用于创建一系列相关对象的工厂。这些相关对象可以是一组不同但相关的对象,例如在GUI界面中,按钮、标签、输入框等都属于相关的对象家族,它们通常会按照某种风格设计,如iOS 风格、Android 风格等。在抽象工厂模式中,每个具体工厂类都实现了这个抽象工厂接口,以便能够按照某种特定的规则或风格创建相关对象的家族。

可以说抽象工厂模式是工厂模式的一个扩展,它提供了一种更高层次的抽象,能够创建一组相关的对象家族,而不仅仅是一个对象

同时,抽象工厂模式也遵循了开闭原则,因为它允许在不修改现有代码的情况下添加新的具体工厂和产品系列,从而提高了代码的可维护性和可扩展性。

例子:

我们要写两个风格的UI,分别是iOS 风格和Android 风格,首先,我们定义一个抽象工厂接口,它包含两个方法:创建按钮和创建标签。然后,我们创建两个具体工厂类,它们实现了这个抽象工厂接口,并分别创建了iOS 和Android 风格的按钮和标签。

最后,我们使用这些具体工厂类来创建具体的按钮和标签对象,而不需要知道它们的具体类。

通过使用抽象工厂模式,我们可以轻松地扩展工厂和产品系列,而不需要修改现有的客户端代码。例如,我们可以创建一个新的具体工厂类,以创建Windows风格的按钮和标签。

抽象工厂模式的优点:

将对象的创建与使用分离,客户端只需要知道如何使用这些对象,而不需要关心它们是如何创建的。

可以轻松地在程序中切换不同的产品簇,只需要改变具体工厂类即可,无需修改客户端代码。

可以确保一组相关的产品只能由同一个工厂创建,避免了不同产品之间的兼容性问题。

可以提高代码的扩展性和可维护性,如果需要添加一个新的产品簇,只需要添加一个新的具体工厂类即可

抽象工厂模式的缺点:

新增加一个产品簇比较困难,需要修改抽象工厂接口和所有的具体工厂类。

如果产品簇过多,会导致抽象工厂接口和所有的具体工厂类变得非常复杂,不易维护。

由于抽象工厂模式需要定义抽象工厂接口和抽象产品接口,因此代码的抽象层次比较高,有一定的学习成本。

总之,抽象工厂模式适合于那些需要一整套一起使用的对象(比如QQ 换皮肤),并且需要能够方便地切换不同的产品簇的场景,但是如果产品簇过多或者需要频繁添加新的产品簇,可能会导致代码变得复杂。

“产品簇:指的是一组相关联的产品,它们通常用于解决同一个问题或者满足同一种需求。例如,在电商网站上,一个产品簇可以包括商品、订单、收货地址等多个相关联的产品;在游戏开发中,一个产品簇可以包括角色、武器、装备等多相关联的产品。”

四、单例模式(Singleton Pattern)

单例模式是一种创建对象的设计模式,它确保一个类只有一个实例,并提供全局访问点以访问该实例。

单例模式通常用于需要全局访问且只需要一个实例的场景。下面是一些常见的用例:

配置对象:在应用程序中,可能需要一个全局配置对象,用于存储应用程序的设置和配置选项。使用单例模式可以确保只有一个配置对象,并使其在整个应用程序中易于访问。

数据库连接对象:在使用数据库的应用程序中,可能需要一个全局的数据库连接对象,用于执行查询和更新操作。使用单例模式可以确保只有一个数据库连接对象,并使其在整个应用程序中易于访问。

日志对象:在应用程序中,可能需要一个全局的日志对象,用于记录应用程序的运行时信息。使用单例模式可以确保只有一个日志对象,并使其在整个应用程序中易于访问。

总之,单例模式在需要全局访问且只需要一个实例的场景下非常有用。但是,使用单例模式也存在一些缺点,例如可能导致代码耦合性增强、单例对象的生命周期可能过长等问题。因此,在使用单例模式时需要谨慎考虑其适用性和使用方式。

五、原形模式(Prototype Pattern)

原形模式允许通过复制现有对象来创建新对象,而不是通过实例化新的对象。这种模式通常用于创建复杂的对象,因为创建这些对象需要大量时间和资源。

原型模式通常在以下场景中使用:

创建复杂对象:如果要创建的对象比较复杂,例如具有多个子对象或需要大量计算才能创建,那么使用原型模式可以避免重复的计算和复杂的对象构建过程。

提高性能:使用原型模式可以提高性能,因为复制现有对象比创建新对象要

保护对象:有时候,为了保护对象的不变性,希望对象不能被修改。在这种情况下,使用原型模式可以确保不会修改原始对象,因为只能复制它而不是直接修改它。

动态配置对象:如果要在运行时动态配置对象,例如根据用户输入或环境变量来配置对象的属性,那么使用原型模式可以方便地生成新对象。

它通常与工厂模式和建造者模式一起使用,以提供更好的灵活性和可维护性。

在实际的开发中,可能会遇到需要创建复杂对象的场景,例如:

图形化界面:开发一个图形化界面,可能需要创建许多复杂的图形对象,例如窗口、按钮、标签、文本框等。

游戏开发:可能需要创建许多复杂的游戏对象,例如角色、怪物、道具等。

数据库访问层:需要创建许多复杂的数据对象,例如表格、列、行等。

网络通信:开发一个网络应用程序,可能需要创建许多复杂的网络对象,例如请求、响应、协议等。

在这些场景中,复杂对象通常包含多个子对象或属性,并且可能需要执行多个计算或操作才能构建。使用原型模式可以避免在每次需要创建对象时执行这些计算或操作,并且可以提高性能和可维护性。

例子

在 iOS 开发中,创建一个复杂的界面可能涉及许多对象,例如UIView、UILabel、UIButton等。我们可以使用原型模式来创建这些对象,而不需要每次都手动创建和配置它们。

在上面的例子中,我们创建了一个包含多个子对象的 UIView子类 MyView,并定义了一个复制方法来实现对象的复制。然后,我们创建了一个原型对象 prototypeView,并设置了它的子对象 titleLabel 和 button。最后,我们复制了原型对象,并在新对象 newView 中修改了 titleLabel 和 button 的属性。

原型模式与工厂模式的结合使用

工厂模式用于创建复杂对象,而原型模式则可以用于快速复制这些复杂对象。 这种结合使用可以提高对象的创建效率和性能

原型模式与建造者模式的结合使用

建造者模式用于创建复杂对象,它通过分步骤的方式来构建对象,而原型模式可以用于快速复制已有的对象实例,使得在建造对象时更加高效。在这种结合使用的情况下,建造者模式通常用于创建原型实例,并使用原型实例的复制方法创建新的对象实例。这种结合使用可以提高对象的创建效率和灵活性。

网络请求

原型模式在网络请求中也可以使用,通常用于缓存请求结果以提高应用程序的性能。在某些情况下,应用程序需要从网络中获取大量的数据,并在应用程序中多次使用相同的数据。如果每次都从网络中获取数据,这将会降低应用程序的性能,并且可能会因为网络延迟导致应用程序的响应速度变慢。在这种情况下,使用原型模式来缓存请求结果可以提高应用程序的性能。

六、建造者模式(Builder Pattern)

建造者模式(Builder Pattern)可以将对象的构建过程与其表示分离,从而使同样的构建过程可以创建不同的表示。这种模式通常用于需要复杂对象的构建过程,并且需要将构建过程的细节隐藏起来,以便于用户只需要关心所需的结果即可。

在建造者模式中,通常会定义一个Builder接口或者抽象类,其中包含了一系列构建复杂对象所需的方法。具体的构建器类实现了Builder接口或抽象类,并根据实际情况来实现每个方法,最终构建出一个具体的产品对象。同时,建造者模式还定义了一个Director类,用于协调Builder对象的构建过程,并且隔离客户端和产品对象的直接联系。

建造者模式通常适用于以下场景:

当创建一个复杂对象时,需要分步骤地进行创建,且需要控制每个步骤的顺序和内容时,可以使用建造者模式。

当需要创建的对象具有复杂的内部结构时,可以使用建造者模式来隔离复杂的对象创建过程,从而使其更易于理解和维护。

当需要创建多个具有相似属性的对象时,可以使用建造者模式来避免重复的构造代码,并且可以更容易地扩展和修改这些对象的构造过程。

当需要创建一个不可变的对象时,可以使用建造者模式来强制其必须通过构造器来创建,从而避免了对象在创建后被修改的可能性。

例子

举一个具体的例子,比如我们需要构建一份简历,其中包括个人信息、教育经历、工作经历等。如果使用建造者模式,我们可以将这份简历的构建过程分为多个步骤,并由具体的构建器来负责每个步骤的实现。这样做的好处是,我们可以根据不同的需求来构建不同风格的简历,而不需要在客户端代码中重复编写相似的代码。

现在我们使用 ResumeDirector 类来调用建造者来创建简历,具体代码如下:

这个 ResumeDirector 类接受一个建造者对象,然后在 construct 方法中按照特定的顺序调用建造者的方法来构建简历。现在我们可以使用这个 ResumeDirector 来构建简历:

输出结果为:

第二种模式:结构型模式:

结构型模式和创建型模式是设计模式中的两个主要分类。

创建型模式关注对象的创建机制,主要解决对象的实例化过程。创建型模式涉及到对象的创建、组合和表示,以及隐藏对象的创建逻辑。

结构型模式关注的是对象之间的组合和关联方式,以构建更大的结构和功能。它们着重于类和对象之间的静态关系,以及如何将它们组织在一起形成更大的结构,以满足系统的需求。结构型模式可以帮助我们在保持系统灵活性和可扩展性的同时,提供清晰的对象组织结构

总的来说,创建型模式更关注的是对象创建的灵活性和可维护性,结构型模式更关注的是系统结构的设计和组织。

常见的结构型模式:

1.适配器模式(Adapter Pattern)

2.桥接模式(Bridge Pattern)

3.组合模式(Composite Pattern)

4.装饰器模式(Decorator Pattern)

5.外观模式(Facade Pattern)

6.享元模式(Flyweight Pattern)

7.代理模式(Proxy Pattern)

这些结构型模式都有不同的应用场景和优势,可以根据具体的需求来选择合适的模式来改善软件设计的灵活性、可维护性和可扩展性。

一、适配器模式(Adapter Pattern)

用于将一个类的接口转换成另一个类的接口,以便两个不兼容的类能够一起工作

适配器模式可以解决在系统中使用已有的类,但其接口与需要的接口不匹配的情况。

使用方法:适配器继承依赖已有的对象,实现想要的目标接口。

继承

适配器可以通过继承已有的对象(旧接口),并实现目标接口来进行适配。通过继承,适配器获得了旧接口的功能,并且可以添加或重写方法来实现目标接口的要求

在这个例子中,OldAPI是旧的接口类,它有一个legacyRequest方法。NewAPI是目标接口,它定义了一个newRequest方法。适配器类Adapter继承自OldAPI,并实现了NewAPI接口。

通过继承,Adapter类继承了legacyRequest方法,然后在newRequest方法中调用了legacyRequest方法来执行旧接口的请求,并添加了适配后的新请求。

依赖

适配器也可以通过依赖已有的对象(旧接口),并将其作为成员变量来实现适配。适配器可以通过委派调用已有对象的方法,并根据目标接口的要求对返回结果进行适配处理。

Adapter适配器类通过依赖旧接口类OldAPI来实现适配。适配器类持有一个OldAPI对象的实例,并实现了NewAPI接口。

在适配器的newRequest方法中,适配器通过调用持有的OldAPI对象的legacyRequest方法来执行旧接口的请求,并在之后添加了适配后的新请求的逻辑。

在客户端代码中,我们创建了适配器类Adapter的实例,并将旧接口类OldAPI的实例设置为适配器类的oldAPI属性。然后调用适配器类的newRequest方法来发起新的请求。

无论是继承还是依赖,适配器都充当了一个中间层,通过转换和包装旧接口的功能,使其符合目标接口的要求。适配器将客户端的调用转换为对旧接口的调用,并对旧接口的返回结果进行适配,以满足客户端的期望。

适配器模式的缺点:

增加代码复杂性:适配器模式引入了额外的类和逻辑,增加了代码的复杂性。适配器的存在可能会增加代码的理解和维护成本。

运行时性能损耗:数据或方法的转换需要额外的处理步骤。

可能会导致过多的适配器类:在某些情况下,如果系统中存在大量不兼容的类,可能需要创建大量的适配器类来进行适配,这可能会导致类的数量过多,增加了系统的复杂性。

总体而言,适配器模式在解决接口不兼容问题提高代码复用性方面具有明显的优点。然而,在设计和使用适配器时需要权衡好代码复杂性、运行时性能以及适配器的数量,确保适配器模式的使用是合理的。

二、桥接模式(Bridge Pattern)

桥接模式用于把抽象化与实现化解耦,使得二者可以独立变化。他们分离后,可以独立地变化。桥接模式通过组合而不是继承来实现这种分离。

在桥接模式中,抽象部分(Abstraction)和实现部分(Implementation)分别定义了两个独立的类层次结构。抽象部分包含一个指向实现部分的引用,并且通过这个引用进行交互。这样,抽象部分就可以与不同的实现部分进行桥接,而不会与特定的实现部分耦合。

桥接模式中的“桥”就是协议接口,符合依赖倒转原则和面向接口的编程。

例子

在上述代码中,我们有一个实现部分的接口 Implementor,以及两个具体实现类 ConcreteImplementorA 和 ConcreteImplementorB。抽象部分 Abstraction 持有一个 Implementor 引用,并通过它调用实现部分的操作。

在客户端代码中,我们首先创建了一个具体的实现对象 ConcreteImplementorA,然后创建了抽象部分对象 Abstraction,并将实现对象A桥接进去。当调用抽象部分的操作[abstraction doAction] 时,实际上是通过抽象部分与具体实现对象A的桥接,执行了实现对象A的具体操作,输出了 "Concrete Implementor A is doing something."。

接下来,我们创建了另一个具体的实现对象 ConcreteImplementorB,并将其桥接进抽象部分对象。再次调用抽象部分的操作 [abstraction doAction] 时,由于实现对象B替换了实现对象A的桥接,输出变为了 "Concrete Implementor B is doing something."。

桥接模式的优点:抽象部分和实现部分可以独立地扩展和变化,它们之间的关系是通过桥接而不是继承建立的。这种分离允许我们在不影响其他部分的情况下修改抽象部分或实现部分,从而提高了系统的灵活性和可扩展性。在iOS开发中,桥接模式可以应用于以下方面:

网络请求库: 当你需要进行网络请求时,可以使用桥接模式将网络请求库与具体的网络协议(如HTTP、WebSocket等)进行解耦。你可以定义一个抽象的网络请求接口,并针对每种具体的网络协议实现一个具体的网络请求类。通过桥接模式,可以灵活地切换或添加新的网络协议实现。

视图控制器和视图之间的桥接: 在iOS应用中,视图控制器负责管理视图的显示和交互逻辑,而视图负责界面的展示。你可以使用桥接模式来将视图控制器与具体的视图实现进行解耦。通过定义一个抽象的视图接口,并针对不同的界面元素(如按钮、标签等)实现具体的视图类,可以实现视图控制器和视图的独立变化和组合。

三、组合模式(Composite Pattern)

组合模式可以将对象组合成树形结构,以表示"整体-部分"的层次结构。组合模式使得客户端能够以统一的方式处理单个对象和组合对象,从而使得代码更加简洁和可扩展。

在组合模式中,有两种基本类型的对象:叶节点(Leaf)和组合节点(Composite)。叶节点表示树结构中的最小单位,它们没有子节点。而组合节点则包含叶节点和其他组合节点,形成了树结构的层次关系。

例子

下面这个例子中将会演示如何使用组合模式来表示一个文件系统的树形结构。

首先,定义一个抽象基类 FileSystemComponent,它包含了共同的行为和属性,包括名称、添加子节点、删除子节点、获取子节点等:

然后,定义叶节点 File 类,表示文件:

接下来,定义组合节点 Directory 类,表示文件夹:

现在,我们可以使用组合模式来构建一个文件系统的树形结构。例如:

在上面的代码中,我们创建了一个文件系统的树形结构。root 是根目录,包含了 Documents 文件夹和 Pictures文件夹,以及一个名为 notes.txt 的文件。Documents 文件夹中包含一个名为 readme.txt 的文件,而 Pictures文件夹中包含两个图片文件 photo1.jpg 和 photo2.jpg。

通过组合模式,我们可以以统一的方式处理文件和文件夹,无论它们是叶节点还是组合节点。例如,我们可以通过递归遍历整个文件系统来打印出所有的文件和文件夹:

调用:

通过上述代码,我们可以遍历整个文件系统,并打印出文件和文件夹的结构。

组合模式的优点是,它简化了对整体和部分之间的处理。客户端可以一致地对待单个对象和组合对象,而不需要关心它们的具体类型。这种一致性使得代码更加灵活和可扩展,可以轻松地添加、删除和修改组合节点,而无需修改客户端代码

在iOS开发中,组合模式可以在多种场景中使用。下面是一些常见的应用场景:

视图层次结构:在iOS中,视图层次结构是一个典型的树形结构。UIView类可以看作是组合模式中的组合节点,它可以包含其他UIView实例作为子视图(叶节点)。通过使用组合模式,可以以统一的方式处理视图层次结构中的单个视图和组合视图。

文件系统操作:在处理文件系统相关操作时,组合模式也非常有用。例如,你可以使用组合模式来构建一个文件管理器应用程序,以便处理文件和文件夹的创建、删除、复制等操作。组合模式使得你可以一致地对待文件和文件夹,无论是单个文件还是包含其他文件和文件夹的文件夹。

数据结构处理:在处理具有层次结构的数据时,组合模式可以派上用场。例如,你可能需要处理一个包含多级分类的数据集合,每个分类又可以包含子分类。通过使用组合模式,可以轻松地表示和操作这种层次结构的数据。

也即:UIView中常用的addSubview方法其实就是组合模式。

通过这个方法,我们可以将一个 UIView 对象作为子视图添加到另一个 UIView 对象中,形成视图层次结构。

在组合模式中,UIView 对象可以看作是组合模式中的组合节点,它可以包含其他 UIView 对象作为子视图(叶节点)。通过使用 addSubview: 方法,我们可以将叶节点视图添加到组合节点视图中,构建复杂的视图层次结构。

组合模式具有以下优点:

统一接口:组合模式提供了一个统一的接口,使得客户端可以以相同的方式处理单个对象和组合对象。这简化了客户端代码,并提高了代码的可读性和可维护性

简化客户端代码:使用组合模式,客户端不需要关心处理单个对象还是组合对象,可以通过统一的接口直接操作整个对象层次结构。这样可以简化客户端代码,减少条件判断和类型检查的逻辑。

可扩展性:组合模式支持动态的添加、删除和修改对象,使得对象层次结构可以很容易地进行扩展和变化。这种灵活性使得我们可以构建复杂的对象结构,并在不影响现有代码的情况下进行修改和调整。

递归处理:组合模式使用递归的方式处理整个对象层次结构,可以方便地对整个层次结构进行遍历和操作。

组合模式也存在一些缺点:

限制类型的组合:在组合模式中,叶节点和组合节点具有相同的接口,但是并不意味着所有操作对于叶节点和组合节点都是有意义的。有些操作可能只适用于叶节点,而有些操作可能只适用于组合节点。

对象层次结构的复杂性:随着对象层次结构的增长,特别是在处理大型和复杂的层次结构时,组合模式可能会导致对象层次结构变得复杂和难以理解。需要合理地划分对象的职责和功能,以避免层次结构的过度复杂化。

可能带来性能损失:由于组合模式涉及递归操作和遍历整个对象层次结构,可能会在一些情况下带来一定的性能损失。特别是在处理大型层次结构时,需要仔细考虑性能问题,并根据实际情况进行优化。

总的来说,组合模式在处理对象层次结构时提供了一种简洁和灵活的方式,但在使用时需要注意合理划分对象的职责和功能,以及在性能方面的考虑。根据具体的应用场景和需求,权衡利弊后选择是否 使用组合模式。

四、装饰器模式(Decorator Pattern)

装饰器模式最大的特点就是在不改变现有对象结构的情况下,动态地向对象添加新的行为或功能

装饰器模式通过将对象包装在一个装饰器对象中,来扩展对象的功能。

在iOS开发中,装饰器模式常常用于以下情况:

动态扩展对象的功能:当我们需要在不修改现有代码的情况下,为一个对象添加额外的功能时,可以使用装饰器模式。这样可以避免修改原有代码,同时实现功能的灵活组合。

对象功能的组合:当一个对象可能有多个功能组合的情况下,装饰器模式可以让我们通过不同的装饰器进行功能的组合。

装饰器模式也可以与其他设计模式结合使用,例如结合工厂模式来创建装饰器对象。

例子

首先,我们定义一个抽象的形状接口 Shape:

然后,我们创建具体的形状类,如矩形和圆形:

接下来,我们创建具体的装饰器类 ColorDecorator 和 BorderDecorator,它们实现了 Shape 接口,并在 draw 方法中添加了颜色和边框的绘制:

接下来,我们定义一个装饰器工厂类 ShapeDecoratorFactory,用于创建装饰器对象:

接下来,我们可以使用装饰器工厂类 ShapeDecoratorFactory 来创建装饰后的形状对象。以下是一个示例的使用代码:

装饰器模式具有以下优点:

动态扩展功能:装饰器模式允许在不修改现有代码的情况下,动态地为对象添加额外的功能。通过装饰器模式,可以在运行时对对象进行灵活的功能组合,实现功能的动态扩展。

遵循开放封闭原则:装饰器模式可以遵循开放封闭原则,即对扩展开放,对修改封闭。通过装饰器模式,可以通过添加装饰器来扩展对象的功能,而无需修改原有对象的代码。

避免继承的复杂性:装饰器模式通过组合和委托的方式,避免了使用大量的子类来实现功能的扩展。相比于继承,装饰器模式更加灵活,并且可以在运行时动态地组合功能。

单一职责原则:装饰器模式可以将功能的细粒度分离,每个装饰器类只关注特定的功能扩展,使得每个类都具有单一职责。

然而,装饰器模式也存在一些缺点:

增加复杂性:使用装饰器模式会增加额外的类和对象,导致代码结构变得更加复杂,理解和维护成本增加。

装饰器顺序依赖:如果装饰器的顺序不正确,可能会影响功能的正确执行。特别是当多个装饰器共同修改同一个方法时,装饰器的顺序非常重要。

不适合大量装饰器的情况:如果需要使用大量的装饰器来装饰对象,会导致装饰器的层级嵌套过深,代码变得复杂且难以维护。

因此,如果需要动态扩展对象的功能且保持代码的灵活性,装饰器模式是一种有效的设计模式选择。

五、外观模式(Facade Pattern)

外观模式提供了一个简单而统一的接口,用于访问复杂系统中的一组接口。

外观模式隐藏了系统的复杂性,并为客户端提供了一个更简单和更直接的访问方式。它可以将一个复杂的子系统封装起来,客户端只需要通过与外观对象进行交互,而无需直接与子系统中的各个对象进行交互

在iOS开发中,外观模式常用于简化复杂的系统或框架,并提供一个更简单的接口供客户端使用。

也就是说,在使用第三方库的应用中,对第三方库进行封装,便于客户端的使用,就是外观模式的一种体现。

外观模式有以下优点:

简化接口:外观模式提供了一个简化的接口,隐藏了底层系统的复杂性,使得客户端更容易使用和理解系统功能。

解耦合:外观模式将客户端与底层系统解耦,客户端只需要通过外观类与系统进行交互,而不需要直接与系统的各个子组件进行交互。

提高灵活性:通过外观类作为中间层,可以灵活地修改底层系统的实现细节,而不影响客户端的代码。

提高可维护性:外观模式将底层系统的复杂性封装在一个类中,使得系统结构更加清晰,易于理解和维护。

外观模式也有一些缺点:

不符合开闭原则当底层系统发生变化时,可能需要修改外观类的代码,这违反了对修改关闭的原则。

可能引入性能问题:外观模式的封装会增加一层额外的间接调用,可能会对系统的性能产生一定影响。

可能导致过度设计:过度使用外观模式可能导致系统结构过于复杂,增加不必要的代码复杂性和维护成本。

六、享元模式(Flyweight Pattern)

享元模式旨在通过共享对象减少内存使用和提高性能。它适用于存在大量相似对象的情况,通过共享这些对象的公共部分来减少内存消耗

在享元模式中,对象分为两种类型:内部状态(Intrinsic State)和外部状态(Extrinsic State)。内部状态是可以被共享的,它包含对象的固有属性,不会因外部环境的改变而改变。外部状态是不可共享的,它取决于对象被使用的上下文。

在iOS开发中,享元模式可以应用于以下场景:

视图复用:在使用UITableView或UICollectionView等可重用视图的情况下,可以使用享元模式来共享可重用视图的实例。这样可以避免频繁地创建和销毁视图对象,提高滚动性能和响应速度。

图像缓存:在需要频繁加载和显示大量图像的应用中,可以使用享元模式来共享已加载的图像实例。这样可以避免重复加载相同的图像,减少内存消耗,并提高性能。

字符串常量池:在应用中使用的字符串常量(例如错误提示、网络请求URL等)可以被视为享元对象。通过共享这些字符串常量的实例,可以节省内存并提高字符串比较的效率。

总之,享元模式在iOS开发中可以用于任何需要共享大量相似对象降低内存消耗的场景。

图像缓存

当需要频繁加载和显示大量图像的应用中,使用享元模式可以有效地进行图像缓存,避免重复加载相同的图像,减少内存消耗,并提高性能。

享元模式具有以下优点:

减少内存消耗:通过共享对象实例,可以大大减少系统中相似对象的数量,从而降低内存消耗。共享的对象可以被多个客户端共同使用,避免了创建大量相似对象的开销。

提高性能:由于减少了对象的数量,享元模式可以提高系统的性能。重复使用共享对象可以避免频繁的对象创建和销毁操作,从而减少了系统的开销。

状态外部化:享元模式将对象的内部状态和外部状态分离。内部状态是可以共享的,而外部状态是变化的,可以根据需要传递给享元对象。这种状态的外部化可以简化对象的逻辑,使对象更易于使用和维护。

提高可维护性和可扩展性:通过将对象的状态外部化,享元模式使得对象的逻辑更加清晰和简洁。这样可以提高代码的可维护性和可扩展性,使系统更容易理解和修改。

然而,享元模式也有一些限制和缺点:

共享对象的状态必须是可共享的:享元模式要求对象的内部状态可以被共享,而外部状态可以在使用时传递。如果对象的状态不可共享,那么无法使用享元模式来实现对象的共享和复用。

增加了系统的复杂性:享元模式将对象分为内部状态和外部状态,并且需要额外的逻辑来管理共享对象的创建和访问。这样会增加系统的复杂性,需要仔细考虑对象的状态和共享机制的设计。

可能导致线程安全问题:如果多个线程同时访问和修改共享对象的状态,可能会导致线程安全问题。在使用享元模式时,需要注意对共享对象的状态进行正确的同步和管理,以避免并发访问问题

综上所述,享元模式适用于需要共享大量相似对象,并且可以提供内存和性能优化的场景。

七、代理模式(Proxy Pattern)

代理模式通过创建一个代理对象来控制对另一个对象的访问。

代理对象充当原始对象的中间人,客户端通过代理对象与原始对象进行交互,代理对象可以在不修改原始对象的情况下增加额外的功能

代理模式的主要目的是实现对对象的间接访问,以提供更灵活的控制和扩展。代理模式常见的应用场景包括:

远程代理:允许通过网络访问远程对象,隐藏了底层的复杂性

虚拟代理:用于懒加载对象,只有在需要时才会创建真实对象。

安全代理:控制对对象的访问权限

智能代理:在访问对象时执行额外的逻辑,如缓存、日志记录等。

例子

假设我们有一个下载器对象(Downloader)负责从互联网上下载文件。现在我们希望在每次下载前后记录下载日志。我们可以使用代理模式来实现:

在上述示例中,Downloader是一个协议,定义了下载文件的方法downloadFile:。RealDownloader是实际执行下载操作的对象,它实现了Downloader协议。DownloaderProxy是代理对象,也实现了Downloader协议。

当客户端调用代理对象的downloadFile:方法时,代理对象在下载前后分别输出日志信息,并将实际的下载操作委托给RealDownloader对象执行。

代理模式有以下优点:

隔离客户端和真实对象:代理模式通过代理对象将客户端与真实对象隔离开来,客户端不直接与真实对象交互,而是通过代理对象进行间接访问。这样可以减少客户端与真实对象之间的耦合,提高系统的灵活性和可维护性。

控制对真实对象的访问:代理对象可以在访问真实对象之前或之后执行额外的操作,例如权限控制、缓存、延迟加载等。这样可以对真实对象的访问进行控制和管理,增加了系统的安全性和可控性

提供额外的功能:代理对象可以在不修改真实对象的情况下,扩展或增强其功能。例如,代理对象可以添加日志记录、性能统计、异常处理等功能,从而为客户端提供更多的服务。

代理模式也有一些缺点:

增加了系统复杂性:引入代理对象会增加系统中的类和对象数量,导致系统的复杂性增加。过多的代理对象可能会使代码结构变得复杂,难以理解和维护。

增加了请求的处理时间:由于代理模式引入了额外的对象,每个请求都需要经过代理对象的处理,可能会增加一定的处理时间。

可能降低直接访问的效率:由于代理模式是通过间接访问实现的,所以在直接访问真实对象时可能会降低一些效率。

第三种模式:行为型模式:

行为型模式和结构型模式、创建型模式一样,是设计模式中的主要分类。

创建型模式更关注的是对象创建的灵活性和可维护性,结构型模式更关注的是系统结构的设计和组织。

行为型模式(Behavioral Patterns)关注的是对象之间的通信和交互方式,以实现特定的行为和责任分配

虽然行为型模式、结构型模式和创建型模式关注的方面不同,但它们之间也存在联系和互相影响。在实际应用中,这些模式往往会结合使用,以达到更好的设计和架构。例如,我们可以使用创建型模式创建对象,并使用结构型模式组织和管理这些对象,最后使用行为型模式定义对象之间的交互方式

常见的行为型模式:

1.责任链模式(Chain of Responsibility Pattern)

2.观察者模式(Observer Pattern)

3.策略模式(Strategy Pattern)

4.模板方法模式(Template Method Pattern)

5.命令模式(Command Pattern)

6.解释器模式(Interpreter Pattern)

7.迭代器模式(Iterator Pattern)

8.中介者模式(Mediator Pattern)

9.备忘录模式(Memento Pattern)

10.状态模式(State Pattern)

11.访问者模式(Visitor Pattern)

在iOS 的实际开发中,责任链模式,观察者模式,策略模式,模板方法模式 这四种模式比较常用。

一、责任链模式(Chain of Responsibility Pattern)

责任链模式将请求沿着处理链传递,直到有一个处理者能够处理该请求。在责任链模式中,每个处理者都有一个对下一个处理者的引用,形成一个链条。

这种模式的主要目的是解耦发送者和接收者之间的关系发送者不需要知道具体是哪个接收者会处理请求,而接收者也不需要知道请求的发送者是谁。 通过这种方式,责任链模式可以灵活地添加、修改或者删除处理者,而不会对系统的其他部分产生影响。

责任链模式是在iOS 的实际开发中比较常用的模式:常用于处理事件或请求的传递。不同的对象可以按照一定的顺序处理事件,直到有对象能够处理该事件为止。

例子

使用:

在这个示例中,抽象处理者(Handler)定义了一个处理请求的方法 handleRequest:,并持有一个对下一个处理者的引用 nextHandler。具体的处理者(ConcreteHandlerA、ConcreteHandlerB和ConcreteHandlerC)继承自抽象处理者,并实现了自己的处理逻辑。

客户端代码创建了具体处理者的实例,并通过设置 nextHandler 属性将它们链接在一起形成责任链。当一个请求被发送给第一个处理者时,它会首先尝试处理请求,如果不满足处理条件,则将请求传递给下一个处理者,直到有一个处理者能够处理请求或者到达链的末尾。

在上述代码中,客户端发送了三个请求:5、15和25。根据具体处理者的处理条件,请求被传递到了不同的处理者进行处理。输出结果如下:

在iOS 开发中,责任链模式一般应用于以下场景:

事件处理:当一个事件需要经过多个对象进行处理时,可以使用责任链模式。例如,iOS中的事件传递机制就可以看作是责任链模式的应用。事件首先被传递给视图层次结构中的顶层视图,然后逐级向下传递,直到找到能够处理该事件的视图。

错误处理:当在应用中发生错误时,可以使用责任链模式来处理错误。每个处理者可以根据错误的类型、严重程度等条件来决定是否能够处理该错误。如果一个处理者无法处理该错误,将错误传递给下一个处理者,直到找到能够处理的处理者或者到达链的末尾。

请求过滤和验证:在网络请求或者数据处理中,可以使用责任链模式对请求进行过滤和验证。每个处理者可以根据请求的特定条件进行过滤和验证操作,例如验证请求的合法性、验证用户权限等。

消息传递和通知处理:当需要将消息或者通知传递给多个对象时,可以使用责任链模式。每个处理者可以根据消息的内容或者类型来决定是否处理该消息,并进行相应的操作。

责任链模式具有以下优点:

解耦:责任链模式将发送者和接收者解耦,发送者不需要知道具体的接收者,而接收者也不需要知道请求的发送者,使系统的各个部分之间的耦合度降低。

灵活性,可扩展性:责任链模式允许动态地添加、修改或删除处理者,可以根据实际需求灵活地调整处理链的结构和顺序,而不会影响其他部分的代码。

可维护性每个处理者只关注自己的职责,使得代码更加模块化和可维护

责任链模式也有一些缺点

请求的处理不保证被接收:由于每个处理者都有可能处理或者传递请求,因此不能保证请求一定会被处理或者接收者会被找到。如果责任链没有被正确地构建或者配置,请求可能会被忽略。

性能影响:在责任链中,请求需要经过多个处理者的依次处理,可能会导致处理链较长,影响性能。

可能导致系统变复杂:如果责任链的设计不当或者链条过长,可能会导致代码变得复杂,降低代码的可读性和可维护性。

综上所述,责任链模式在解耦、灵活性和可扩展性方面具有优点,但需要注意处理顺序、性能和代码复杂性等问题。

二、观察者模式(Observer Pattern)

观察者模式用于对象之间的一对多依赖关系。在该模式中,一个对象(称为主题或可观察者)维护一系列观察者对象,使得当主题状态发生变化时,所有观察者都会自动收到通知并进行相应的更新。

而在iOS开发中,通过使用KVO(Key-Value Observing)机制NotificationCenter 实现观察者模式,可以实现对象之间的松耦合通信

例子

在iOS 开发中,观察者模式经常使用NotificationCenter 来实现事件和数据的通知机制。NotificationCenter 是一个全局的通知中心,允许不同的对象发布和接收通知。

假设我们有一个需求,当用户完成登录操作后,需要通知其他模块进行相应的更新。我们可以使用观察者模式来实现这个功能。

首先,在登录完成后,我们会发送一个通知:

然后,在其他模块中,我们可以注册对该通知的观察,以便在收到通知时执行相应的操作:

这种方式在iOS 开发中广泛应用,比如在数据更新时发送通知,让相关的UI 模块进行刷新,或者在应用程序状态发生变化时发送通知,让其他模块作出相应的处理。观察者模式的使用可以大大简化不同模块之间的交互和通信,提高代码的可维护性和可扩展性。

三、策略模式(Strategy Pattern)

策略模式通过定义不同的策略对象,并将其封装在具体的类中,可以实现在运行时动态地选择不同的算法或行为。

策略模式将算法独立于使用它的客户端进行封装,使得算法可以独立于客户端的变化而变化。这种模式通过定义一系列可互换的算法族,并将每个算法封装起来,使它们可以相互替换。

例子

算法选择:当需要根据不同的条件或用户输入选择不同的算法时,可以使用策略模式。例如,对于一个计算器应用程序,可以根据用户选择的运算符使用不同的算法来执行计算:

首先,我们定义一个策略接口(Strategy)来声明所有具体策略类都需要实现的方法:

然后,我们实现几个具体的策略类,它们实现了策略接口的方法:

接下来,我们定义一个上下文类(Calculator),它包含一个指向策略接口的引用,并提供一个方法供客户端设置具体的策略:

最后,使用:

运行上述代码,将会输出以下结果:

通过使用策略模式,我们可以根据用户选择的不同运算符,动态地切换不同的策略,从而实现不同的计算操作。这种模式使得算法的变化独立于客户端,并且提高了代码的灵活性和可维护性。

策略模式具有以下优点:

提供了更好的代码组织和结构:策略模式将每个具体策略类封装在单独的类中,使得代码结构清晰、可读性强。它将算法的实现与使用算法的客户端代码分离,降低了耦合性,使代码更易于维护和扩展。

提供了更高的灵活性和可扩展性:策略模式使得可以在运行时动态地切换不同的策略,而不需要修改客户端代码。新的策略类可以很容易地添加到系统中,从而增加了系统的灵活性和可扩展性。

使算法独立于客户端:策略模式将算法封装在策略类中,使得算法可以独立于客户端进行变化。客户端只需要知道如何选择和使用不同的策略,而无需了解具体策略的实现细节,提高了代码的抽象程度和可理解性。

促进了代码复用:通过定义通用的策略接口和多个具体策略类,可以促进代码的复用。不同的客户端可以共享相同的策略类,避免了代码的重复编写。

然而,策略模式也存在一些缺点:

增加了类的数量:引入策略模式会增加类的数量,特别是当策略较多时,可能会导致类的爆炸性增长,增加了代码的复杂性。

客户端需要了解不同的策略:虽然客户端代码与具体策略的实现分离,但客户端仍然需要了解不同策略的存在和选择。

策略的选择和切换开销:在运行时选择不同的策略会引入一定的开销。如果策略的选择频繁发生变化,可能会影响系统的性能。

四、模板方法模式(Template Method Pattern)

模板方法模式定义了一个操作中的算法的骨架将一些步骤延迟到子类中实现。模板方法允许子类在不改变算法结构的情况下重新定义算法的某些步骤。

在iOS开发中,通过定义一个抽象类或协议,并在其中定义一个模板方法,允许子类或遵循者实现特定的步骤,从而实现代码复用和统一的算法骨架

例子

使用:

在上述示例中,AbstractClass是基类,定义了模板方法templateMethod和两个抽象方法primitiveOperation1和primitiveOperation2。子类ConcreteClass1和ConcreteClass2继承了AbstractClass,并实现了抽象方法。在使用示例中,我们可以看到不同子类的具体实现步骤在模板方法中被调用。这样,模板方法提供了算法的骨架,而具体实现由子类完成。

在iOS开发中,模板方法模式常常用于以下场景:

UIViewController的生命周期方法:在iOS开发中,UIViewController 是一个常用的基类,用于管理视图控制器的生命周期和视图的显示。UIViewController 提供了一系列生命周期方法(例如viewDidLoad、viewWillAppear、viewWillDisappear等),开发者可以重写这些方法以添加自定义逻辑。这里的生命周期方法可以看作是模板方法,定义了视图控制器的整体行为骨架,而具体的实现可以由子类重写。

UITableView和UICollectionView的数据源方法:UITableView 和UICollectionView 是iOS 开发中常用的用于显示列表和网格的视图控件。它们使用数据源协议(UITableViewDataSource和UICollectionViewDataSource)来提供数据和配置单元格。这些数据源方法(例如numberOfSectionsInTableView、numberOfRowsInSection、cellForRowAtIndexPath等)可以看作是模板方法,定义了列表或网格的整体结构,而具体的实现由数据源对象提供。

在这些场景中,模板方法模式能够提供一个框架或者约定,使得基类或协议定义了整体行为的骨架,而具体的实现可以由子类或对象来提供。这种模式可以提高代码的可重用性和可维护性,同时也能够提供一致的编程接口和约束,方便开发者进行扩展和定制。

模板方法模式也有一些缺点:

限制了部分自由度:模板方法模式在定义算法骨架时,将一些步骤固定下来,子类只能重写或扩展指定的方法。

增加了类的个数:使用模板方法模式需要定义基类和多个子类,这样会增加类的个数和代码量。如果算法的变化较小或者只有少数几个子类需要不同的实现,使用模板方法模式可能会显得过于繁琐

难以控制子类的行为:在模板方法模式中,子类可以通过重写方法来实现特定步骤的定制,这也意味着子类可能会对算法的整体行为产生影响

五、命令模式(Command Pattern)

命令模式用于将请求(命令)封装成一个对象,从而允许根据不同的请求参数来参数化客户端对象。通过使用命令模式,可以将方法调用、请求或操作封装到单个对象中,使得我们可以将这些命令队列、记录日志、撤销操作或进行重做等。

在命令模式中,有四个核心组件

命令(Command):命令对象封装了对特定操作的请求。它通常包含执行操作的方法。

发送者(Invoker):发送者是一个对象,它知道如何触发命令来执行特定的操作。它将命令对象与接收者对象解耦

接收者(Receiver):接收者是实际执行操作的对象。命令模式通过将命令与接收者分离,使得可以独立地改变接收者或命令,而不需要修改发送者。

客户端(Client):客户端创建具体的命令对象并设置其接收者。

例子

首先,我们创建一个命令接口 Command,其中包含一个执行方法 execute:

接下来,我们实现接收者类 Receiver,其中包含执行实际操作的方法:

然后,我们实现具体的命令类 ConcreteCommand,它遵循命令接口,并将操作请求委托给接收者对象:

最后,我们实现发送者类 Invoker,它接受命令对象并调用其执行方法:

使用:

这个例子中,命令模式将一个特定的操作(performAction)封装在一个命令对象中。发送者(Invoker)通过持有命令对象,可以触发命令的执行,而无需直接与接收者(Receiver)进行交互。这种解耦允许我们灵活地替换命令或接收者,以满足不同的需求。

在iOS开发中,命令模式可以在以下几个常见场景中使用:

撤销和重做操作:命令模式可以用于实现撤销和重做操作。通过将操作封装成命令对象,可以将每个操作保存在一个历史记录中,并在需要时按照顺序执行或撤销。这在图形编辑器、文本编辑器等应用程序中非常有用。

动作菜单和工具栏:命令模式可以用于实现动作菜单和工具栏。每个菜单项或工具栏按钮可以关联一个命令对象,并在触发时执行相应的命令。这种方式使得用户界面的动作和操作与实际的命令对象解耦,使得用户界面更加灵活和可扩展。

异步任务队列:命令模式可以用于管理异步任务队列。每个命令对象可以代表一个需要执行的异步任务,将任务的执行封装在命令对象中。通过使用命令模式,可以实现任务队列的管理、优先级控制、任务取消等功能。

异步任务队列

命令模式管理异步任务队列,实现添加、删除和执行任务,可以实现任务的优先级控制,也可以支持任务的暂停。

六、解释器模式(Interpreter Pattern)

解释器模式(Interpreter Pattern)用于定义一种语言的文法规则,并且解释和执行该语言中的表达式。该模式将一个问题领域划分为一组类,每个类代表语言中的一个文法规则,而解释器则使用这些类来解释语言中的表达式。

解释器模式主要由以下几个角色组成:

抽象表达式(Abstract Expression):定义了一个抽象的解释方法 interpret,所有具体表达式都要实现该方法。

终结符表达式(Terminal Expression):实现了抽象表达式的解释方法,它代表语言中的终结符,不能再进行进一步解释。

非终结符表达式(Nonterminal Expression):实现了抽象表达式的解释方法,它代表语言中的非终结符,可以继续进行进一步解释。

上下文(Context):包含解释器解释的全局信息。

客户端(Client):创建并配置表达式的解释器,并调用解释方法解析语言中的表达式。

(事实上,解释器模式在iOS 开发中比较少用)

例子

假设我们有一个简单的算术表达式语言,可以计算加法和减法:

使用:

在上面的示例中,我们定义了抽象表达式 Expression,并实现了终结符表达式 NumberExpression 和非终结符表达式 AdditionExpression、SubtractionExpression。上下文 Context 包含了表达式解释所需的全局信息。在客户端代码中,我们创建了一个算术表达式,并使用上下文中的变量进行解释和计算,最后输出计算结果。

但是也可以看到,类型检查和类型转换非常繁琐,如果使用Swift 这样的语言,可能会得到改善,但事实上,在iOS 实际开发中,解释器模式确实比较少用到,它通常在需要构建一种特定语言的解释器或者需要解析复杂的表达式时使用。

七、迭代器模式(Iterator Pattern)

迭代器模式提供了一种访问集合对象元素的方法,而无需暴露集合的内部表示。通过使用迭代器模式,可以在不暴露集合内部结构的情况下,按顺序访问集合中的元素。

通常,可以使用迭代器模式来遍历集合对象(如数组、字典等)

例子

使用:

在上面的代码中,我们首先定义了迭代器接口(Iterator)和集合接口(Aggregate)。然后,我们实现了具体的迭代器类(ConcreteIterator),它维护了一个集合对象和当前元素的索引,实现了迭代器接口中的方法。接着,我们实现了具体的集合类(ConcreteAggregate),它实现了集合接口,并在createIterator方法中返回一个具体迭代器对象。最后,我们使用迭代器来遍历集合对象的元素。

使用迭代器模式的好处是,客户端代码可以通过统一的迭代器接口来访问不同类型的集合对象,而无需关心集合内部的实现细节。这样可以使代码更加灵活、可扩展,并且符合面向对象的设计原则。

八、中介者模式(Mediator Pattern)

中介者模式通过封装一系列对象之间的交互,将对象之间的通信转变为通过中介者进行的集中式通信。中介者模式的目标是减少对象之间的直接耦合,通过引入一个中介者对象,使得对象之间的交互更加灵活、可维护和可扩展

在中介者模式中,各个对象不再直接相互通信,而是通过中介者进行通信。当一个对象发生改变时,它不需要知道具体需要通知哪些对象,而是将消息发送给中介者,由中介者来处理通知其他相关对象。这样,对象之间的耦合度降低,它们只需要和中介者进行通信,而不需要了解其他对象的具体细节。

例子

现在有两个同事需要通过中介者通知:

使用:

在上述示例中,Mediator是中介者接口,定义了sendMessage:fromColleague:方法。ConcreteMediator是具体的中介者类,实现了中介者接口,并在sendMessage:fromColleague:方法中根据不同的发送者通知其他同事对象。

Colleague是抽象同事类,其中包含了中介者对象,并定义了send:和receiveMessage:方法。ConcreteColleague1和ConcreteColleague2是具体的同事类,继承自抽象同事类,分别实现了具体的发送和接收行为。

在使用示例中,创建了一个具体的中介者对象mediator,以及两个具体的同事对象colleague1和colleague2。通过设置中介者的同事对象,并通过调用同事对象的send:方法发送消息,触发中介者对象的消息传递过程。

当colleague1发送消息时,中介者对象mediator将消息传递给colleague2,colleague2收到消息后打印出来。同样地,当colleague2发送消息时,中介者对象mediator将消息传递给colleague1,colleague1收到消息后打印出来。

这样,通过中介者模式,对象之间的通信通过中介者进行集中处理,实现了对象之间的解耦。

在iOS开发中,中介者模式常常应用于以下场景:

视图控制器之间的通信:在iOS应用程序中,ViewController之间需要进行数据传递和交互。使用中介者模式可以将这些通信逻辑抽象到一个中介者对象中,视图控制器只需要与中介者进行通信,而无需直接依赖其他视图控制器。这样可以降低视图控制器之间的耦合度,使代码更加清晰和可维护。

多个模块之间的通信:在大型iOS应用程序中,通常由多个模块或组件组成,它们之间需要进行数据传递和交互。使用中介者模式可以引入一个中介者对象,用于集中处理模块之间的通信。 模块只需要与中介者进行通信,而无需了解其他模块的具体细节,从而实现模块之间的解耦和灵活性。

从上面的应用也可以看到,中介者模式有减少对象之间耦合性增加复用性和扩展性集中控制交互逻辑的优点。同时,中介者模式也可能存在中介对象随着通信的对象变多而变的复杂,臃肿,难以维护,还有增加系统的复杂性的缺点。

九、备忘录模式(Memento Pattern)

备忘录模式(Memento Pattern)用于在不破坏封装性的前提下捕获和恢复对象的内部状态。该模式通过创建一个备忘录对象来存储对象的状态,并将其保存在原始对象之外,以便在需要时恢复状态。

在备忘录模式中,通常涉及三个主要角色:

发起人(Originator):它是需要保存状态的对象。它可以创建一个备忘录来保存当前状态,也可以使用备忘录来恢复之前保存的状态

备忘录(Memento):它是保存发起人对象状态的对象。它通常具有能够获取发起人状态的方法,以及设置发起人状态的方法。

管理者(Caretaker):它负责保存和恢复备忘录对象。它通常会保存多个备忘录对象,并可以选择在适当的时候将其提供给发起人。

例子

使用:

在上述示例中,Originator代表发起人对象,它具有状态属性state。它通过createMemento方法创建一个备忘录对象并保存当前状态,通过restoreFromMemento方法从备忘录对象中恢复状态。

Memento表示备忘录对象,它具有一个state属性用于保存发起人的状态。

Caretaker充当管理者对象,它负责保存和恢复备忘录对象。它使用一个可变数组mementos来保存多个备忘录对象,并提供了addMemento和getMementoAtIndex方法来添加和获取备忘录。

在使用备忘录模式时,发起人可以创建备忘录对象并将其交给管理者保存。如果需要恢复之前的状态,可以从管理者那里获取相应的备忘录对象,并通过发起人对象恢复状态。这样可以在不破坏封装性的情况下实现状态的保存和恢复。

备忘录模式在iOS开发中常用于需要保存和恢复对象状态的场景,尤其是在撤销和恢复功能、数据持久化、界面状态管理以及游戏状态管理等方面。通过使用备忘录模式,可以更好地控制对象的状态,并提供灵活的状态管理机制。

可以看出,备忘录模式具有状态保存和恢复的分离,支持撤销和恢复的优点,但同时,备忘录模式也存在内存消耗过大会暴露对象原始属性的缺点。

十、状态模式(State Pattern)

状态模式(State Pattern)允许对象在内部状态改变时改变其行为,使其看起来像是修改了自身的类。状态模式通过将状态封装成独立的类,使得状态的变化不直接影响到对象的行为,从而实现了状态与行为的解耦。

在状态模式中,对象根据内部状态的不同而改变其行为。它包含以下主要角色:

Context(上下文):定义客户端感兴趣的接口,并维护一个具体状态类的实例,这个实例定义当前状态。

State(状态):定义一个接口,用于封装与Context的特定状态相关的行为。

ConcreteState(具体状态):每个具体状态类实现State接口,并实现与该状态相关的行为。

例子

假设我们正在构建一个音乐播放器应用,其中包含多个播放状态(暂停、播放、停止):

我们对于不同状态的变换,只需要简单使用MusicPlayer:

在上面的示例中,我们定义了一个音乐播放器应用的上下文类MusicPlayer和三个具体状态类PlayState、PauseState和StopState,它们实现了状态接口State。在MusicPlayer中,我们通过维护一个currentState实例变量来跟踪当前状态,并且在状态改变时更新它。客户端代码通过调用playMusic、pauseMusic和stopMusic等方法来操作音乐播放器,具体的行为由当前状态对象处理。

这样,当我们需要添加新的播放状态时,只需创建一个新的具体状态类并实现相应的行为方法,而不需要修改MusicPlayer类的代码,实现了状态和行为的解耦。

在iOS 实际开发中,状态模式适用于对象有多个状态且状态之间有复杂的转换逻辑的情况。(比如:视图控制器生命周期管理:视图控制器在其生命周期中经历多个状态,如viewDidLoad、viewWillAppear、viewDidAppear、viewWillDisappear等。可以使用状态模式来管理这些状态,并根据不同的状态执行相应的操作,以实现良好的生命周期管理)它可以使代码更加模块化、灵活和可扩展。但在简单的场景中,引入状态模式可能会过于复杂,不切实际

十一、访问者模式(Visitor Pattern)

访问者模式(Visitor Pattern允许我们在不修改对象结构的情况下定义对对象的新操作。该模式适用于需要对一个复杂对象结构中的各个元素进行不同操作的情况。

访问者模式基于两个核心概念:元素(Element访问者(Visitor)。元素是一个具体对象结构,它定义了接受访问者对象的方法。访问者是一个表示新操作的对象,它定义了访问元素的方法。通过将访问者对象传递给元素,元素可以将自身委托给访问者来执行特定的操作

例子

首先,我们定义元素接口 Element,包含一个接受访问者对象的方法 acceptVisitor::

接下来,我们定义访问者接口 Visitor,其中包含了针对不同元素的访问方法:

然后,我们定义两个具体元素类 ConcreteElementA 和 ConcreteElementB,它们实现了 Element 接口,并根据需要调用访问者的相应方法:

然后,我们实现具体的访问者类 ConcreteVisitor,其中实现了对两个具体元素的不同操作:

使用

当运行这段代码时,将会输出以下结果:

通过使用访问者模式,我们可以将元素和操作进行解耦,使得新增操作时不需要修改元素的代码。这提高了代码的可扩展性和可维护性。

需要注意的是,访问者模式在iOS开发中并不是一个经常使用的模式,它通常用于处理复杂的对象结构和操作。在简单的情况下,使用其他更简单的设计模式可能更加合适。使用访问者模式需要权衡代码的复杂性和可维护性,确保它能够带来实际的好处。


iOS 24种设计模式 - Swift Demo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容