设计模式心经

  • 类的可预见性很重要,尽量不要动态属性(特别是全局公用的单例)
  • 封装变化点是面向对象编程的一个重要点,封装变化就是从大量的事实中找到相同的规律,并用一个统一的模型来解释这些事实
  • 策略模式:封装了变化
  • 策略模式解除了强制编码到父类
  • 封装(继承:封装的一种特性)可以实现复用
  • 属性是属于对象管理的:虽然子类公用的代码存在于父类,但是子类实例化对象后,每个对象都有各自的完整属性库
  • 继承的细节体现是:模板方法模式(父类是子类的模板,所有重复的代码都应该上升到父类)。要完成某一细节层次一致的一个过程或者一系列步骤,但是个别步骤在更详细的层次上的实现不同时,就可以提炼出一个虚方法封装这个不同的个别步骤
  • 模板方法将一些步骤延迟到子类
  • 父类/抽象类/接口是抽象模板,给出的是一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中(方法中),推迟到子类实现(覆写)
  • 责任分离原则:如果能想到有多个动机改动一个类,那这个类就有多于一个的职责,就应该考虑类的职责分离
  • 解耦:就是让彼此的影响越少越好(让客户端认识的类越少越好),设计模式主要目标之一
  • 解耦一般就是,就算需求变了也要让客户端尽量的不改动。让客户端依赖于系统(或者某个包/组件)的抽象类而不是具体的功能类
  • 开放封闭原则:面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的。这就需要构造抽象来隔离变化
  • 针对抽象不是具体(依赖倒转)编程的前提是里氏替换原则(一个软件实体如果使用一个父类的话,那么一定适用于其子类。也即是可以互相替换,程序的行为没有变化)
  • 依赖倒转就是谁也不约定谁,除了约定好的接口(主要是转变为下层依赖上层定义好的接口)。在传统软件设计中,上层代码依赖下层代码,当下层出现变动时,上层代码也要相应变化,维护成本较高
  • 面向对象编程的标志:程序中所有的依赖都是终止于抽象类或者接口,那就是面向对象的设计,反之就是过程化的设计了
  • 在父类中添加新功能,子类也就具有这些功能(或者在某级子类添加功能,此级下面的子类也就具有该功能)。但这违背了开放-封闭原则。装饰器模式,在水平上,装饰、拓展了类的功能(让核心功能和装饰功能区分开来)。装饰器的父类应该继承于要装饰的对象的类(装饰一个对象)或者要装饰对象的接口(装饰一组对象即一类)
  • 装饰器模式适用于:
  1. 要动态地给一个对象添加功能,这些功能可以动态撤销
  2. 需要增加由一些基本功能的排列组合而产生非常大量功能时,从而使继承关系变的不现实
  3. 当不能采用生成子类的方法进行扩充时。比如:类的定义被隐藏
  • 代理模式,代理就是真实对象的代表(一个对象不适用或者不能直接引用另一个对象)。可以让一个对象有多种展示(可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外内容和服务)(有一点装饰器模式的作用)
  1. 代理模式的重点就是代理。也就是表示,给某一个对象提供一个代理,并由代理对象控制对原对象的引用
  2. 隐藏实体的具体操作细节。实体就是实际的业务逻辑,不用关心其他非本职责的事务
  3. 适用场景之一:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建
  4. RealSubject和Proxy公用一个接口Subject
  • 工厂模式:调用自身静态方法来生产对象实例
  • 工厂方法模式,因为简单工厂会与分支耦合(添加新功能就违背了开放-封闭原则),根据依赖倒置,就把工厂抽象出一个接口。然后所有要生产具体类的工厂都去实现这个接口。工厂方法模式是简单工厂的进一步抽象与推广,让子类决定实例化哪个类
  • 抽象工厂:提供了一个创建一系列相关或相互依赖对象的interface(就是interface提供了多个方法,用来创建不同的对象)
  • 变化点->要封装->简单工厂帮忙选择实例化哪个类->简单工厂升级为工厂方法->抽象工厂->原型模式
  • 简单工厂是系统选择实例化哪类对象不过有if条件分支语句;而工厂方法/抽象工厂是让客户端去选择使用哪个工厂,从而实例化哪类对象;用反射加简单工厂就可以去除if条件分支语句了,反射让程序从编译时变为运行时(PHP是支持运行态的)
  • 动态实例化对象,即是:需要实例化的对象实在程序运行时(run-time)动态决定(由变量决定)需要实例化什么的对象,而不是直接写死在代码里
  • eval()、create_function()、call_user_func_array()
  • 反射类是有方法可以获取构造函数的参数列表
  • 工厂模式对于增加新的产品,无能为力;工厂方法模式,支持增加任意产品;抽象工厂,支持产品族
  • 工厂模式的好处在于,客户端不用改代码,我们可以随时替换了实际工作的类
  • 一般初始化信息不变的情况下,克隆比实例化好,节约资源对性能有提高(克隆有坑,注意嵌套引用)(并且嵌套引用有可能出现循环引用造成内存泄漏)。所以应运而生出来了原型模式(关键:有个clone()方法)
  • 迪米特法则:如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果一个类需要调用另一个类的某一个方法的话,可以通过第三者来管理,转发这个调用
  • 迪米特法则首先强调的前提是在类的结构设计上,每一个类都应当尽量降低成员的访问权限。类之间的耦合越弱,越有利于复用。信息的隐藏促进了软件的复用(跟前几个原则比,侧重点在于细节,在于类的内部。当然在宏观上也有实现,比如:外观模式)
  • 外观模式:为子系统(一组接口)定义了一个高层接口,提供一个一致的界面,这个接口使得子系统更加容易使用(将用户的请求代理给适当的子系统处理)
  • 当使用子系统的代码时,也许会发现自己过于深入地调用子系统的逻辑代码。如果子系统总是在不断变化,而你的代码又在许多地方与子系统代码交互。此时,适合使用外观模式Facade
  • 对一个遗留的大型系统时,可能系统非常难以维护和扩展,但因为它包含非常重要的功能,新的需求开发必须要依赖于它。此时用外观模式Facade也是非常合适的
  • 外观模式和代理模式有点类似
  • 建造者模式:创建对象的一种模式,创建一些复杂的对象(不好再抽象到具体细节,只能抽象到步骤),这些对象内部的构造顺序(或者某些步骤)是稳定的,但每个对象有各自复杂的变化。Director指挥者类就是步骤类,Builder就是建造每个复杂变化对象类的抽象类
  • 建造者模式比工厂方法模式更加细节;其次,建造者模式比工厂模式多了个“导演类”角色
  • 建造者模式适用于:
  1. 对象的生成需要复杂的初始化
  2. 对象生成时,可以根据初始化的顺序或者数据不同,而生成不同的角色
  • 观察者模式:当运用组合去构建一个系统时,系统被分割为一系列相互协作的类。有一个不好的副作用,就是维护相关对象间的一致性。但不能为了一致性而产生耦合,此时可以使用观察者模式(也是彼此依赖于抽象)。观察者模式的好处在于一个对象的改变不需要具体知道有多少对象需要改变(对主题感兴趣的观察者,可以搬着小板凳聚过来)。有个细节就是,可能观察者的更新方式(及方法)各不相同,此时可以使用委托(.net的挂载)来解决
  1. 要注意,观察者和被观察对象之间的互动关系不能体现成类之间的直接调用,否则就使观察者和被观察者之间紧密地耦合起来了,从根本上违反了面向对象的设计原则
  2. 应该是观察者实例本身的方法接收消息并通知观察者本身对应处理事件的方法
  3. 观察者模式实现了低耦合,非侵入式的通知和更新机制
  4. 从面向过程的角度来看,首先是观察者向主题注册,注册完之后,主题再通知观察者做出相应操作,整个事情就完了
  5. 从面向对象的角度来看,主题提供注册和通知的接口,观察者提供自身操作的接口。观察者利用主题的接口向主题注册,而主题利用观察者的接口通知观察者。耦合度相当之低
  6. 适用于:当一个对象的状态发生改变时,依赖它的对象会全部收到通知,并自动更新
  • 状态模式:当一个对象对外的表现形式自己内部状态变化时。并且状态转换的条件表达式过于复杂时,把状态的判断逻辑以及对于的操作转移到表示不同状态的一系列类中
  1. 状态模式和观察者模式都可以解决判断条件过多的情况,只是情况的类别不同(状态模式是当状态分支条件针对自身表现形式时,而观察者模式是自身状态对其他对象的表现形式造成影响时)
  2. 状态模式有个Context环境类(保存有当前的状态),用来传递给具体State类
  • 适配器模式:希望复用已存在的类,但是接口又与复用环境不一致,此时可以使用适配器模式。尽量在设计初期就把功能相似的类设计好统一的接口(主要是方法名)。对接第三方时,就是使用适配器模式的好时候
  1. 适配器Adapter要完成的功能很明确,引用现有接口的方法来实现新的接口的方法,使原本不兼容的而不能在一起工作的那些类可以一起工作
  2. 适配器的使用场景:
  3. 想使用一个已经存在的类,而它的接口不符合你的要求
  4. 想使用一个已经存在的子类,但是不可能对每一个都进行子类实例化以匹配它们的接口(此时,适配器可以用一个数组属性保存所有需要适配的子类,而此时对象适配器可以适配它的父类的接口)
  • 要让调用者与被调用者之间的联系越少越好(自己只负责自己的分内之事),比如只知道对方的类名或者某个方法名,还有根本不需要知道的,就用代理、门面等模式。彼此知道的越少,修改彼此内部结构时,对彼此的影响最小
  • 备忘录模式:这个模式,说明了把对象的属性状态,用另一个类对象存起来的好处,可以具有"撤销"功能。适用于保存对象部分数据,若保存对象全部数据,还是clone比较好(数据存在对象或者普通变量中,就是存在内存中)
  1. 不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存状态
  2. 模式中的角色:Originator(发起人)(负责创建一个备忘录Memento,用以记录当前时刻自身的状态,并可使用备忘录恢复内部状态)、Memento(备忘录)、Caretaker(管理者)
  3. 如果只有一个备份,那么我们也可以不用备份管理者,而备份管理者存在的好处,当然是管理多个备份
  • 组合模式:需要体现部分与整体的层次结构时,并且客户端可以忽略组合对象和单个对象的不同,统一使用组合结构中的所有对象时,就可以考虑使用该模式
  1. 用作把一组相似的对象当作一个单一的对象。组合模式使得用户对单个对象和组合对象的使用具有一致性
  2. 将对象组合成树形结构以表示“部分-整体”的层次结构。模糊了简单元素和复杂元素的概念,客户端可以像处理简单元素一样来处理复杂元素,从而使客户端和复杂元素的内部结构解耦
  3. 组合模式的特点:1.存在不可分割的基本元素,2.组合后的物体依然可以被组合
  • 迭代器模式:抽象出一个迭代器类来专门负责遍历集合对象。这样既可以做到不暴露集合的内部结构,又可以让外部代码透明地访问集合内部的数据
  1. 如果把集合对象和对集合对象的(遍历)操作放在一起,当我们想换一种方式遍历集合对象中元素时,就需要修改集合对象了,违背了“单一职责原则”
  • 所有类的构造方法,不编码则系统默认生成空的构造方法,若有显示定义的构造方法,默认的构造方法就会失效
  • 单例模式:让类自身负责保存它的唯一实例,在多线程的语言中要用锁来确保只有一个实例,php单线程每次请求都是一个新的生命周期,所以不用加锁(有一种叫静态初始化的方法,即在类一加载就实例化,因为只会加载一次也就实现了单例,也适用与多线程)
  1. 三私一公也无法阻止得到一个新的对象,可以通过序列化和反序列化得到一个对象
  • 面向对象的原则:要优先使用用合成/聚合复用原则,其次在考虑使用继承(只用继承会使体系过于庞大)。学会用职责而不是结构来考虑
  • 拿到一个系统或者包,应当首先看,谁合成/聚合成谁
  • 组合和聚合的区别在于:组合整体和部分的生命周期一致,比如A类的构造方法里创建B类的对象,也就是,当A类的一个对象产生时,B类的对象随之产生,当A类的这个对象消亡,B类的对象也随之消亡。聚合是A类的对象在创建时不会立即创建B类的对象,而是等待一个外界的对象传给它,传给它的这个对象不是A类创建的
  • 合成/聚合的好处,也是可以对象与对象之间可以产生笛卡尔积的多功能对象
  • 优先使用对象的合成/聚合将有助于你保持每个类被封装,并集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物
  • 聚合也是一种模式,桥接模式:实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让他们独立变化,减少他们之间的耦合(抽象的就是多角度)(比如要准备三种粗细(大中小),并且有五种颜色的毛笔)
  1. 桥接模式适用于一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态解耦;不希望在抽象和它的实现部分之间有一个固定的绑定关系
  2. 桥接模式将继承关系转换为组合关系,降低了系统间的耦合,减少了代码编写量
  3. 桥接模式和装饰器模式在一定程度上都可以减少子类的数目,避免出现复杂的继承关系。但他们的解决方式不同,装饰器模式是由基类和装饰器类两大类组成,装饰器类通过组合可以实现很多的功能组合;桥接模式使用“对象间的组合关系”解耦了抽象和实现之间的固有绑定关系,使得抽象和实现可以沿着各自的维度来变化
  • 命令模式:客户端Client(实例化具体命令对象,以及确定其接收者),调用者Invoker(调用命令,相当于Command的leader,在这里可以记录日志,可以撤销命令等),接受者Receiver(命令类的参数,操作的实际执行者)
  1. 命令模式就是过程语言中的回调函数这种参数化机制。所谓回调函数是指函数先在某处注册,而它将在稍后某个需要的时候被调用。Command模式是回调机制的一个面向对象的替代品:抽象出待执行的动作(Receiver对象)以参数化某对象(Command对象)
  2. 命令模式好处:对命令进行封装,把请求一个操作的对象(Invoker)与知道怎么执行一个操作的对象(Receiver)分割开;可以实现命令队列 
  3. 降低系统耦合性
  • 职责链模式:当客户端提交一个请求时,请求是沿链传递直至有一个ConcreteHandler对象负责处理它
  1. 职责链模式包含一些命令对象和一些处理对象,每个处理对象决定它能够决定它能处理哪些命令对象,它也知道应该把自己不能处理的命令对象交下一个处理对象
  2. 职责链模式最重要的两点:一个是需要事先给每个具体管理者设置他的上司类。其二是在每个具体具体管理者处理请求时,做出判断,是可以处理这个请求,还是转移给后继者去处理
  • 状态模式和职责链模式,都可以沿对象链找到处理当前情形的对象
  • 需不需要抽象类,就看需不需要抽象(要是只有一个子类没有其他拓展完全不需要抽象类)或者另一个角度是未来是否有拓展
  • 中介者模式:用一个中介对象来封装一系列的对象交互。中介者使得各对象不需要显示地相互引用,从而使其耦合松散,而且(中介者)可以独立地改变他们之间的交互
  1. 中介者对象需要知道所有的具体同事类,并从具体同事接收消息,向具体同事对象发出命令
  2. 对象间如何协作进行了抽象,并封装在一个独立的概念(一个中介者对象)中,当然交互的复杂性也就变成了中介者的复杂性
  3. 使用中介者模式好处:一,中转作用(结构性),通过中介者提供的中转作用,各个同事对象就不再需要显示引用其他同事,当需要和其他同事进行通信时,通过中介者即可。二:协调作用(行为性),中介者可以更进一步的对同事之间的关系进行封装,同事可以一致地和中介者进行交互,而不需要指明中介者需要具体怎么做,中介者根据封装在自身内部的协调逻辑,对同事的请求进行进一步处理,将同事成员之间的关系行为进行分离和封装
  • 享元模式:大幅度减少需要实例化的类的数量。在程序设计中,有时需要生产大量细粒度的类实例来表示数据,如果发现这些实例除了参数外基本相同,就可以把那些参数移到类实例外面,在方法调用时将他们传递进来,就可以通过共享大幅度减少单个实例的数目(享元对象中有个状态数组,存着各种状态以供展示)
  1. 享元模式可以配合延迟加载一起使用
  2. 享元模式只有当有足够多的对象实例可供共享时,才值得使用
  • 解释器模式:当有一个语言需要解释执行,并且你可将该语言中的句子表现为一个抽象语法树时,可以使用该模式
  1. 存在一个环境角色类,定义解释规则的全局信息
  2. php模版引擎就是解释器模式的一个应用
  • 访问者模式:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作(即增加Visitor子类)
  • 访问者模式是把作用于结构之上的操作从数据结构中解耦解脱开来,适用于数据结构(即Element对象个数确定)相对稳定(如果Element对象的个数不稳定,那么数据的类别就不确定,那么抽象访问者类的抽象方法就不确定,不符合封闭-开放原则),算法易于变化的系统(算法集中在了Visitor子类)
  • 访问者模式中用到双分派即两次分派,Visitor与Element都有各自的子类,先选Visitor再选Element,Visitor中为对象结构中具体的每一个ConcreteElement类声明了一个Visit操作
  • 访问者模式适用于:
  1. 一个对象结构包含很对类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作
  2. 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor模式使你可以将相关的操作集中起来定义在一个类中
  3. 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作
  • 泛化关系:能用父类实例的地方就能用子类的实例,因为父类的属性、操作子类应该都有
  • 依赖关系:假设A类的变化引起了B类的变化,则称B依赖于A。表现在代码上依赖关系一般有如下三种情况:
  1. A类是B类中(某个方法)的局部变量(不是属性)
  2. A类是B类中某个方法的参数
  3. A类向B类发送消息,从而影响B类变化
  • 关联关系:一个对象中的属性是另一个对象
  • 组合和聚合的区别:组合的对象是在组合容器中生成的(new),聚合是直接传给容器生成好的对象
  • 抽象、封装,然后组合,互相依赖于彼此(组合/聚合,有相互设置依赖的setter)的接口产生委托。形成各种模式
  • 抽象类是接口的细分(表现形式的区别:抽象类有子类的公有实现),也就是再抽象,接口是类的leader
  • 对于任何一款面向对象的编程语言,接口都是存在并且有很大作用的。因为一个对象都是由他的模版或者原型---类经过实例化后的一个具体事物。有时候,要实现统一方法但具有不同功能或特性的时候,需要很多类,这个时候就需要一个契约,让大家编写出可以随时替换但又不会产生影响的接口,这种由编程语言本身提出的硬性规范,会增加更多优秀的特性
  • 继承服务于组合
  • 把需要的参数用类封装,可以减少耦合,好维护(比如service层)
  • 由外部负责依赖需求的行为,我们称之为"控制反转(IoC)"(工厂模式及其扩展模式都是属于"IoC")(控制反转也就是依赖注入)
  • 工厂模式的缺点:接口未知(即没有一个很好的契约模型)、产生的对象类型单一。总之就是,还是不够灵活
  • 服务容器是工厂的升级版,彻底的解除了 超人 与 超能力模组 的依赖关系,更重要的是,容器类也丝毫没有和他们产生任何依赖。再配合反射自动搜寻依赖
  • 服务容器解释为一个用于管理类依赖和执行依赖注入的强大工具
  • 一个类要被容器所能够提取,必须要先注册至这个容器
  • 我们可以向容器注册一些生产脚本,这些生产脚本在生成指令下达之时便会执行。例如,我们可以向容器注册、绑定一段可以被执行的回调(可以是匿名函数、非匿名函数、类的方法)作为生产一个类的实例的脚本,只有在真正的生产(make)操作被调用时,才会触发。这样一种方式,使得我们更容易在创建一个实例的同时解决其依赖关系,并且更加灵活
  • Long Method有股坏味道
  • 常用操作逻辑,递归or循环or沿链
  • 面向对象封装了变化的边界,从而更容易将代码与变化的影响隔离开来
  • 设计模式分为:创建型、结构型、行为型
  • 设计模式的原则就是对面向对象的抽象,封装,多态的灵活运用
  • 软件开发的目标:内部完整,也就是高聚合,而与其他例程之间的联系则是小巧、直接、可见、灵活的,这就是松耦合
  1. 设计模式最重要的就是:单一原则和抽象
  2. 某项功能就封装成方法,某一类功能就封装成类
  • 强类型语言,在编译时,就确定了变量类型;而PHP这种动态类型语言,在运行时,才确定变量类型。PHP的类型约束,具有强类型语言的特质。但是,强类型语言,不只是函数参数,有约定的类型;函数的返回值,以及变量等都有约定的类型。强类型语言,可以定义接口,来抽象这些约定。PHP中的trait,灵活程度有点类似其他语言的duck typing。比传统的继承,灵活,方便
  • 磁盘 + CPU->操作系统->开发语言(因为社会的进步和需求,而产生)->实用软件工具(服务端部分+客户端部分)->框架(会支持很多流行实用软件,开箱即用,仅修改配置就OK)->包

设计模式再抽象,就是架构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值