阅读 20

[重构实践]函数参数过多如何优雅的传入

原标题《函数参数过多如何优雅的传入》,创造于2019-05-23

好的代码一定是具有自解释功能的


参数太多如何构建?这里要介绍的方法是通过一个类似ParamsBuilder的方式,通过对象构建参数,然后传入对象的方式。先看一段代码,这段代码是选自小米推送服务器端sdk的代码。

背景

调用小米推送的Api,传递 Push Notify 的标题、描述、是否透传、推送id等参数,还有推送的对象,比如设备的别名,官方提供的可用参数有十几个,但是如何对这么多参数进行封装呢,以POST方式调用小米的接口?难道也是

<?php

$parans['name1'] = 'name';
$parans['name2'] = 'name';
$parans['name3'] = 'name';
$parans['name4'] = 'name';
$parans['name5'] = 'name';
$parans['name6'] = 'name';

// 然后调用 post接口

$sender->post($this->url,$params);

复制代码


其实小米并没有做,小米的设计更接近面向对象的。把消息体抽象成一个对象,把发送工具也抽象成一个对象,消息体可以指定字段设置内容,发送工具具有不同的方法可以指定不同的群体如 设备id、标签、设备别名,把刚刚构建好的消息体发送出去。

通过类方法构建参数:
1 可以清晰明了看到有那些参数可以设置;
2 编辑器还可以自动补全和参数类型判断。这种比之前的直接传 params数组的方式要好多了,目前我们的项目里有不少params 数组的方式要好多了,目前我们的项目里有不少 params 连PHPDoc都没有,只能从代码里看,这样成本就太高了。

设计思路

Message构建要发送的消息内容。
Builder构建发送给Android设备的Message对象,继承Message
SenderMiPush消息发送类。

Message


<?php

class Message
{
    protected $payload;                //消息内容
    protected $pass_through;            //是否透传给app(1 透传 0 通知栏信息)
    protected $notify_type;                //通知类型 
    protected $notify_id;                //0-4同一个notifyId在通知栏只会保留一条
    protected $extra;                    //可选项,额外定义一些key value
    protected $description;                //在通知栏的描述,长度小于128
    protected $title;                    //在通知栏的标题,长度小于16

    protected $fields;                //含有本条消息所有属性的数组
    protected $json_infos;

    public function __construct()
    {
        $this->extra = array();
        $this->fields = array();
    }

    public function getFields()
    {
        return $this->fields;
    }

    public function getJSONInfos()
    {
        return $this->json_infos;
    }

}
复制代码

Builder

<?php

class Builder extends Message
{
  
    public function __construct() {
        $this->notify_id = 0;
        $this->notify_type = -1;
        $this->payload = '';
        parent::__construct();
    }

    public function payload($payload) {
        $this->payload = $payload;
    }

    public function title($title) {
        $this->title = $title;
    }

    public function description($description) {
        $this->description = $description;
    }

    public function passThrough($passThrough) {
        $this->pass_through = $passThrough;
    }

    public function notifyType($type) {
        $this->notify_type = $type;
    }

    public function notifyId($notifyId) {
        $this->notify_id = $notifyId;
    }

    public function extra($key, $value) {
        $this->extra[$key] = $value;
    }

    public function build() {
        $keys = array(
            'payload', 'title', 'description', 'pass_through', 'notify_type', 'notify_id'
        );
        foreach ($keys as $key) {
            if (isset($this->$key)) {
                $this->fields[$key] = $this->$key;
                $this->json_infos[$key] = $this->$key;
            }
        }

        //单独处理extra
        $JsonExtra = array();
        if (count($this->extra) > 0) {
            foreach ($this->extra as $extraKey => $extraValue) {
                $this->fields[Message::EXTRA_PREFIX . $extraKey] = $extraValue;
                $JsonExtra[$extraKey] = $extraValue;
            }
        }
        $this->json_infos['extra'] = $JsonExtra;

    }
}
复制代码

Sender

<?php

class Sender extends HttpBase
{

    public function __construct()
    {
        parent::__construct();
    }

    // 指定别名列表群发
    public function sendToAliases(Message $message, $aliasList, $retries = 1)
    {
            $fields = $message->getFields(); // 在这里直接获取所需要发送的消息所有参数
            $jointAliases = '';
            foreach ($aliasList as $alias) {
                if (strlen($jointAliases) > 0) {
                    $jointAliases = $jointAliases . Constants::$comma;
                }
                $jointAliases = $jointAliases . $alias;
            }
            $fields['alias'] = $jointAliases;
         // 获取完参数就去发送HTTP请求
        // 第一个参数是发送的对象,第二个参数是 消息体,第三个参数是 如果失败重试的次数
        return $this->postResult(PushRequestPath::V3_ALIAS_MESSAGE(), $fields, $retries);
    }
复制代码

用法

<?php

$title = '你好';
$desc = '这是一条mipush推送消息';
$payload = '{"test":1,"ok":"It\'s a string"}';

// 关键点:构建消息体的参数
$message1 = new Builder();
$message1->title($title);  // 通知栏的title
$message1->description($desc); // 通知栏的descption
$message1->passThrough(0);  // 这是一条通知栏消息,如果需要透传,把这个参数设置成1
$message1->payload($payload); // 携带的数据
$message1->extra(Builder::notifyForeground, 1); // 应用在前台是否展示通知
$message1->notifyId(2); // 通知类型
// 关键点:下面的方法将所有所需参数读取类属性并放至数组中
$message1->build();

$sender = new Sender();
$aliasList = array('alias1', 'alias2'); // 这两个是要发送的对象

$sender->sendToAliases($message1, $aliasList);
复制代码

总结

  1. 少于5个参数个数尽量一字排开,清晰明了,并且在PHPDoc注释各个参数含义,并且最好全部都是 简单变量,如整型、字符、布尔型等;
  2. 不建议:传多个参数的时候,有简单变量,有数组甚至对象,这种情况一般是从数据库查询出来的数据不想再组装就直接简单粗暴传过来;
  3. 函数的功能应该简单、微小,传值少,返回类型也尽量简单,以满足 单一原则;
  4. 合理给函数设定访问权限,遵守“开-闭原则”,只在类内部调用的使用private或者protected;
  5. 函数参数一般都是外部传入,除了route的controller层可以做接收,其他层绝不能再用GET_GET _POST获取用户参数
  6. 合理注释,方便调用函数时编辑器可以智能提示和补全或参数检查,写注释没坏处;
  7. 学习别人的代码是最好的提升方式

扩展思考

1、这个用法让我联想到Laravel Eloquent 之前修改数据的时候,可以先从User模型里find()出一条数据$user_info = User:find(1),得到的数据对象,如果我们想修改数据可以指定字段如 $user_info->user_name = '新名字';``$user_info->save(); 这其中是否有相同的设计模式?

要了解Eloquent >>> learnku.com/articles/63…

2、Java编程中有一层是DTO,负责数据传输对象的,也是否是一致的设计思路呢?

要了解DTO>>> www.cnblogs.com/xt0810/p/36…

完整代码包


MiPush_Server_Php_20190225.zip