dongmen1860 2015-09-07 13:20
浏览 28

报告解决方案的多态性和类结构

I'm coding a report builder in PHP. These reports will either be shown on a web page, emailed or sent through some other communication line. I'd like to support different types of report data (currently text and tabular).

I've built this and it works, the main classes are:

  • ReportBuilder
  • ReportMailer
  • ITextReport (an interface)
  • ITabularReport (an interface)

My concern is that my solution is not extensible, due to my implementation of 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;
}

My problem:

If I want to add a new report type (say IGraphReport), I need to modify ReportBuilder to accept this new report type, and then parse it in the ReportBuilder::getData method. This violates the Open and Closed principle, and just seems wrong.

Some solutions I considered:

  1. Creating a more generic interface IReport, with a render method on it, and replacing the addTextReport and addTabularReport in ReportBuilder to a more generic addReport. However, the different communication channels require different methods of rendering the data so the render method somehow accepts instruction on how to format the data. I'm not sure if this is the cleanest solution.

    1. Letting the communication channel decide on how to render reports, but then I envision a number of if statements checking the type of the report: if($report instanceof ITabularReport) { // handle }, which would then lead me to "replace conditionals with polymorphism" and take me back to point 1.

I'm not sure how to refactor. Any thoughts?

  • 写回答

1条回答 默认 最新

  • dsa1230000 2015-09-07 13:40
    关注

    Having an addTextReport and addTabularReport method seems like you're tying yourself to the implementation logic. Why not just have an addReport method?

    Have each type of Report adhere to a contract (interface) which implements the getData method. I.e. delegate the responsibility of how the data is returned, to the class.

    ReportBuilder

    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;
    }
    

    ReportContract

    interface ReportContract
    {
        public function getData();
    }
    

    ITextReport

    class ITextReport implements ReportContract
    {
        public function getData()
        {
            // return some data
        }
    }
    

    Now, each new type of report (Graph for example) simply has to implement a getData method and your base ReportBuilder class requires no changes or refactoring to support it.

    评论

报告相同问题?

悬赏问题

  • ¥15 如何在scanpy上做差异基因和通路富集?
  • ¥20 关于#硬件工程#的问题,请各位专家解答!
  • ¥15 关于#matlab#的问题:期望的系统闭环传递函数为G(s)=wn^2/s^2+2¢wn+wn^2阻尼系数¢=0.707,使系统具有较小的超调量
  • ¥15 FLUENT如何实现在堆积颗粒的上表面加载高斯热源
  • ¥30 截图中的mathematics程序转换成matlab
  • ¥15 动力学代码报错,维度不匹配
  • ¥15 Power query添加列问题
  • ¥50 Kubernetes&Fission&Eleasticsearch
  • ¥15 報錯:Person is not mapped,如何解決?
  • ¥15 c++头文件不能识别CDialog