在面向对象的环境中建模变化

4
面向对象编程语言的正式语义包括封装状态。在状态改变之前,是否有封装潜在变化的用例?虽然以下示例是使用PHP编写的,请在回答中也考虑语言无关性。

背景

我有一个Client对象,其职责是向服务器发送请求并检索响应,并通过调用API来更改驻留在另一个服务器上的对象的状态。有几个URL,其中一个具有端点create,另一个具有端点update。问题是,update可以用于更新给定对象中的多个不同元素,每个元素都需要不同的参数。

'Layer'对象

假设object拥有以下对象:

  • ImageLayer
  • BackgroundLayer
  • TextLayer
  • AudioLayer
  • (N)Layer

为了更改ImageLayer,API需要:

  • 一个ID
  • 新图片的URL

为了改变TextLayer,API需要:

  • 一个ID
  • 要更改的内容(是字体、大小还是文本内容?)
  • 新值

现在,在你认为这可以简单地抽象出一个ID和替换值之前,请考虑AudioLayer

  • 一个ID
  • 比特率
  • 新音频文件的URL
  • 一些其他值

我的考虑

我最初考虑添加Client::updateImageLayer()Client::updateTextLayer(),但后来意识到Client对象可能会因未来的(N)Layer而变得指数级增长。

然后我考虑添加Client::updateLayer(Layer $layer, array $values),但我觉得这还不够好。

最后,这里有另一个选项我一直在考虑:一个Change对象。

更改对象

如果我创建了一个Change对象来封装对任何特定图层的更改,然后将其传递给Client,已经经过验证并准备好在请求API时发送,会怎样呢?
interface Change
{
    public function isValid();

    public function getChanges();
}

class ImageLayerChange implements Change
{
    protected $url;

    protected $id;

    public function __construct($id, $url)
    {
        $this->url = $url;
        $this->id  = $id;
    }

    public function isValid()
    {
        // Use a validator object to check url is valid etc
    }

    public function getChanges()
    {
        return array($this->id, $this->url);
    }
}

使用上述方法,Client对象可以循环一组Change对象,我甚至可以创建一个ChangeSet,通过调用isValid()确保它们都有效,并调用getChanges()将特定数组直接发送到API,因为所有内容都已经过验证。

问题

  • 我从未听说过建模更改。您对上述选项有何看法?我是否过于复杂化了事情?我喜欢能够随意添加/删除ChangeSet中的更改,并且只要它们符合Change接口,一切都能按预期工作。

  • 也许我的方法不是最佳方式?我的解决方案有什么缺点吗?

  • 在使用我的解决方案或您提出的解决方案时,我是否正在使用或应该考虑任何模式?我对良好的代码感兴趣。


看起来你发明了撤销-重做机制,是吗?:-) - Aleksei Matiushkin
@mudasobwa 啊,我没有考虑到这一点。我想它可以用来撤销未来的状态更改! :-) - Jimbo
1个回答

1
从您所描述的情况来看,我认为您需要一个API客户端和请求对象。
namespace Api;

interface Client {

    /**
     * @param string $method
     * @param string $urn
     * @param array $params
     * @return Response
     */
    public function call($method, $urn, array $params = array());
}

interface Request {

    public function isValid();

    public function make(Client $client);
}

例如,实现类似这样的内容。应用程序的 ApiClient 负责进行 API 调用,知道要定位哪个 URL。我希望 ApiClient 知道 API 的 URL,而请求将保存 URN 部分(资源名称)。它们一起形成完整的 URI。(这是可选项,只是为了预测 API 版本)。
namespace App;

class ApiClient implements \Api\Client {

    private static $url = 'https://api.yourapp.tld';

    /**
     * Just an example implementation using json (not completed)
     */
    public function call($method, $uri, array $params = array()) {
        $cUrlHandle = \curl_init(self::$url . '/' . $uri);
        \curl_setopt($cUrlHandle, CURLOPT_CUSTOMREQUEST, $method);
        \curl_setopt($cUrlHandle, CURLOPT_RETURNTRANSFER, true);

        if (!empty($params)) {
            \curl_setopt($cUrlHandle, CURLOPT_POSTFIELDS, \json_encode($params));
        }

        $response = curl_exec($cUrlHandle);

        //...
    }

}

现在我们可以创建不同类型的请求。每个请求都负责验证和打包API客户端的参数。
我注意到所有更新请求都需要id。我会使用它来处理一个对象的创建和更新请求(如果不可能,您可以将它们拆分)。
class ImageLayerRequest implements \Api\Request {

    private $id;
    private $url;
    private $apiUrn;
    private $params = array();

    /**
     * If $id provided it's an update request otherwise it'll be create
     * 
     * @param string $id
     * @param string $imageUrl
     */
    public function __construct($id, $imageUrl) {
        $this->id = $id;
        $this->url = $imageUrl;
    }

    public function isValid() {
        if ($this->id === null) {
            //validate create request
            //...

            $this->apiUrn = 'image-layer/create';
        } else {
            //validate update request
            //...
            $this->params['id'] = $this->id;
            $this->apiUrn = 'image-layer/update';
        }

        $this->params['url'] = $this->url;
    }

    public function make(\Api\Client $client) {
        return $client->call('post', $this->apiUrn, $this->params);
    }

}

对于AudioLayer:

class AudioLayerRequest implements \Api\Request {

    private $id;
    private $bitrate;
    private $url;

    //the rest is similar, just diferent parameters
}

我明白了。所以每个实现都需要指定它们的参数、apiUrn等,这是客户端读取的内容。听起来我需要一个抽象的“请求”类来指定最低要求(apiUrn、参数等)。 - Jimbo
在我看来,抽象基类没有任何问题。 - Weltschmerz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接