PHP OOP设计-在实现泛型接口时将参数限制为特定的子类


PHP OOP design - limiting parameters to specific child classes while implementing generic interfaces

我经常做PHP项目,旨在从网页中抓取分层数据并将其保存到数据库(本质上是结构化数据-想想抓取有数据但不以结构化方式提供数据的政府网站)。每次,我都试图提出一个OOP设计,使我能够实现以下目标:

  • 如果原始网页发生变化,可以轻松替换当前的HTML解析脚本。
  • 允许轻松扩展抓取和保存的数据,因为这些项目也意味着其他人可以接受和构建。我的目标是收集"基础"数据,而其他人可能会决定包括一些额外的东西,改变它的保存方式等。

到目前为止,我还没有找到解决方案,但我得到的最接近的东西是这样的:

我为数据容器定义了一个抽象类,它将实现常见的树遍历函数:

abstract class DataContainer {
  protected $parent = NULL;
  protected $children = NULL;   
  public function getParent() {
    return $this->parent;
  }
  public function getChildren() {
    return $this->children;
  }             
}

然后是实际的数据容器。想象一下,我正在搜集有关议会会议参与情况的数据,将其细化为"会议中的具体问题"。我将有SessionContainer, SittingContainer, QuestionContainer它们都将扩展DataContainer

每个会话、坐席和问题数据都是从不同的URL中抓取的。撇开获取URL内容的机制不谈,假设我需要scraper类,它将接受容器和DOmDocument进行实际解析。所以我将定义一个像这样的通用接口:

interface Scraper {
  public function scrapeData(DOMDocument $Dom, DataContainer $DataContainer);   
}

然后,每个会话、坐席和问题都有自己的抓取器,这些抓取器实现了接口。但我也想确保他们只能接受他们想要的集装箱。所以它看起来像:

class SessionScraper implements Scraper {
  public function scrapeData(DOMDocument $DOM, SessionContainer $DataContainer) {
  }
}

最后,我将有一个通用的Factory类,它也实现了Scraper接口,并将抓取分配给相关的Scraper。这样的:

public function scrapeData(DOMDocument $DOM, DataContainer $DataContainer) {
  //get the scraper from configuration array
  $class = $this->config[get_class($DataContainer)];
  $craper = new $class();
  $class->scrapeData($DOM, $DataContainer);
}

这是代码中实际调用的类。非常类似地,我可以处理保存到DB的问题——每个数据容器都可以有它的DBSaver类,它将实现DBSaver接口。同样,所有调用都可以通过Factory类完成,该类也将实现DBSaver接口。

一切都是完美的,但问题是实现接口的类应该实现接口的精确签名。例如,方法SessionScraper::scrapeData不能只接受 SessionContainer对象,它必须接受所有DataContainer对象。但这不是故意的!

最后,问题:

    是我的设计错了,我应该以完全不同的方式构建一切吗?(如何?),或者:
  • 我的设计是OK的,只是我需要在instanceof和类似的检查方法内强制类型,而不是通过类型提示强制?

提前感谢所有的建议/批评。如果有必要的话,我非常高兴有人推翻这段代码!

Container跃入眼帘。这个名称非常通用,您可能需要更动态的名称。我想你有Data,你有classify,所以它有type

所以你应该把精确的接口硬编码到类型提示中,你应该动态地解决这个问题。

如果现在每个Container都有一个type, Scraper可以发出信号/判断它是否适用于Containertype

抓取的具体形式实际上是您对特定数据使用的策略来解析它。容器封装了这个策略,为规范化数据提供了接口。

你只需要在ContainerScraper之间添加一些逻辑/契约,这样它们就可以相互通信了。这个契约可以放在两者的接口中。

这也允许你有一个Scraper,可以处理多个types,如果你想拉伸它。

对于您的Container,也请查看SPL,您实现了一些接口,以便您可以使用迭代器(和递归迭代器)。这可能是您所指的通用结构,SPL可以提高Container类的可用性。

你不需要在OOP中硬编码所有东西,你可以保持动态,特别是在PHP中,你通常在运行时解决问题。

这也将允许您更容易地用新版本替换Scrapers。作为Scrapers现在将有一个类型的定义(如上所述),你可以在运行时解决具体的类应该做抓取,例如,动态加载它们从一个好的文件系统结构的。php文件。

我的2美分。