PHP设计模式之适配器模式(Adapter)原理与用法详解
本文实例讲述了PHP设计模式之适配器模式(Adapter)原理与用法。分享给大家供大家参考,具体如下:
这个适配器模式,就是为了将一个类的接口转换成客户希望的另外一个接口,并且使用原本不兼容的而不能在一起工作的那些类可以在一起工作。它的核心思想就是把对某些相似的类的操作转化为一个统一的“接口”(这里是比喻的说话)–适配器,或者比喻为一个“界面”,统一或屏蔽了那些类的细节。适配器模式还构造了一种“机制”,使“适配”的类可以很容易的增减,而不用修改与适配器交互的代码,符合“减少代码间耦合”的设计原则。
我们来考虑下开发过程中,我们引用一个第三方类库的场景,这个类库随着版本的改变,它提供的API也可能会改变。如果很不幸的是,你的应用里引用的某个API已经发生改变的时候,除了在心中默默地骂“wocao”之外,你还得去硬着头皮去改大量的代码,这个时候,为了减少工作量,我们就可以使用适配器模式。
先来看一个网上的案例:
- 假如我们原始的有一个UserInfo的类,提供用户信息的类,早起设计该类的时候,只实现了一个getUserName获取用户名的方法。
- 我们的MyOldObject类中,将从UserInfo这个类中获取用户信息,并且输出用户名
- 随着时间的推移,我们旧的UserInfo这个类只提供的获取用户名的方法,已经没法满足需求,我们同时需要获取用户的年龄等信息。
- 为了不改变原本UserInfo这个类,我们就继承UserInfo,建立一个UserInfoAdapter类,实现getAge获取年龄这样的方法。
- 在我们的MyNewObject新的类中,我们实例化UserInfoAdapter,打印出用户姓名和年龄。
- 这样,随着我们的扩展,我们没有改变原先UserInfo这个类和使用这个类的接口,我们通过适配的方法,将UserInfo类扩展出来
代码实现过程如下:
<?php
//早期的一个用户类,只实现获取用户名的方法
class UserInfo {
public function getUserName() {
return 'initphp';
}
}
//MyOldObject类,从UserInfo类中获取信息,输出用户名
<?php
include_once("UserInfo.php");
class MyOldObject {
public function write() {
$UserInfo = new UserInfo;
echo $UserInfo- getUserName();
}
}
$a = new MyOldObject;
$a- write();
上述代码是早期的时候,我们使用的案例。然而UserInfoAdapter类,随着时间推移,项目需求在变化,UserInfo类无法满足需求,我们做了UserInfo类的适配器,满足新功能的需求,如下:
<?php
include_once("UserInfo.php");
class UserInfoAdapter extends UserInfo{
public function getUserAge() {
return 28;
}
public function getUser() {
return array(
'username' = $this- getUserName(),
'age' = $this- getUserAge()
);
}
}
MyNewObject类,新功能的类,需要打印出用户年龄和姓名,UserInfo类无法满足需求,需要调用UserInfoAdapter适配器这个类,如下:
<?php
include_once("UserInfoAdapter.php");
class MyNewObject {
public function write() {
$UserInfoAdapter = new UserInfoAdapter;
print_r($UserInfoAdapter- getUser());
}
}
$a = new MyNewObject;
$a- write();
大概了解了哈,接下来咱们通过一个故事来了解下。
开始的时候,黑枣玩具公司专门生产玩具,生产的玩具不限于狗、猫、狮子,鱼等动物,并且每个玩具都可以进行“张嘴”与“闭嘴”操作,分别调用了openMouth与closeMouth方法。在这个时候,黑枣玩具公司的程序猿就定义一个抽象类Toy,甚至是接口Toy,完事其他的类去继承父类,实现父类的方法,很和谐的是吧。
后来,为了扩大业务,也因为红枣遥控公司可以使用遥控设备对动物进行嘴巴控制,黑枣玩具公司打算与红枣遥控公司合作。不过,麻烦的是,红枣遥控公司的遥控设备是调用的动物的doMouthOpen及doMouthClose方法。所以,黑枣玩具公司的程序员现在必须要做的是对Toy系列类进行升级改造,使Toy能调用doMouthOpen及doMouthClose方法。
在考虑实现的方法时,黑枣玩具公司的程序猿可以再在他们的父类子类里给红枣遥控公司添加这么两个方法就好啦。但是,当黑枣玩具公司的程序猿一次又一次在父类子类里面重复添加着这两个方法的时候,总会想着如此重复的工作,难道不能解决么?当有数百个子类的时候,程序员会改疯的。程序员往往比的是谁在不影响效率的时候更会“偷懒”,这样做下去程序员会觉得自己很傻。
咱也不废话了,先来看下最开始的时候的代码:
abstract class Toy
{
public abstract function openMouth();
public abstract function closeMouth();
}
class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouthn";
}
public function closeMouth()
{
echo "Dog open Mouthn";
}
}
class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouthn";
}
public function closeMouth()
{
echo "Cat open Mouthn";
}
}
完事,因为绿枣遥控公司遥控设备更便宜稳定,所以黑枣玩具公司又打算要与绿枣遥控公司合作。
不过绿枣遥控公司的遥控设备是调用的动物的operMouth(type)方法来实现嘴巴控制。如果type)方法来实现嘴巴控制。如果type为0则“闭嘴”,反之张嘴。这下好了,程序员又得对Toy及其子类进行升级,使Toy能调用operMouth()方法。
在这个时候,程序员必须要动脑子想办法了,就算自己勤快,万一哪天紫枣青枣黄枣山枣这些遥控公司全来的时候,忽略自己不断增多的工作量不说,这个Toy类可是越来越大,总有一天程序员不崩溃,系统也会崩溃的。
那么,问题出在哪里呢?
其实就是一开始的代码设计实现违反了“开-闭”原则,也就是一个软件实体应当对扩展开放,对修改关闭。也就是说,在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。也就是说每个尸体都是一个小王国,你让我参与你的事情这个可以,但你不能修改我的内部,除非我的内部代码确实可以优化。
来看下最后的结果:
<?php
abstract class Toy
{
public abstract function openMouth();
public abstract function closeMouth();
}
class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouthn";
}
public function closeMouth()
{
echo "Dog close Mouthn";
}
}
class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouthn";
}
public function closeMouth()
{
echo "Cat close Mouthn";
}
}
//目标角色:红枣遥控公司
interface RedTarget
{
public function doMouthOpen();
public function doMouthClose();
}
//目标角色:绿枣遥控公司及
interface GreenTarget
{
public function operateMouth($type = 0);
}
//类适配器角色:红枣遥控公司
class RedAdapter implements RedTarget
{
private $adaptee;
function __construct(Toy $adaptee)
{
$this- adaptee = $adaptee;
}
//委派调用Adaptee的sampleMethod1方法
public function doMouthOpen()
{
$this- adaptee- openMouth();
}
public function doMouthClose()
{
$this- adaptee- closeMouth();
}
}
//类适配器角色:绿枣遥控公司
class GreenAdapter implements GreenTarget
{
private $adaptee;
function __construct(Toy $adaptee)
{
$this- adaptee = $adaptee;
}
//委派调用Adaptee:GreenTarget的operateMouth方法
public function operateMouth($type = 0)
{
if ($type) {
$this- adaptee- openMouth();
} else {
$this- adaptee- closeMouth();
}
}
}
class testDriver
{
public function run()
{
//实例化一只狗玩具
$adaptee_dog = new Dog();
echo "给狗套上红枣适配器n";
$adapter_red = new RedAdapter($adaptee_dog);
//张嘴
$adapter_red- doMouthOpen();
//闭嘴
$adapter_red- doMouthClose();
echo "给狗套上绿枣适配器n";
$adapter_green = new GreenAdapter($adaptee_dog);
//张嘴
$adapter_green- operateMouth(1);
//闭嘴
$adapter_green- operateMouth(0);
}
}
$test = new testDriver();
$test- run();
大概了解了使用方式之后,我们来看下适配器模式之中的主要角色:
- 目标(Target)角色:定义客户端使用的与特定领域相关的接口,这也就是我们所期待得到的
- 源(Adaptee)角色:需要进行适配的接口
- 适配器(Adapter)角色:对Adaptee的接口与Target接口进行适配;适配器是本模式的核心,适配器把源接口转换成目标接口,此角色为具体类
使用场景如下:
1、你想使用一个已经存在的类,而它的接口不符合你的需求 2、你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作 3、你想使用一个已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口(仅限于对象适配器)
再来看下类适配器和对象适配器的一些解释和区别:
类适配器:Adapter与Adaptee是继承关系
1、用一个具体的Adapter类和Target进行匹配。结果是当我们想要一个匹配一个类以及所有它的子类时,类Adapter将不能胜任工作 2、使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子集 3、仅仅引入一个对象,并不需要额外的指针以间接取得adaptee
对象适配器:Adapter与Adaptee是委托关系
1、允许一个Adapter与多个Adaptee同时工作。Adapter也可以一次给所有的Adaptee添加功能 2、使用重定义Adaptee的行为比较困难
再来看下其它和适配器模式的对比:
- 桥梁模式(bridge模式):桥梁模式与对象适配器类似,但是桥梁模式的出发点不同,桥梁模式目的是将接口部分和实现部分分离,从而对它们可以较为容易也相对独立的加以改变。而对象适配器模式则意味着改变一个已有对象的接口
- 装饰器模式(decorator模式):装饰模式增强了其他对象的功能而同时又不改变它的接口。因此装饰模式对应用的透明性比适配器更好。
最后来看下类适配器和对象适配器案例,如下:
//类适配器使用的是继承
<?php
/**
* 目标角色
*/
interface Target {
/**
* 源类也有的方法1
*/
public function sampleMethod1();
/**
* 源类没有的方法2
*/
public function sampleMethod2();
}
/**
* 源角色
*/
class Adaptee {
/**
* 源类含有的方法
*/
public function sampleMethod1() {
echo 'Adaptee sampleMethod1 <br / ';
}
}
/**
* 类适配器角色
*/
class Adapter extends Adaptee implements Target {
/**
* 源类中没有sampleMethod2方法,在此补充
*/
public function sampleMethod2() {
echo 'Adapter sampleMethod2 <br / ';
}
}
class Client {
/**
* Main program.
*/
public static function main() {
$adapter = new Adapter();
$adapter- sampleMethod1();
$adapter- sampleMethod2();
}
}
Client::main();
?
//对象适配器使用的是委派
<?php
/**
* 目标角色
*/
interface Target {
/**
* 源类也有的方法1
*/
public function sampleMethod1();
/**
* 源类没有的方法2
*/
public function sampleMethod2();
}
/**
* 源角色
*/
class Adaptee {
/**
* 源类含有的方法
*/
public function sampleMethod1() {
echo 'Adaptee sampleMethod1 <br / ';
}
}
/**
* 类适配器角色
*/
class Adapter implements Target {
private $_adaptee;
public function __construct(Adaptee $adaptee) {
$this- _adaptee = $adaptee;
}
/**
* 委派调用Adaptee的sampleMethod1方法
*/
public function sampleMethod1() {
$this- _adaptee- sampleMethod1();
}
/**
* 源类中没有sampleMethod2方法,在此补充
*/
public function sampleMethod2() {
echo 'Adapter sampleMethod2 <br / ';
}
}
class Client {
/**
* Main program.
*/
public static function main() {
$adaptee = new Adaptee();
$adapter = new Adapter($adaptee);
$adapter- sampleMethod1();
$adapter- sampleMethod2();
}
}
Client::main();
?
好啦,本次记录就到这里了。
更多关于PHP相关内容感兴趣的读者可查看本站专题:《php面向对象程序设计入门教程》、《PHP数组(Array)操作技巧大全》、《PHP基本语法入门教程》、《PHP运算与运算符用法总结》、《php字符串(string)用法总结》、《php+mysql数据库操作入门教程》及《php常见数据库操作技巧汇总》
希望本文所述对大家PHP程序设计有所帮助。
- Java基础-day03-代码题
- mongodb11天之屠龙宝刀(九)js函数入门:MongoDB基于js的数据类型修改
- Go语言社区 APP --问答模块数据存储流程及代码
- Java基础-day09-重构随机点名器
- OpenCV3.4两种立体匹配算法效果对比
- 文件操作常用函数
- Java基础-day09-对象;类;封装 学生管理系统
- two Pass方法连通域检测
- 【Java入门提高篇】Day14 Java中的泛型初探
- 使用shell脚本快速得到主备关系(r9笔记第93天)
- 【Java入门提高篇】Day13 Java中的反射机制
- 仿腾讯课堂固定滚动列表ReactNative组件
- Golang通过socket与java通讯
- Java基础-day09-基础题-对象;类;封装
- php概述
- php教程
- php环境搭建
- PHP书写格式
- php变量
- php常量
- PHP注释
- php数组
- php字符串 string
- PHP整型 integer
- PHP浮点型 float
- php布尔型
- php数据类型之数组
- php数据类型之对象
- php数据类型之null
- php数据类型之间的转换
- php运算符
- php表达式
- PHP循环控制
- PHP流程控制
- php函数
- php全局变量
- PHP魔术变量
- php命名空间
- php 日期
- PHP包含文件
- php文件
- PHP 文件上传
- php Cookies
- php Sessions
- php email
- php安全email
- php错误处理
- PHP异常处理
- php过滤器
- PHP 高级过滤器
- php json
- php 表单
- PHP MySQL 简介
- PHP 连接 MySQL
- php创建数据库
- php 创建表
- php mysq 插入数据
- PHP MySQL 插入多条数据
- PHP MySQL 预处理语句
- php mysql 读取数据
- php mysql where
- PHP MySQL Order By
- PHP MySQL Update
- PHP MySQL Delete
- php ODBC
- python实现语音在线合成,让你的小说自己念给你听
- Python爬虫实战:模拟登录淘宝网站
- Python制作动态二维码(附源码,复制再贴修改路径名字即可)
- 新手小白的福利,零基础也能上手的项目——学生信息管理系统
- Python制作小脚本,一键可以让你同事的电脑在你指定时间关机
- Python小白爬虫入门的第一个案例:爬取全站小说
- Python基础入门知识点——字符串的介绍
- 文章要保存为TXT文件,其中的图片要怎么办?Python帮你解决
- 分析B站弹幕,川普同志暴露的那一天,没有一个鬼畜up是无辜的
- python-爬取地理坐标
- Python基础第一个案例:猜数字游戏,这个都写不出,那就放弃吧
- 现在听歌要各大平台到处跑,嫌麻烦?制作个人专属的音乐下载器
- 爬取上市公司数据、分析数据,并用可视化现实全国各地区公司数量
- 今天刚上手爬虫,当然要从最简单的开始啦,验证一下所学的知识
- Python数据可视化入门:使用Matplotlib绘图