分类目录归档:设计模式

UML类图 —— 基本概念

这只是一个简单的UML类图入门,介绍一些基本概念。

1、 类

一般类分为3层:

  • 第一层: 类名
  • 第二层: 属性
  • 第三层: 方法

属性和方法都有各自的修饰:

  • + 表示 公共
  • # 表示 受保护
  • - 表示 私有
  • 下划线 表示 静态
  • 斜体 表示 抽象

2、 接口

接口一样分三层。

不过一般属性层不会去放东西。因为将常量变量放在 interface 中违背了其作为接口的作用而存在的宗旨,也混淆了 interface 与类的不同价值。

3、关系

3.1、 继承关系/泛化关系

继承关系为 is-a的关系;两个对象之间如果可以用 is-a 来表示,就是继承关系:xx是yy

比如:自行车是车、猫是动物

泛化关系用一条带空心箭头的直接表示。

3.2、 实现关系

实现关系用一条带空心箭头的虚线表示;

比如:”车”为一个抽象概念,在现实中并无法直接用来定义对象;只有指明具体的子类(汽车还是自行车),才 可以用来定义对象。

3.3、 聚合关系

聚合关系用一条带空心菱形箭头的直线表示。

聚合关系用于表示实体对象之间的关系,表示整体由部分构成的语义;例如一个部门由多个员工组成;

与组合关系不同的是,整体和部分不是强依赖的,即使整体不存在了,部分仍然存在;例如, 部门撤销了,人员不会消失,他们依然存在;

3.4、 组合关系

组合关系用一条带实心菱形箭头直线表示。

与聚合关系一样,组合关系同样表示整体由部分构成的语义;比如公司由多个部门组成;

但组合关系是一种强依赖的特殊聚合关系,如果整体不存在了,则部分也不存在了;例如, 公司不存在了,部门也将不存在了;

3.5、 关联关系

关联关系是用一条直线表示的。

它描述不同类的对象之间的结构关系。它是一种静态关系, 通常与运行状态无关,一般由常识等因素决定的。它一般用来定义对象之间静态的、天然的结构。 所以,关联关系是一种“强关联”的关系。

3.6、 依赖关系

依赖关系是用一套带箭头的虚线表示的。

他描述一个对象在运行期间会用到另一个对象的关系。与关联关系不同的是,它是一种临时性的关系,通常在运行期间产生,并且随着运行时的变化。依赖关系也可能发生变化。

在程序中,我们要多用依赖关系,少用关联关系。

参考自:

http://blog.csdn.net/sfdev/article/details/3906243
http://design-patterns.readthedocs.io/zh_CN/latest/read_uml.html

仓库模式 —— Repository Pattern

在上一篇文章《laravel/lumen —— 面向接口编程》中,举了个RedisConfig的栗子,涉及到了仓库模式这个概念,但是还不够深入。现在特别再写一篇来讲述仓库模式的使用方式以及优势。

Repository 模式主要思想是建立一个数据操作代理层,把controller里的数据操作剥离出来,这样做有几个好处:

  • 把数据处理逻辑分离使得代码更容易维护
  • 数据处理逻辑和业务逻辑分离,可以对这两个代码分别进行测试
  • 减少代码重复
  • 降低代码出错的几率
  • controller代码的可读性大大提高

以下演示时基于lumen框架的。

一般我们都会在controller中回去数据,然后处理展示到页面。如下:

但是这段代码有几个问题:

  1. 控制层与数据层耦合
  2. 严重依赖Model
  3. 无法写单元测试

所谓控制层与数据层耦合就是目前数据来源是本地数据库,如果将来该成服务化了,数据来源将会是远程服务,这个时候我们就不得不改写这个控制层。那么久违反了开闭原则。

严重依赖Model的意思就是,如果控制层和Model由2个开发人员开发,那么控制层不得不等待Model的完成才能继续工作。

由于数据严重依赖Model层,所以无法写出各种数据格式的单元测试来进行测试。

好的单元测试时重构的基础。也就无法进行重构。

接下来我们就引入仓库模式来进行改造。这里我们会借助一些laravel/lumen框架的基本特性,比如说:服务容器,依赖注入。如果你对这些不熟悉,那么请先进行了解。

第一步:完成“仓库”

我们在app目录下建立仓库文件夹。

  • app
    • Repositories
      • Interfaces
      • Instances

Interfaces里面是“面向接口编程”的接口,Instances里面是实现接口的类。

记下来我们编码工作。先写一个ContentRepositoryInterface的接口,建立规范。
再写3个类ContentLocalRepositoryContentHttpRepository以及ContentCustomerRepository,表示三个不同数据来源:本地、远程HTTP以及自定义数据。

第二步:服务注册

1、写一个服务提供者

2、 添加到bootstrap\app.php

第三步:改造源代码

接下来我们可以正常访问了。目前在服务提供者中,注册的是ContentLocalRepository。本地获取数据。如果说Model层还未完成,我们可以切换到ContentCustomerRepository自定义数据。将来做了服务化,可以切换到ContentHttpRepository获取数据。

着一些只需要东一行代码就可以完成。完全符合开闭原则,解耦了代码,并且使得代码职责明确,容易阅读。

关于单元测试我们可以用mockery/mockery插件,模拟各种操作,来进行生产数据。

composer安装mockery/mockery

编写单元测试

使用phpunit进行测试

设计模式 —— 代理模式

一、简介

模式意图 :为其他对象提供一种代理以控制对这个对象的访问

代理模式是对象的结构模式,代理模式给某一个对象提供一个代理对象,并由此代理对象控制对原代理对象的引用。代理模式不应该让用户感觉到代理的存在,所以代理对象和原对象的对外的调用接口是一致的。

二、代理

CD 音乐商城,购买商品时链接本地mysql,由于架构调整需要链接阿里云上的数据库。可以用以下操作实现。当然现实中我们只需修改配置文件即可。

远程mysql代理

client调用

与委托模式区别

在C#语言层面上,是有将观察者模式用“事件委托”的方式来处理。
“委托”是一种应用方法的类型。
代理是一种用来解决问题的方法。
其实2者没有可比性。
从实现功能上讲,
代理是提供一种“一个类对另外一个类的控制权。”是类与类之间关系。
委托提供了“一种方法的执行会同时执行加载在上面的方法”。是方法与方法之间的关系。
委托可以代替代理,但是代理不能代替委托。
委托可以动态加载方法,代理不能实现。
委托对象所加载的方法不一定要属于同一个类。但是代理的类必须属于同一个类。

设计模式 —— 观察者模式

观察者(Observer)模式:

  观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

  观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

 下面以一个简单的示意性实现为例,讨论观察者模式的结构。

观察者模式

 

观察者模式所涉及的角色有:

●  抽象主题(Subject)角色:抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。

●  具体主题(ConcreteSubject)角色:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。

●  抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。

●  具体观察者(ConcreteObserver)角色:存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态 像协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。

案例:

星际争霸中,重工厂的附件可以升级坦克支架。如果有多个重工厂附件时,其中一个升级了坦克支架那么其他几个附件中,坦克支架图片就会消失并且不能升级。

 

 

 

 

 

设计模式 —— 中介者模式

所谓中介者模式就是用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

     通过定义我们可以看出中介者主要是通过中介对象来封装对象之间的关系,使之各个对象在不需要知道其他对象的具体信息情况下通过中介者对象来与之通信。同时通过引用中介者对象来减少系统对象之间关系,提高了对象的可复用和系统的可扩展性。

     但是就是因为中介者对象封装了对象之间的关联关系,导致中介者对象变得比较庞大,所承担的责任也比较多。它需要知道每个对象和他们之间的交互细节,如果它出问题,将会导致整个系统都会出问题。所以它比较容易应用也很容易误用。故当系统中出现了“多对多”交互复杂的关系群时,千万别急着使用中介者模式,你首先需要做的就是反思你的系统在设计上是不是合理。

下图是中介者模式的UML结构图:

2222

      它主要包含如下几个角色:

        Mediator: 抽象中介者。定义了同事对象到中介者对象之间的接口。

        ConcreteMediator: 具体中介者。实现抽象中介者的方法,它需要知道所有的具体同事类,同时需要从具体的同事类那里接收信息,并且向具体的同事类发送信息。

        Colleague: 抽象同事类。

        ConcreteColleague: 具体同事类。每个具体同事类都只需要知道自己的行为即可,但是他们都需要认识中介者。

在中介者模式中中介者对象处于核心地位,因为它定义了整个系统中所有具体同事类之间的关系。在整个系统中它主要承担两个方面的责任。

      1、 结构上起到中转作用。通过中介者对象对关系的封装,使得具体的同事类不再需要显示的引用其他对象,它只需要通过中介者就可以完成与其他同事类之间的通信。

      2、 行为上起到协作作用。中介者对同事类之间的关系进行封装,同事类在不需要知道其他对象的情况下通过中介者与其他对象完成通信。在这个过程中同事类是不需要指明中介者该如何做,中介者可以根据自身的逻辑来进行协调,对同事的请求进一步处理,将同事成员之间的关系行为进行分离和封装。

      同时由于中介者对对象的关系进行了封装,使得各个同事类之间的耦合减少了,使得他们可以独立改变和复用。

 

案例:

房东与租客通过房屋中介进行联系。

 

具体调用

 

参考链接:

http://www.cnblogs.com/chenssy/p/3348520.html

设计模式 —— 迭代器模式

迭代器 (Iterator)模式:迭代器模式提供一种方法顺序一个聚合对象中各个元素,而又不暴露该对象内部表示。

迭代器模式由以下角色组成:

迭代器角色(Iterator):迭代器角色负责定义访问和遍历元素的接口。

具体迭代器角色(Concrete Iterator):具体迭代器角色要实现迭代器接口,并要记录遍历中的当前位置。

容器角色(Container):容器角色负责提供创建具体迭代器角色的接口。

具体容器角色(Concrete Container):具体容器角色实现创建具体迭代器角色的接口。这个具体迭代器角色与该容器的结构相关。

迭代器设计模式结构如如下:

具体如下:

 

设计模式 —— 解释器模式

解释器模式(Interpreter),给定一个语言,定义它的文法的一种表示,并定义一个解释器,用这个解释器使用该表示来解释语言中句子。解释器模式需要解决的是,如果一种特定类型的问题发生的频率足够高,那么可能就值得。

在开篇之前还是要科普几个概念:
抽象语法树
解释器模式并未解释如何创建一个抽象语法树。它不涉及语法分析。抽象语法树可用一个表驱动的语法分析程序来完成,也可用手写的(通常为递归下降法)语法分析程序创建,或直接client提供。

解析器
指的是把描述客户端调用要求的表达式,经过解析,形成一个抽象语法树的程序。

解释器
指的是解释抽象语法树,并执行每个节点对应的功能的程序。

要使用解释器模式,一个重要的前提就是要定义一套语法规则,也称为文法。不管这套文法的规则是简单还是复杂,必须要有这些规则,因为解释器模式就是按照这些规则来进行解析并执行相应的功能的。

enter image description here

案例:

一个计算器。(目前只能加减/或者乘除,并且必须按照一定格式)

接口,让子类必须存在此方法

主要用于存放数字的类

符号抽象类。定义规则,存在符号左侧变量及右侧变量。

一下是加减乘除操作类

计算器类,用户解析表达式,并进行计算。

调用计算

 

一个比简单粗俗的计算器完成。

参考链接:

http://www.cnblogs.com/chenssy/p/3346427.html

http://www.html-js.com/article/2085

设计模式 —— 工厂模式

工厂方法模式(Factory Method),定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。说的通俗一点吧,就是把将工厂类抽象成接口,具体的代工厂去实现此接口,同时把产品类也抽象成接口,再构造具体的产品去实现些接口。

 

案例:

创建不同类型的灯泡

 

工厂模式与建造者模式的类似。但是还是有一些区别的。

  1. 建造模式侧重于“装配”、“拼装”…而工厂侧重于“生产”
  2. 建造模式的导演,很清楚产生的步骤,因为他把握这生产者的接口(知道生产步骤)而工厂,却不知道,他只知道自己产出产品
  3. 建造模式屏蔽的是“装配”的细节,工厂模式则是屏蔽“构造”的细节…(有点晕其实)

建造模式,适用的范围是:

1)当产品对象非常复杂,无法一步到位生产,需要有N个部件拼合才能成行,但是步骤是固定的(重点),不过部件的属性可能不一样(内部表象不一样)

2)产品里面的需要填充的属性,比较难取得,需要另外的生产者进行生产才行,概括起来说就是,建造模式是当产品比较复杂(不能一下子完成),而且生产步骤已经稳定,但是同一个步骤 产出的“部件”可能属性不一样!这时候使用建造者模式最好

 

另一个角度说,建造模式比工厂模式“宏观”,建造模式里面的生产者可以是工厂模式!

设计模式 —— 外观模式

Facade外观模式,是一种结构型模式,它主要解决的问题是:组件的客户和组件中各种复杂的子系统有了过多的耦合,随着外部客户程序和各子系统的演化,这种过多的耦合面临很多变化的挑战。

GoF《设计模式》中说道:为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

缺点就是,外观模式违反了开闭原则。

案例:

公司的web站点,每晚都要将其库存信息传递至公司内的不同系统。不过它是一个比较老的系统,并且只能处理大写的字符串。

具体操作: CD 对象 -> 属性转大写 -> 传输XML。

创建实例

 

普通调用,如果需求更对那么每次调用的时候都需要写N多行代码。如下:

外观模式,把上面代码封装起来,其他地方调用时候只需一行代码即可。如下:

 

设计模式 —— 委托模式

委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。委托模式是一项基本技巧,许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式。委托模式使得我们可以用聚合来替代继承,它还使我们可以模拟mixin。

 

案例:

MP3文件播放列表,访问者可以选择下载M3U或者PLS格式的播放列表。

添加歌曲,通过用户的命令下载播放列表。

如果说播放列表的格式越来越多了,那么if…else…更多了,那样很不美观和代码冗余。

我们可以委托者模式类解决这个问题。

Playlist是委托者,然后有m3u和pls等的被委托者。

playlist委托者存在一个私有变量存放被委托者,然后我们可以调用被委托者的方法,让用户下载其所要的格式。

调用如下,代码就是如此清晰简洁。