一个类只做一件事
说明
本文主要结合代码说明如何去在写代码时做到 一个类只做一件事 。
需求
实现一个 cURL 类,针对不同平台的 API 进行操作,并最终返回 array 给方法的调用方。
思路
分析需求设计到的功能点:
cURL操作类- 不同平台的验证规则可能不一样,所以每一个平台都应该对应一个类
cURL请求一般返回的是字符串,需要将结果转化为array
实现
-
定义好
CurlInterfaceDriver类的Interface规范。主要定义三个常用方法
namespace App\Curl\bin; interface CurlInterfaceDriver { public function get($url, $options = []); public function post($url, $options = []); public function request($method, $url, $options = []); } -
实现抽象类
AbstractHttpCurlDrivernamespace App\Curl\bin; use GuzzleHttp\Client; abstract class AbstractHttpCurlDriver implements CurlInterfaceDriver { /** * @var null|Client */ protected $client = null; protected $response = null; public function __construct(Client $client = null) { if (is_null($client)) { $client = new Client(); } $this->setClient($client); } /** * @param $url * @param array $options * @return string * @date 2019/01/25 * @author ycz * @throws \GuzzleHttp\Exception\GuzzleException */ public function get($url, $options = []) { $response = $this->getClient()->request('GET', $url, $options)->getBody()->getContents(); $this->setResponse($response); return $response; } /** * @param $url * @param array $options * @return string * @date 2019/01/25 * @author ycz * @throws \GuzzleHttp\Exception\GuzzleException */ public function post($url, $options = []) { $response = $this->getClient()->request('POST', $url, $options)->getBody()->getContents(); $this->setResponse($response); return $response; } /** * @param $method * @param $url * @param array $options * @return string * @date 2019/01/25 * @author ycz * @throws \GuzzleHttp\Exception\GuzzleException */ public function request($method, $url, $options = []) { $response = $this->getClient()->request($method, $url, $options)->getBody()->getContents(); $this->setResponse($response); return $response; } /** * @return null|Client */ private function getClient() { return $this->client; } /** * @param Client $client */ private function setClient(Client $client) { $this->client = $client; } /** * @return null|string */ public function getResponse() { return $this->response; } /** * @param null|string $response */ private function setResponse($response) { $this->response = $response; } } -
每个平台返回数据格式可能不一样,所以抽象类中只将结果字符串返回,针对不同返回数据格式,我们可以创建不同的
Driver继承AbstractHttpCurlDriver针对
JSON返回结果的类。namespace App\Curl\bin; class JsonHttpCurlDriver extends AbstractHttpCurlDriver { }针对
XML返回结果的类。namespace App\Curl\bin; class XMLHttpCurlDriver extends AbstractHttpCurlDriver { }因为
JSON和XML两个驱动的只有返回结果不一样,其他内容基本一样,所以不用在自己类里面改写抽象类里面的东西。 -
实现将
response转换为array的类根据使用的
driver自动转换结果。namespace App\Curl\bin; use App\Exceptions\ResponseNotJsonException; use App\Exceptions\ResponseNotXMLException; use SimpleXMLElement; class ApiData2ArrayFactory { public static function make(CurlInterfaceDriver $curl) { if ($curl instanceof JsonHttpCurlDriver) { return static::json2Array($curl->getResponse()); } if ($curl instanceof XMLHttpCurlDriver) { return static::xml2Array($curl->getResponse()); } } /** * json 转数组 * * @param $json * @return array * @date 2019/01/25 * @author ycz * @throws ResponseNotJsonException */ private static function json2Array($json) { try { $data = \GuzzleHttp\json_decode($json, true); } catch (\InvalidArgumentException $e) { throw new ResponseNotJsonException(); } return $data; } /** * @param $xml * @return array * @date 2019/01/25 * @author ycz * @throws ResponseNotXMLException */ private static function xml2Array($xml) { if (!static::isXml($xml)) { throw new ResponseNotXMLException(); } function _XML2Array(SimpleXMLElement $parent) { $array = array(); foreach ($parent as $name => $element) { ($node = &$array[$name]) && (1 === count($node) ? $node = array($node) : 1) && $node = &$node[]; $node = $element->count() ? _XML2Array($element) : trim($element); } return $array; } $xml = new SimpleXMLElement($xml); $data = _XML2Array($xml); return $data; } /** * @param $xml * @return int * @date 2019/01/25 * @author ycz */ private static function isXml($xml) { return xml_parse(xml_parser_create(), $xml, true); } } -
实现对外调用的
cURL类namespace App\Curl\bin; /** * Class Curl * * @package App\Curl\bin * @method get * @method post * @method request * @see AbstractHttpCurlDriver */ class Curl { private $curlDriver = null; public function __construct(CurlInterfaceDriver $curl) { $this->curlDriver = $curl; } public function __call($name, $arguments) { $this->curlDriver->$name(...$arguments); return ApiData2ArrayFactory::make($this->curlDriver); } } -
实现单个平台的
cURL类,以亚马孙为例,我们创建一个AmazonCurlnamespace App\Curl\Amazon; use App\Curl\BaseCurl; use App\Curl\bin\Curl; use App\Curl\bin\JsonHttpCurlDriver; class AmazonCurl extends BaseCurl { public function __construct() { $this->curl = new Curl(new JsonHttpCurlDriver());//假设亚马逊返回的是 json 数据 } public function getProductList() { return $this->curl->get('http://amazon.com/get-product-list',[]); } } -
外部调用
$amazonCurl = new AmazonCurl(); return $amazonCurl->getProductList(); -
说明
如果哪天亚马逊的接口返回变成了
XML数据,我们只用将AmazonCurl构造函数中的JsonHttpCurlDriver替换为XMLHttpCurlDriver即可。后续如果新增了其他格式的数据返回,支持新数据只用实现两个东西:1. 继承于
AbstractHttpCurlDriver的驱动类。2. 在ApiData2ArrayFactory实现改格式转array的方法。
本作品采用《CC 协议》,转载必须注明作者和本文链接
关于 LearnKu
第一次发布文章,希望各位大佬能轻拍 :satisfied: :satisfied: :satisfied: :satisfied:
很好,基本思路不错,既然有了自己的思考,可以多看看现有的 设计模式,互通有无,同时可以避免前人踩过的坑。不过,有一个地方我觉得比较值得注意:
既然已经有了 Interface,可以避免 Abstract Class 的存在;或者直接使用 Abstract Class。
另外,Abstract Class 和 Interface 的用途是完全不同的,尽管都是「抽象」。可以自行谷歌了解一下。
最后,以我的经验来看,尽可能多使用 Interface,避免 Abstract Class。
以上信息仅供参考。
@Wi1dcard 我的想法是初步定义好
Interface,先实现一个用Guzzle发送请求的抽象类,后续可能会有别的请求方式,比如Soap,到时候Soap的抽象类也继承于这个Interface。@minororange
那这个抽象类的意义何在?为何不直接实现一个 GuzzleDriver Class?
这里说法不太准确,应当是:实现接口(Implement a Interface)或继承类(Inherit a Class)。
另外,关于请求的话,其实有现成的 PSR-7 可以参照。
@Wi1dcard :joy: 好的,其实我对
Interface的定义也不是很了解,一直把他当做一个类的规范@minororange Interface 可用于要求某个类具备某某能力。例如:
Serializable、ArrayAccess等。多个能力组合实现,便形成了一个真实的类。Abstract Class 通常则用于表述某个类最根本「是什么」,就像你前面引用所讲的「表示的是 "is a" 关系」。个人拙见。@Wi1dcard 嗯嗯,多谢大佬指点
@lovecn guzzle 封装了一遍
json_decode,解析错误会有异常,原生的没有异常。@minororange 谢谢,guzzle\src\functions.php 文件找到了
没有找到App\Curl\BaseCurl.php类啊?
@YLR 这个类是业务基础类,没有代码,方便后续扩展用的
@minororange 谢谢了,你回复好快!
JsonHttpCurlDriver(),修改为外部传递是不是更好些呢?@leung0826
AmazonCurl已经到业务层了,你指的外部传入是什么@leung0826 恰巧在线 :laughing:
@minororange 回复都能回复歪来?/滑稽
@li-luo-ao /滑稽
@minororange 类似于这样
思路很不错,
isXml方法中,只有create没有free大量调用,会有内存累积问题@Hachiko :kissing_heart: 感谢老大指点,转
xml的方法和判断都是百度的 :joy: :joy: 没有用过$this->curl = new Curl(new JsonHttpCurlDriver()); 请问JsonHttpCurlDriver这里面怎么写啊