对于部分设计模式的个人理解

作者: 忆辰念家 | 来源:发表于2019-10-11 12:39 被阅读0次

    定义

    设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。 使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。

    设计模式的六大原则

    • 单一职责原则(Single responsibility principle)
      应该有且仅有一个原因引起类的变更(There should never be more than one reason for a class to change)
      单一职责原则为我们提供了一个编写程序的准则,要求我们在编写类,抽象类,接口时,要使其功能职责单一纯碎,将导致其变更的因素缩减到最少。

    • 开闭原则(Open Close Principle)
      开闭原则要求我们要尽可能的通过保持原有代码不变添加新代码而不是通过修改已有的代码来实现软件产品的变化
      说的通俗一点就是,已经开发好的软件实体(如类、模块、函数),在升级迭代引入新功能时,不应该修改已有的代码,而是在已有代码的基础上,添加新代码来实现。

    • 里氏代换原则(Liskov Substitution Principle)
      任何使用基类的地方都可以在不了解其子类具体实现的情况下,无条件地使用其子类替换,而不会产生任何错误或异常。
      这要求子类可以拓展父类的功能,但不能改变父类原有的功能。包含以下4个方面:
      1、子类必须实现父类的抽象方法,但是不能重写父类的非抽象方法。
      2、子类可以增加自己特有的方法
      3、当子类重载父类的方法时,方法的形参要比父类更宽松
      4、当子类重载父类的方法时,方法的返回值要比父类更严格

    • 依赖倒转原则(Dependence Inversion Principle)
      高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。
      这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

    • 接口隔离原则(Interface Segregation Principle)
      客户端不应该依赖它不需要的接口(Clients should not be forced to depend upon interfaces that they don't use)
      类间的依赖关系应该建立在最小的接口上(The dependency of one class to another one should depend on the smallest possible interface)
      接口隔离原则要求我们在设计接口时,要使用多个专门的接口不要使用单一的庞大臃肿的总接口,一个类对另一个类的依赖性应该建立在最小的接口上。要做到接口与角色一一对应,不应该让一个接口承担多个角色,也不应该让一个角色由多个接口承担。这样设计的接口在应对未来变更时,会更具有灵活性和可拓展性。

    • 迪米特法则,又称最少知道原则(Demeter Principle)
      最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
      通俗的讲一 个类对自己需要耦合或者调用的类应该知道的最少,你类内部是怎么复杂、怎么的纠缠不清都和我没关系, 那是你的类内部的事情,我就知道你提供的这么多 public 方法,我就调用这个

    这些东西都是一下概念上的描述,理解起来比较困难,小伙伴们可以看看下面的文章,有助于大家更好的理解这些原则。
    设计模式原则之接口隔离原则
    设计模式心法
    偶my耶-设计模式

    设计模式的类型

    根据设计模式的参考书 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 中所提到的,总共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)结构型模式(Structural Patterns)行为型模式(Behavioral Patterns)

    创建型模式:
    这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
    主要用于创建对象。

    结构型模式:
    这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
    主要用于处理类或对象的组合。

    行为型模式
    这些设计模式特别关注对象之间的通信。
    主要用于描述对类或对象怎样交互和怎样分配职责。

    常用的设计模式

    本文将简单介绍在iOS开发中几种常用的设计模式:

    创建型模式

    工厂模式

    在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

    比如存在如下需求:
    系统需要提供两个画笔(铅笔、钢笔),根据业务需要使用其中一种来绘制页面。
    根据上述需求,我们可以创建两个类 Pencil 和 FountainPen,然后他们俩各自有自己的方法 draw。
    到这里,我们的工作其实已经结束,开发者只需在使用的地方进行判断,初始化相应的对象来调用 draw 即可。

    这样的话会存在什么样的问题呢,比如项目中多个对方存在对这个两个对象的初始化,假如有一天,对于这两个对象的创建存在一些逻辑上的限制,就需要修改多个地方。利用工厂模式对将创建对象都放在一个地方的话,将来进行修改就比较方便。
    在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

    那么对于上述需求该怎么设计呢:
    首先 Pencil 和 FountainPen 肯定是实现了同一个协议(PenProtocol 协议提供方法 func draw())的,然后我们为 Pencil 和 FountainPen 分别创建一个工厂类 PencilFactory 和 FountainPenFactory,这两个工厂类也是实现了同一个协议 (PenFactoryProtocol 协议提供方法 static func createPen() -> PenProtocol),这样我们就可以在需要的时候,来使用相应的工厂,创建出我们需要的对象来进行绘制了。

    上面一堆文字不太好看懂,来张图看看:


    简单工厂类图

    图也看不懂,没关系,上代码:

    两个协议

    protocol PenProtocol {
        
        //绘制的方法
        func draw()
    }
    
    protocol PenFactoryProtocol {
        
        //生产笔的方法
        static func createPen() -> PenProtocol
    }
    

    两种产品

    import UIKit
    
    class Pencil: NSObject {
    
    }
    
    extension Pencil:PenProtocol{
        func draw() {
            print("Pencil" + "绘制")
        }
    }
    
    import UIKit
    
    class FountainPen: NSObject {
    
    }
    
    extension FountainPen:PenProtocol{
        func draw() {
            print("FountainPen" + "绘制")
        }
    }
    

    两个产品对应两个工厂

    import UIKit
    
    class PencilFactory: NSObject {
    
    }
    
    extension PencilFactory:PenFactoryProtocol{
        class func createPen() -> PenProtocol {
            return Pencil()
        }
    }
    
    import UIKit
    
    class FountainPenFactory: NSObject {
    
    }
    
    extension FountainPenFactory:PenFactoryProtocol{
        class func createPen() -> PenProtocol {
            return FountainPen()
        }
    }
    

    调用

    print("工厂模式")
    //工厂模式使用(需要什么对象,就用对应的工厂类来创造)
    PencilFactory.createPen().draw()
    
    //工厂模式使用
    FountainPenFactory.createPen().draw()
    

    工厂模式的结构就是上面这种的,总体思想就是一个工厂生产一个类的对象。

    下面是一个在iOS中使用的实例:
    iOS - TableViewCell(CollectionViewCell)的工厂设计模式

    在创建型模式模式中,还存在两个和工厂模式类似的模式,就是 简单工厂模式抽象工厂模式。本文就不做介绍了,以后有时间可以另外补充文章。

    单例模式

    单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

    单例模式的要点有三个:
    一是某个类只能有一个实例;
    二是它必须自行创建这个实例;
    三是它必须自行向整个系统提供这个实例。

    使用场景:

    • 需要频繁的进行创建和销毁的对象;
    • 创建对象时耗时过多或耗费资源过多,但又经常用到的对象;
    • 工具类对象;
    • 频繁访问数据库或文件的对象。

    类图 :


    单例模式类图.png

    代码:

    import UIKit
    
    class SingleObject: NSObject {
        @objc static let shared = SingleObject.init()
        
        private override init() {
            super.init()
            
        }
    }
    

    调用

    print("单例模式")
    print(SingleObject.shared)
    print(SingleObject.shared)
    

    这里给到的是 swift 的写法,比较简单粗暴,关于单例在 oc 中的实现,可以看下面的文章。

    相关文章
    iOS 单例模式
    iOS-单例模式写一次就够了

    结构型模式

    代理模式

    这个模式不说啦,我是越研究越懵逼。
    主要在纠结 iOS 中的代理、委托 到底是不是基于这种设计模式的。
    真的整不明白啦,就不多说啦,免得误导大家,等哪天能力提升,搞清楚啦,再来补充这里一块儿。

    贴几个文章大家有兴趣的可以看看。
    设计模式---代理模式

    设计模式:代理(Proxy)模式

    iOS设计模式之代理模式

    你真的了解iOS代理设计模式吗?

    【java设计模式】(3)---代理模式(案例解析)

    行为型模式

    观察者模式

    当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。

    意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

    主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

    何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

    举例说明,在一场短跑比赛上,裁判员的枪声一响,所有运动员都要极速冲出去。在这里运动员就是观察者,裁判员就是目标对象。

    使用场景:

    • 对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变。
    • 对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节。

    现在就使用上面提到的运动员和裁判员作为业务需求,看看代码应该怎么实现。

    类图 :


    观察者模式.png

    代码:

    首先需要一个抽象的观察者,这类观察者就观察发号枪的枪声

    protocol Observer {
        
        //观察 发号枪 枪响
        func acceptGunshot()
    }
    

    具体的实例(运动员),来扩展 Observer 类,从而在观察到 acceptGunshot 触发时,就赶紧冲出去。

    import UIKit
    
    class Sportsman: NSObject {
        
        //跑步
        fileprivate func run(){
            print("\(self):快跑")
        }
    }
    
    extension Sportsman: Observer{
        func acceptGunshot() {
            run()
        }
    }
    

    被观察者(裁判员),需要给外部提供一个成为自己观察者的方法,然后在特定的时间点,来通知观察者。

    import UIKit
    
    class Judgment: NSObject {
        //观察者
        fileprivate var observers:[Observer] = []
        
        //添加观察者
        public func addObserver(observer:Observer){
            observers.append(observer)
        }
        
        //开枪
        public func gunshot(){
            print("biubiubiu")
            
            //通知所有观察者
            notifyAllObservers()
        }
        
        fileprivate func notifyAllObservers(){
            for item in observers {
                item.acceptGunshot()
            }
        }
    }
    

    调用

    print("观察者模式")
    //裁判就位
    let judgment:Judgment = Judgment.init()
            
    //三个运动员就位
    let sportsman1:Sportsman = Sportsman()
    let sportsman2:Sportsman = Sportsman()
    let sportsman3:Sportsman = Sportsman()
            
    //都要听裁判的发令枪
    judgment.addObserver(observer: sportsman1)
    judgment.addObserver(observer: sportsman2)
    judgment.addObserver(observer: sportsman3)
            
    //开炮
    judgment.gunshot()
    

    观察者模式在iOS中经典的应用场景就是 Notification、KVO。

    有兴趣的同学可以看看下面这篇文章:
    iOS Notification实现原理

    策略模式

    在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

    意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。

    主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。

    使用场景: 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

    现在产品又来了一个这样的需求,要设计计算类,这个类需要支持加法运算和减法运算。多数人的处理方案应该就是以下两种:
    1、写一个运算的方法,通过传递参数进行不同的运算。
    2、写两个方法,来处理不同的运算。
    这样的话如果哪天需求变更,比如要添加一个乘法运算和除法运算,这时候用上面的方法就要修改原有的类,这样就不太优雅。使用策略模式改怎么处理呢?

    类图:


    策略模式类图.png

    代码:
    为了扩展,肯定有个策略的接口,方便将来策略的具体实现。

    protocol OperationStrategy {
        
        /// 运算处理
        ///
        /// - Parameters:
        ///   - num1: 数字1
        ///   - num2: 数字2
        /// - Returns: 返回值
        func doOperation(num1:Int, num2:Int) -> Int
    }
    

    然后就是各个策略的具体实现喽

    class AddOperation: NSObject {
    
    }
    
    extension AddOperation:OperationStrategy{
        func doOperation(num1: Int, num2: Int) -> Int {
            return num1 + num2
        }
    }
    
    class SubstractOperation: NSObject {
    
    }
    
    extension SubstractOperation:OperationStrategy{
        func doOperation(num1: Int, num2: Int) -> Int {
            return num1 - num2
        }
    }
    

    所有准备做好就需要执行计算的管理类了

    class OperationManager: NSObject {
        
        /// 运算
        ///
        /// - Parameters:
        ///   - num1: 数字1
        ///   - num2: 数字2
        ///   - strategy: 运算策略
        /// - Returns: 运算结果
        public class func operation(num1:Int, num2:Int, strategy:OperationStrategy) -> Int{
            return strategy.doOperation(num1: num1, num2: num2)
        }
    }
    

    使用

    print("策略模式")
    //根据需要,选择相应的运算方式就好喽
    let result = OperationManager.operation(num1: 1, num2: 2, strategy: AddOperation())
    print("\(result)")
    
    let result = OperationManager.operation(num1: 2, num2: 1, strategy: SubstractOperation())
    print("\(result)")
    

    都这里我们就完成了最初的需求,将来需要添加不同的计算方式的时候,添加具体策略的实现就好啦。就不存在改动已有代码的问题喽。

    上面的几种设计模式看完,可能会脑子里有点迷糊,肯能会搞不清楚两个设计模式之间的区别。

    下面是我总结的我比较迷惑的几个设计模式之间的区别:

    工厂模式vs策略模式
    工厂:注重点是生产不同的对象。
    策略:注重点是得到不同的方法。

    比如上面的计算器的功能,网上有个解决方案就是使用工厂模式。具体实现就是为每一种运算方法提供一个类。

    策略模式vs观察者模式
    策略:注重其他类为自己提供某一功能的具体实现。
    观察者:自身变化对其他类的通知。

    这篇文章到这来就结束啦,小伙伴也许都是一脸懵逼,其实在项目开发中大家如果谨记面向对象的特点:继承、封装、多态。谨记自己写出的代码要高内聚、低耦合、易扩展。能够想方设法、多费点精力使敲出的代码符合这几点要求,就在自己无意识的情况下使用了某些设计模式。
    另外就是需要多去阅读一些知名轮子的源码,多些代码,多些经过脑子的代码。

    设计模式还有很多,网上也还有挺多文章的。这里再给大家贴个链接,需要的同学可以看看。
    设计模式

    相关文章

      网友评论

        本文标题:对于部分设计模式的个人理解

        本文链接:https://www.haomeiwen.com/subject/msfmyctx.html