在PHP和Java中都有Interface的概念,刚接触开发时大家都知道在面向对象中Interface负责定义一些抽象方法来抽象和界定类对象的行为,更有一个“鸭式辩型”理论大概的意思就是使用者并不关心对象的内部是怎么实现的只要你会“呱呱叫(method)”就认为这是一个鸭子对象,但是很多人实际开发的时候并不会去定义Interface,认为多定义这么一层额外增加了工作量并且对程序开发看起来没有明显的增益效果。这篇文章里我就结合着Laravel框架来说一下为什么要使用Interface以及通过Interface给程序在长期维护、团队协作和测试带来收益。

首先在Interface在Laravel框架中被称为契约, 例如我们在介绍用户认证的章节中到的用户看守器契约Illumninate\Contracts\Auth\Guard 和用户提供器契约Illuminate\Contracts\Auth\UserProvider

以及框架自带的 App\User模型所实现的Illuminate\Contracts\Auth\Authenticatable契约。

为什么使用契约

通过上面几个契约的源码文件我们可以看到,Laravel提供的契约是为核心模块定义的一组interface。Laravel为每个契约都提供了相应的实现类,下表列出了Laravel为上面提到的三个契约提供的实现类。

契约 Laravel内核提供的实现类
Illumninate\Contracts\Auth\Guard Illuminate\Auth\SessionGuard
Illuminate\Contracts\Auth\UserProvider Illuminate\Auth\EloquentUserProvider
Illuminate\Contracts\Auth\Authenticatable Illuminate\Foundation\Auth\Authenticatable(User Model的父类)

所以在自己开发的项目中,如果Laravel提供的用户认证系统无法满足需求,你可以根据需求定义看守器和用户提供器的实现类,比如我之前做的项目就是用户认证依赖于公司的员工管理系统的API,所以我就自己写了看守器和用户提供器契约的实现类,让Laravel通过自定义的Guard和UserProvider来完成用户认证。自定义用户认证的方法在介绍用户认证的章节中我们介绍过,读者可以去翻阅那块的文章。

所以Laravel为所有的核心功能都定义契约接口的目的就是为了让开发者能够根据自己项目的需要自己定义实现类,而对于这些接口的消费者(比如:Controller、或者内核提供的 AuthManager这些)他们不需要关心接口提供的方法具体是怎么实现的, 只关心接口的方法能提供什么功能然后去使用这些功能就可以了,我们可以根据需求在必要的时候为接口更换实现类,而消费端不用进行任何改动。

定义和使用契约

上面我们提到的都是Laravel内核提供的契约, 在开发大型项目的时候我们也可以自己在项目中定义契约和实现类,你有可能会觉得自带的Controller、Model两层就已经足够你编写代码了,凭空多出来契约和实现类会让开发变得繁琐。我们先从一个简单的例子出发,考虑下面的代码有什么问题:

class OrderController extends Controller
{public function getUserOrders(){$orders= Order::where('user_id', '=', \Auth::user()->id)->get();return View::make('order.index', compact('orders'));}
}

这段代码很简单,但我们要想测试这段代码的话就一定会和实际的数据库发生联系。也就是说, ORM和这个控制器有着紧耦合。如果不使用Eloquent ORM,不连接到实际数据库,我们就没办法运行或者测试这段代码。这段代码同时也违背了“关注分离”这个软件设计原则。简单讲:这个控制器知道的太多了。 控制器不需要去了解数据是从哪儿来的,只要知道如何访问就行。控制器也不需要知道这数据是从MySQL或哪儿来的,只需要知道这数据目前是可用的。

Separation Of Concerns 关注分离

Every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class.

每个类都应该只有单一的职责,并且职责里所有的东西都应该由这个类封装

接下来我们定义一个接口,然后实现该接口

interface OrderRepositoryInterface
{public function userOrders(User $user);
}
class OrderRepository implements OrderRepositoryInterface
{public function userOrders(User $user){Order::where('user_id', '=', $user->id)->get();}
}

将接口的实现绑定到Laravel的服务容器中

App::singleton('OrderRepositoryInterface', 'OrderRespository');

然后我们将该接口的实现注入我们的控制器

class UserController extends Controller
{public function __construct(OrderRepositoryInterface $orderRepository){$this->orders = $orderRespository;}public function getUserOrders(){$orders = $this->orders->userOrders();return View::make('order.index', compact('orders'));}
}

现在我们的控制器就完全和数据层面无关了。在这里我们的数据可能来自MySQL,MongoDB或者Redis。我们的控制器不知道也不需要知道他们的区别。这样我们就可以独立于数据层来测试Web层了,将来切换存储实现也会很容易。

接口与团队开发

当你的团队在开发大型应用时,不同的部分有着不同的开发速度。比如一个开发人员在开发数据层,另一个开发人员在做控制器层。写控制器的开发者想测试他的控制器,不过数据层开发较慢没法同步测试。那如果两个开发者能先以interface的方式达成协议,后台开发的各种类都遵循这种协议。一旦建立了约定,就算约定还没实现,开发者也可以为这接口写个“假”实现

class DummyOrderRepository implements OrderRepositoryInterface
{public function userOrders(User $user){return collect(['Order 1', 'Order 2', 'Order 3']);}
}

一旦假实现写好了,就可以被绑定到IoC容器里

App::singleton('OrderRepositoryInterface', 'DummyOrderRepository');

然后这个应用的视图就可以用假数据填充了。接下来一旦后台开发者写完了真正的实现代码,比如叫 RedisOrderRepository。那么使用IoC容器切换接口实现,应用就可以轻易地切换到真正的实现上,整个应用就会使用从Redis读出来的数据了。

接口与测试

建立好接口约定后也更有利于我们在测试时进行Mock

public function testIndexActionBindsUsersFromRepository()
{    // Arrange...$repository = Mockery::mock('OrderRepositoryInterface');$repository->shouldReceive('userOrders')->once()->andReturn(['order1', 'order2']);App::instance('OrderRepositoryInterface', $repository);// Act...$response  = $this->action('GET', 'OrderController@getUserOrders');// Assert...$this->assertResponseOk();$this->assertViewHas('order', ['order1', 'order2']);}

总结

接口在程序设计阶段非常有用,在设计阶段与团队讨论完成功能需要制定哪些接口,然后设计出每个接口具体要实现的方法,方法的入参和返回值这些,每个人就可以按照接口的约定来开发自己的模块,遇到还没实现的接口完全可以先定义接口的假实现等到真正的实现开发完成后再进行切换,这样既降低了软件程序结构中上层对下层的耦合也能保证各部分的开发进度不会过度依赖其他部分的完成情况。

在程序设计中使用Interface相关推荐

  1. [程序] 程序设计中的范畴论 (第一部分)

    程序设计中的范畴论 (第一部分) 文章目录 程序设计中的范畴论 (第一部分) 0 引言 0.1 抽象 0.2 悖论 1 范畴 (Category) 1.1 交换图 1.2 示例 正整数与偏序关系 矩阵 ...

  2. R程序设计中的IF、IFELSE、SWITCH

    R程序设计中的IF.IFELSE.SWITCH 目录 R程序设计中的IF.IFELSE.SWITCH R程序设计中的IF语句 R程序设计中的IFELSE语句

  3. 在Java程序设计中,设置环境变量path和classpath的作用分别是什么?

    在Java程序设计中,设置环境变量path和classpath的作用分别是什么? asd79308 10级  分类: 编程开发  被浏览344次  2013.07.22 额,你这问题问的有够" ...

  4. 学号20175313 《程序设计中临时变量的使用》第八周

    目录 程序设计中临时变量的使用 一.题目要求 二.运行结果截图 三.遇到的问题及其解决方法 四.代码链接 五.心得体会 程序设计中临时变量的使用 一.题目要求 //定义一个数组,比如int arr[] ...

  5. 浅谈单片机程序设计中的“分层思想”!

    浅谈单片机程序设计中的"分层思想",并不是什么神秘的东西,事实上很多做项目的工程师本身自己也会在用.看了不少帖子都发现没有提及这个东西,然而分层结构确是很有用的东西,参透后会有一种 ...

  6. 在ASP程序设计中在使用Response对象

    Response对象在ASP程序设计中的主要功能是从浏览器端到服务器端传送数据到浏览器的客户端,我们知道ASP的脚本是在服务器端执行的,他并没有输出"值"的功能.要想拥有输出&qu ...

  7. 状态机思路在程序设计中的应用

    状态机思路在单片机程序设计中的应用 状态机的概念 状态机是软件编程中的一个重要概念.比这个概念更重要的是对它的灵活应用.在一个思路清晰而且高效的程序中,必然有状态机的身影浮现. 比如说一个按键命令解析 ...

  8. 程序设计中的驼峰原则

    程序设计中的驼峰原则是程序中的变量和函数名的命名原则,比如变量username要写作userName或者UserName 规则:变量名或函数名的每一个单词的首字母要大写,第一个单词的字母可大写或者小写 ...

  9. createprocess重启程序_C++_VC程序设计中CreateProcess用法注意事项,对于windows程序设计来说,启动 - phpStudy...

    VC程序设计中CreateProcess用法注意事项 对于windows程序设计来说,启动一个进程有三种方法:WinExec,ShellExecute,CreateProcess.这里仅对Create ...

最新文章

  1. java异步请求显示数据格式_JSON(四)——异步请求中前后端使用Json格式的数据进行交互...
  2. Embedding技术在房产推荐中的应用
  3. ElasticSearch集群部署【windows+Linux双系统搭建】
  4. 在安卓JNI/NDK中使用C++11
  5. 学习笔记(55):Python实战编程-Scrollbar
  6. .NET做人脸识别并分类
  7. 递归javascript_JavaScript中的递归
  8. 刑法中关于计算机犯罪的规定
  9. 网易云android视频播放器,网易云服务-在线搜歌-数据结构
  10. Unity3d设置成中文版
  11. 软件项目管理的基本流程
  12. MySQL Bug一例-----ibuf cursor restoration fails
  13. mysql查询学生表年龄语句_mysql中一张(居民)表按年龄段查询数据
  14. 高通机器视觉快速指南二
  15. SRAM电路工作原理
  16. Tekton笔记(四)之Authentication及catalog skopeo
  17. Mybatis03-封装MybatisUtil实体类
  18. 牛客每日练习----​​​​​​​最长回文,Alice和Bob赌糖果,N阶汉诺塔变形
  19. python自带的集成开发环境是什么-跟老齐学Python之集成开发环境(IDE)
  20. oo 浏览文件服务器,文件服务器的配置.docx

热门文章

  1. SQL Server Pivot 隐藏group
  2. NSBundle 的理解和 mainBundle
  3. 修改oracle重做日志文件大小
  4. 替换 centOS6.5 默认安装的旧版 firefox ,安装最新版 firefox 全过程
  5. Linux MTD子系统 _从模型分析到Flash驱动模板
  6. ios7以后隐藏状态栏
  7. VS2012程序打包部署详解
  8. 适合Web服务器的iptables规则
  9. Android之Fragment
  10. jQuery attributes(上)