jjzjj

php - 在 OO 上下文中建模更改

coder 2024-04-20 原文

Formal semantics of an Object-oriented programming language include encapsulated state. Is there a use-case for encapsulating a potential change, previous to the state change? Although the following examples are in PHP, please also think language-agnostic in your answer.

背景

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

“图层”对象

想象一下 object拥有以下对象:

  • 图像图层
  • 背景图层
  • 文本图层
  • 音频层
  • (N)层

为了改变ImageLayer ,API 要求:

  • 身份证
  • 新图片的网址

为了改变TextLayer ,API 要求:

  • 身份证
  • 您要对其进行哪些更改(是字体、大小、文本内容吗?)
  • 新值(value)

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

  • 身份证
  • 比特率
  • 新音频文件的url
  • 一些其他值

我的考虑

我本来考虑加上Client::updateImageLayer() , Client::updateTextLayer() , 但后来意识到 Client给定(N)Layer,对象可能会呈指数级增长 future 。

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

最后,这是我一直在考虑的另一个选择:a 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界面。

  • 也许我不是解决这个问题的最佳方式?我的解决方案有什么不好的吗。

  • 在使用我的解决方案或您建议的解决方案时,是否有我正在使用或应该考虑的模式?我对好的代码很感兴趣。

最佳答案

根据您的描述,在我看来您需要一个 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);
}

例如实现类似这样的东西。 App 的 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
}

关于php - 在 OO 上下文中建模更改,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24799622/

有关php - 在 OO 上下文中建模更改的更多相关文章

  1. ruby-on-rails - Ruby on Rails 迁移,将表更改为 MyISAM - 2

    如何正确创建Rails迁移,以便将表更改为MySQL中的MyISAM?目前是InnoDB。运行原始执行语句会更改表,但它不会更新db/schema.rb,因此当在测试环境中重新创建表时,它会返回到InnoDB并且我的全文搜索失败。我如何着手更改/添加迁移,以便将现有表修改为MyISAM并更新schema.rb,以便我的数据库和相应的测试数据库得到相应更新? 最佳答案 我没有找到执行此操作的好方法。您可以像有人建议的那样更改您的schema.rb,然后运行:rakedb:schema:load,但是,这将覆盖您的数据。我的做法是(假设

  2. ruby-on-rails - 建模收藏夹 - 2

    我希望将Favorite模型添加到我的User和Link模型。业务逻辑用户可以有多个链接(即可以添加多个链接)用户可以收藏多个链接(他们自己的或其他用户的)一个链接可以被多个用户收藏,但只有一个所有者我对如何为这种关联建模以及在模型就位后如何创建用户收藏夹感到困惑?classUser 最佳答案 下面的数据模型怎么样:classUser:destroyhas_many:favorite_links,:through=>:favorites,:source=>:linkendclassLink:destroyhas_many:favor

  3. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  4. ruby - Capistrano 3 在任务中更改 ssh_options - 2

    我尝试使用不同的ssh_options在同一阶段运行capistranov.3任务。我的production.rb说:set:stage,:productionset:user,'deploy'set:ssh_options,{user:'deploy'}通过此配置,capistrano与用户deploy连接,这对于其余的任务是正确的。但是我需要将它连接到服务器中配置良好的an_other_user以完成一项特定任务。然后我的食谱说:...taskswithoriginaluser...task:my_task_with_an_other_userdoset:user,'an_othe

  5. ruby - 更改 ActiveRecord 中对象的类 - 2

    假设我有一个FireNinja我的数据库中的对象,使用单表继承存储。后来才知道他真的是WaterNinja.将他更改为不同的子类的最干净的方法是什么?更好的是,我很想创建一个新的WaterNinja对象并替换旧的FireNinja在数据库中,保留ID。编辑我知道如何创建新的WaterNinja来self现有FireNinja的对象,我也知道我可以删除旧的并保存新的。我想做的是改变现有项目的类别。我是通过创建一个新对象并执行一些ActiveRecord魔法来替换行,还是通过对对象本身做一些疯狂的事情,或者甚至通过删除它并使用相同的ID重新插入来做到这一点,这是问题的一部分。

  6. python - 如何读取 MIDI 文件、更改其乐器并将其写回? - 2

    我想解析一个已经存在的.mid文件,改变它的乐器,例如从“acousticgrandpiano”到“violin”,然后将它保存回去或作为另一个.mid文件。根据我在文档中看到的内容,该乐器通过program_change或patch_change指令进行了更改,但我找不到任何在已经存在的MIDI文件中执行此操作的库.他们似乎都只支持从头开始创建的MIDI文件。 最佳答案 MIDIpackage会为您完成此操作,但具体方法取决于midi文件的原始内容。一个MIDI文件由一个或多个音轨组成,每个音轨是十六个channel中任何一个上的

  7. ruby-on-rails - 有没有一种工具可以在编码时自动保存对文件的增量更改? - 2

    我最喜欢的Google文档功能之一是它会在我工作时不断自动保存我的文档版本。这意味着即使我在进行关键更改之前忘记在某个点进行保存,也很有可能会自动创建一个保存点。至少,我可以将文档恢复到错误更改之前的状态,并从该点继续工作。对于在MacOS(或UNIX)上运行的Ruby编码器,是否有具有等效功能的工具?例如,一个工具会每隔几分钟自动将Gitcheckin我的本地存储库以获取我正在处理的文件。也许我有点偏执,但这点小保险可以让我在日常工作中安心。 最佳答案 虚拟机有些人可能讨厌我对此的回应,但我在编码时经常使用VIM,它具有自动保存功

  8. ruby - 在 Ruby 中,在类方法的上下文中,什么是实例变量和类变量? - 2

    如果我有以下一段Ruby代码:classBlahdefself.bleh@blih="Hello"@@bloh="World"endend@blih和@@bloh到底是什么?@blih是Blah类中的一个实例变量,@@bloh是Blah类中的一个类变量,对吗?这是否意味着@@bloh是Blah的类Class中的一个变量? 最佳答案 人们似乎忽略了该方法是类方法。@blih将是常量Bleh的类Class实例的实例变量。因此:irb(main):001:0>classBlehirb(main):002:1>defself.blehirb

  9. ruby - 是否可以将 IRB 提示配置为动态更改? - 2

    我想在IRB中浏览文件系统并让提示更改以反射(reflect)当前工作目录,但我不知道如何在每个命令后进行提示更新。最终,我想在日常工作中更多地使用IRB,让bash溜走。我在我的.irbrc中试过这个:require'fileutils'includeFileUtilsIRB.conf[:PROMPT][:CUSTOM]={:PROMPT_N=>"\e[1m:\e[m",:PROMPT_I=>"\e[1m#{pwd}>\e[m",:PROMPT_S=>"FOO",:PROMPT_C=>"\e[1m#{pwd}>\e[m",:RETURN=>""}IRB.conf[:PROMPT_MO

  10. ruby - Watir 更改 Mozilla Firefox 首选项 - 2

    我正在使用Watir运行一个Ruby脚本来为我自动化一些事情。我试图自动将一些文件保存到某个目录。因此,在我的Mozilla设置中,我将默认下载目录设置为桌面并选择自动保存文件。但是,当我开始运行我的脚本时,这些更改并没有反射(reflect)出来。似乎首选项恢复为默认值。我已经包括以下内容require"rubygems"#Optional.require"watir-webdriver"#Forwebautomation.require"win32ole"#Forfilesavedialog.并打开一个新的firefox实例:browser=Watir::Browser.new(:

随机推荐