easy-fedex 关于组合模式的粗浅应用
文章创建于: 2017-03-15,最后修改于:2018-09-21

php
2
设计模式
1

最近的工作有接触到SDK的设计和编码,公司业务需要调用 fedex (联邦快递) 的web service服务,而 fedex 的开发文档长达1033页,全英文,非常啰嗦和繁琐,为了方便使用决定将 fedex 的服务封装成 SDK。

简单介绍一下web service

Web Service是一种面向服务的架构的技术,通过标准的Web协议提供服务,目的是保证不同平台的应用服务可以互操作。

PHP 直接调用fedex web service

下面是fedex官方给的PHP调用实例代码

$path_to_wsdl = "../../wsdl/TrackService_v12.wsdl";

ini_set("soap.wsdl_cache_enabled", "0");

$client = new SoapClient($path_to_wsdl, array('trace' => 1));

$request = [
'WebAuthenticationDetail' => [
'ParentCredential' => [
'Key' => 'xxx',
'Password' => 'xxx'
],
'UserCredential' => [
'Key' => 'xxxx',
'Password' => 'xxxx'
]
],
'ClientDetail' => [
'AccountNumber' => 'xxxx',
'MeterNumber' => 'xxx'
],
'TransactionDetail' => ['CustomerTransactionId' => '*** Track Request using PHP ***'],

'Version' => [
'ServiceId' => 'trck',
'Major' => '12',
'Intermediate' => '0',
'Minor' => '0'
],

'SelectionDetails' = [
'PackageIdentifier' => [
'Type' => 'TRACKING_NUMBER_OR_DOORTAG',
'Value' => '12312312312' // Replace 'XXX' with a valid tracking identifier
]
]
];
$response = $client ->track($request);

此种通过组合不同的数据到同一个服务的方式,非常适合使用设计模式中的组合模式来封装各个数据节点。

组合模式介绍

组合模式的官方定义:

组合模式 (Composite Pattern) 也叫合成模式,有时又叫做部分-整体模式(Part-Whole),主要是用来描述部分与整体的关系,将对象组合成树形结构以表示部分-整体的层次结构,使得用户对单个对象和组合对象的使用具有一致性

为什么确定使用组合模式对fedex的服务进行封装?因为请求服务时组装的数据是一个典型的二叉树 xml 结构,上述例程中 $request 数组如果将每一个子节点封装成对象再调用时再组合。

这种方式的好处是,当我们想增加一个树枝节点或树叶节点都非常的容易扩展,符合开闭原则,简化了外部调用的代码。

我的实现过程

类图如下:

  • 首先需要一个抽象节点类,并要求所有节点继承抽象节点
abstract class AbstractElement
{
protect $elements = [];

function addElement(AbstractElement $element)
{
$this->elements = array_merge($this->elements, $element->toArray())
}

function delElement(AbstractElement $element)
{

}
}
  • 创建根节点
class Root extends AbstractElement
{

}
  • 然后需要所有具体的节点继承抽象节点类
class Address extends AbstractElement
{
protect $country;
protect $city;

function setCountry($country)
{
$this->country = $country;
return $this;
}

function setCity($city)
{
$this->city = $city;
return $this;
}

function toArray()
{
return [
'Address' => [
'Country' => $this->country,
'City' => $this->city
]
];
}
}
  • 最后终端用户调用时可以按如下方式组合数据
$root = new Root();

$address = new Address();
$address->setCountry('India');
$address->setCity('xxx');

$root->addElement($address);

更多具体实现可以看完整的代码。在这份代码中的实现与本文描述有一定差异,因为是先实现的代码再写的这篇文章,以后会逐渐重构代码使其符合设计。

组合模式关键的实现在于你想要构建组件树的每一个组件节点必须实现组件接口,因此理论上来说,组合模式适用于所有需要使用树形结构表示数据的场景。如文件夹菜单,部门组织架构图等等。

通用结构

构建一个完整的组合模式需有以下几个条件

  1. 抽象构建
abstract class Component
{
// 个体和整体都具有的操作或逻辑
public function doSomeThing()
{

}
}
  1. 树枝节点
class Composite extends Component
{
protected $list = []; // 容器,可添加下级树枝节点或叶子节点

// 添加一个叶子节点或下级树枝节点
public function add(Component $component)
{

}

// 删除一个叶子节点或下级树枝节点
public function remove(Component $component)
{

}

// 获得所有该节点下的所有子节点
public function getChildren()
{

}
}
  1. 叶子节点
// 叶子节点无下级对象,用于定义参与组合的最原始元素
class Leaf extends Component
{
function doSomeThing
{

}
}

客户端调用组合模式实现如下:

$root = new Composite();
$branch_1 = new Composite(); // 根节点下第一个分支

$branch_2 = new Composite(); // 根节点下第二个分支

$branch_1->add(new Leaf()); // 添加元素
$branch_1->add(new Leaf()); // 添加元素
$branch_2->add(new Leaf()); // 添加元素
$branch_2->add(new Leaf()); // 添加元素

$root->add($branch_1);
$root->add($branch_2);

$root->getChildren(); // 得到组合后的树形结构

结束

以上是通过写fedex sdk整理出的设计模式的应用,组合模式还可以进行一些扩展,比如将每个节点都增加一个属性叫做父节点对象,并在每个节点中实现一个find方法,这样就可以在任意节点一层层找到自己的父节点直到找到根节点为止,有了这个属性,即可实现如前序遍历,中序遍历,后续遍历等问题。

设计模式是一个好东西,但不能滥用,在了解设计模式的时候一度被它束缚住手脚,导致迟迟不能下手,不知道用哪种设计模式能最贴合业务需求。

事实上设计模式是前人根据实际业务场景总结出的更好的设计,是用来从你的现有代码中重构和让你的设计更上一层楼的东西,过早优化是万恶之源

共勉!

-->