【问题标题】:Proper way to implement "layered" class logic实现“分层”类逻辑的正确方法
【发布时间】:2014-02-26 19:31:00
【问题描述】:

我必须承认,我不知道我的设计模式是否存在问题、过度思考甚至可能只是类命名,所以欢迎任何建议。这一定是一个简单的问题,但我什至很难用语言来解释它(语言障碍),所以我会尝试用代码来做。

例如我正在尝试逐字节迭代网络数据流(该任务与问题完全无关,只是为了更好地解释伪代码,请不要说我使用了错误的工具来做到这一点) .基类看起来像这样:

class BaseNetworkIterator implements Iterator {

    protected $stream;
    protected $currentByte;

    public function __construct() {
        $this->stream = open_network_stream();
    }

    /** .... * */
    public function next() {
        $this->currentByte = $this->stream->getByte();
    }

    public function current() {
        return $this->currentByte;
    }

}

现在想象一下,这个类中的逻辑本身就很复杂(每个方法不是一行,还有更多的辅助方法),但我需要做的更多。例如,我需要在每个字节中反转位顺序。我不想在基类中包含这个逻辑,因为它已经很大了,所以我扩展了它:

class ReverseByte extends BaseNetworkIterator {
    /** .... * */
    public function next() {
        parent::next();
        $this->currentByte = $this->reverseStreamByte($this->currentByte);
    }

    protected function reverseStreamByte($byte) {
        /** .... * */
    }
}

此外,我想完全跳过此流中的所有换行符(反转):

class SkipNewLine extends ReverseByte {
    /** .... * */
    public function next() {
        do {
            parent::next();
        } while ($this->currentByte === "\n");
    }

}

如果我们得到两个连续的字节(在所有这些操作之后 - 它们可能被换行符等分隔)等于“否”,那么我们必须再次反转一个后续字节(不要想它:只是一些东西使用所有先前的逻辑和在先前类之一中定义的受保护方法):

class HandleDoubleReverseKeyword extends SkipNewLine {

    const DOUBLE_REVERSE_KEYWORD = 'NO';

    /** .... * */
    public function next() {
        parent::next();
        if ($this->getTwoCharsCache() === self::DOUBLE_REVERSE_KEYWORD) {
            $this->currentByte = $this->reverseStreamByte($this->currentByte);
            $this->clearTwoCharsCache();
        } else {
            $this->saveMaxTwoCharsCache($this->currentByte);
        }
    }

    private function saveMaxTwoCharsCache($newChar) {
        /** .... * */
    }

    private function getTwoCharsCache() {
        /** .... * */
    }

    private function clearTwoCharsCache() {
        /** .... * */
    }

}

现在,虽然逻辑在类之间被很好地划分为我的口味(每个连续的类做一些完全不同和孤立的事情,很容易看出每一层做了什么),但处理起来很乱。如果我们在中间添加新的“过滤器”,我们必须更改后面的类扩展的内容......命名开始变得疯狂,因为“HandleDoubleReverseKeyword”甚至远程无法解释这个类的真正作用(即使我们在“NetworkIterator “命名空间)。我真的不明白“最终”课程应该是什么样子。

我正在考虑使用特征,因为在那里命名更容易,但它们会导致更多问题然后解决(执行顺序、代码完成等)。

我也在考虑使用类似 Yii 的“行为/事件”模式(类似于:base next() 方法连续调用一个类/方法数组,并且一些外部源可以向该数组添加更多类/方法,而不是扩展基类)但是属性/方法的可见性存在很大问题(这是一个问题,基本上我必须公开所有属性和方法),更重要的是不清楚如何正确访问其他附加类的方法。

好的,这可能还不是很清楚,因此感谢任何反馈。我知道这个“问题”中没有问号,但我希望我的意图是明确的,唯一想到的是:如何正确地进行这种设计?

【问题讨论】:

    标签: php oop design-patterns inheritance overriding


    【解决方案1】:

    我的 2 美分:

    1. 流应该与迭代器类及其子类分开。迭代器定义了如何访问数据,每个迭代器实例应该只保存对数据的引用,而不是实际的数据。

    2. HandleDoubleReverseKeyword 类中的逻辑是关于如何处理数据,而不是如何访问数据;并且动作是根据数据是什么来决定的,因此该类不应继承自迭代器类;这里我认为我们可以使用状态模式。

    所以处理流的代码可能是这样的:

    $stream = open_network_stream();
    $iterator it = new Iterator(&stream);
    $data = it->next(); 
    while(data){
      switch(data->value){
         case 'A': AcitionA->action();break;
         case 'B': AcitionB->action();break;
         }
    $data = it->next();
    }
    

    【讨论】:

    • 非常感谢您的宝贵时间!这种方法的问题在于,最后我想要一个“干净的”迭代器进行进一步处理。我的意思是,在我的设计中,最后一层“FinalNetworkIterator”将在另一个代码中用于更复杂的分析(令牌、解析、逻辑等),这个迭代器的工作仅仅是从最原始的障碍中“清理”流.如果我实现这样的逻辑,那么所有这些都将集中在一个地方,并且很难将它们分开。我不希望基本的逐字节分析与解析令牌逻辑位于同一位置。
    【解决方案2】:

    我认为你从错误的角度来处理这个问题。

    您当前的层次结构极大地限制了每个组件的可重用性,因为它们都被实现为仅对字节流迭代器的结果起作用。例如,如果您需要实现一个文件迭代器,然后跳过每个新行,那么您不可能重用现有的SkipNewLine,因为它仅适用于反向字节迭代器的结果。

    另一个谬误是,如果不重写整个类结构,就无法控制每个步骤发生的顺序。甚至像将 SkipNewLine 类移到 ReverseByte 之前(或 HandleDoubleReverseKeyword 之后)这样简单的事情也是重构的噩梦。

    我建议您将迭代中的每个步骤实现为一个单独的类,该类仅作用于通用流,并且不对源流的状态做出任何假设。您可以将每个组件的实例注入您的BaseNetworkIterator,并相互独立地使用它们。

    这将进一步使您能够在了解迭代的全局状态的情况下对每个单独的状态采取行动。

    【讨论】:

    • 非常感谢您的宝贵时间!我知道这个设计真的很糟糕,我完全同意你的观点并且我知道它们,只是这个设计的问题列表太长了,我认为写下来没有用。关于您的建议:“仅作用于通用流”-我不知道如何正确地做到这一点。例如,在我糟糕的设计中,HandleDoubleReverseKeyword 将在像“N\n\n\nO”这样的流上正常工作,因为删除换行符是在前一层完成的,我怎样才能让它独立?
    • 那么有一个问题是我希望能够重用上一层添加的方法(比如“reverseStreamByte”),也许我从错误的角度来处理这个问题?
    • 这个想法是您一次处理一个流(您可以一次处理一个字节,具体取决于您执行的操作类型)。如果步骤之间存在依赖关系(例如 HandleDoubleReverseKeyword 不需要换行),则该问题应由了解所有步骤的协调器类处理,而不是由每个特定类处理。
    • 假设您一次处理一个字节:这个字节需要经过哪些步骤?将每个步骤隔离到它自己的类中,以使它们可重用,并确保它们不会对前面或后面的步骤做出假设。
    • 一开始我尝试这样做,但我写的内容要复杂得多。最大的问题是我需要在其他“处理”类中重用“处理”类中编写的大量代码。如果“reverseStreamByte”问题可以通过其他方式解决(仍然是丑陋的方式),那么当我需要在另一个“处理”类中重用“TwoCharsCache”来制作和处理遇到“OK”消息时该怎么办?创建全新的缓存?我必须重用我在不同“层”上添加的很多东西,它们以更多方式相互依赖,然后是顺序。它本身就错了吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-05
    • 2015-05-14
    • 1970-01-01
    相关资源
    最近更新 更多