我正在使用PHP编写报表生成器。这些报告将显示在网页上,通过电子邮件发送或通过其他通信线路发送。我想支持不同类型的报告数据(目前是文本和表格)。
我已经构建了这个并且它可以工作,主要类是:
我担心的是,由于我实施了ReportBuilder,我的解决方案无法扩展:
public function addTextReport(ITextReport $report)
{
$this->textReports[] = $report;
return $this;
}
public function addTabularReport(ITabularReport $report)
{
$this->tabularReports[] = $report;
return $this;
}
public function getData()
{
$data = ['tabularReports' => [], 'textReports' => []];
foreach($this->tabularReports as $report)
{
$data['tabularReports'][] = [
'title' => $report->getTitle(),
'keys' => $report->getDataTitles(),
'data' => $report->getData(),
];
}
foreach($this->textReports as $report)
{
$data['textReports'][] = [
'title' => $report->getTitle(),
'content' => $report->getContent(),
];
}
return $data;
}
我的问题:
如果我想添加新的报告类型(例如IGraphReport
),我需要修改ReportBuilder
以接受此新报告类型,然后使用ReportBuilder::getData
方法解析它。这违反了开放和封闭原则,似乎错了。
我考虑过的一些解决方案:
使用IReport
方法创建更通用的界面render
,并将addTextReport
中的addTabularReport
和ReportBuilder
替换为更通用的addReport
。但是,不同的通信通道需要不同的呈现数据的方法,因此render
方法以某种方式接受有关如何格式化数据的指令。我不确定这是否是最干净的解决方案。
if
语句来检查报告的类型:if($report instanceof ITabularReport) { // handle }
,这会导致我“替换条件“多态”并带我回到第1点。我不确定如何重构。有什么想法吗?
答案 0 :(得分:1)
使用addTextReport
和addTabularReport
方法似乎与实施逻辑联系在一起。为什么不采用addReport
方法?
让每种类型的Report
遵守实现getData
方法的合同(接口)。即将数据返回方式的责任委托给班级。
private $reports;
public function addReport(ReportContract $report)
{
$this->reports[] = $report;
return $this;
}
public function getData()
{
$data = [];
foreach($this->reports as $report) {
$data[] = $report->getData();
}
return $data;
}
interface ReportContract
{
public function getData();
}
class ITextReport implements ReportContract
{
public function getData()
{
// return some data
}
}
现在,每种新类型的报告(例如Graph)都必须实现getData
方法,而基础ReportBuilder类不需要更改或重构来支持它。