官术网_书友最值得收藏!

Open/closed principle

The open/closed principle states that a class should be open for extension but closed for modification, as per the definition found on Wikipedia:

"software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification"

The open for extension part means that we should design our classes so that new functionality can be added if needed. The closed for modification part means that this new functionality should fit in without modifying the original class. The class should only be modified in case of a bug fix, not for adding new functionality.

The following is an example of a class that violates the open/closed principle:

class CsvExporter {
    public function export($data) {
        // Implementation...
    }
}

class XmlExporter {
    public function export($data) {
        // Implementation...
    }
}

class GenericExporter {
    public function exportToFormat($data, $format) {
        if ('csv' === $format) {
            $exporter = new CsvExporter();
        } elseif ('xml' === $format) {
            $exporter = new XmlExporter();
        } else {
            throw new \Exception('Unknown export format!');
        }
        return $exporter->export($data);
    }
}

Here we have two concrete classes, CsvExporter and XmlExporter, each with a single responsibility. Then we have a GenericExporter with its exportToFormat method that actually triggers the export function on a proper instance type. The problem here is that we cannot add a new type of exporter to the mix without modifying the GenericExporter class. To put it in other words, GenericExporter is not open for extension and closed for modification.

The following is an example of refactored implementation, which complies with OCP:

interface ExporterFactoryInterface {
    public function buildForFormat($format);
}

interface ExporterInterface {
    public function export($data);
}

class CsvExporter implements ExporterInterface {
    public function export($data) {
        // Implementation...
    }
}

class XmlExporter implements ExporterInterface {
    public function export($data) {
        // Implementation...
    }
}

class ExporterFactory implements ExporterFactoryInterface {
    private $factories = array();

    public function addExporterFactory($format, callable $factory) {
          $this->factories[$format] = $factory;
    }

    public function buildForFormat($format) {
        $factory = $this->factories[$format];
        $exporter = $factory(); // the factory is a callable

        return $exporter;
    }
}

class GenericExporter {
    private $exporterFactory;

    public function __construct(ExporterFactoryInterface $exporterFactory) {
        $this->exporterFactory = $exporterFactory;
    }

    public function exportToFormat($data, $format) {
        $exporter = $this->exporterFactory->buildForFormat($format);
        return $exporter->export($data);
    }
}

// Client
$exporterFactory = new ExporterFactory();

$exporterFactory->addExporterFactory(
'xml',
    function () {
        return new XmlExporter();
    }
);

$exporterFactory->addExporterFactory(
'csv',
    function () {
        return new CsvExporter();
    }
);

$data = array(/* ... some export data ... */);
$genericExporter = new GenericExporter($exporterFactory);
$csvEncodedData = $genericExporter->exportToFormat($data, 'csv');

Here we added two interfaces, ExporterFactoryInterface and ExporterInterface. We then modified the CsvExporter and XmlExporter to implement that interface. The ExporterFactory was added, implementing the ExporterFactoryInterface. Its main role is defined by the buildForFormat method, which returns the exporter as a callback function. Finally, the GenericExporter was rewritten to accept the ExporterFactoryInterface via its constructor, and its exportToFormat method now builds the exporter by use of an exporter factory and calls the execute method on it.

The client itself has taken a more robust role now, by first instantiating the ExporterFactory and adding two exporters to it, which it then passed onto GenericExporter. Adding a new export format to GenericExporter now, no longer requires modifying it, therefore making it open for extension and closed for modification. Again, this is by no means a universal formula, rather a concept of possible approach towards satisfying the OCP.

主站蜘蛛池模板: 建阳市| 盐津县| 岚皋县| 和平县| 平山县| 芜湖市| 华宁县| 合作市| 阳高县| 辽宁省| 德阳市| 噶尔县| 望谟县| 公安县| 合肥市| 辽阳市| 巴林左旗| 凉城县| 沈阳市| 滨海县| 木兰县| 乌兰察布市| 鸡东县| 海丰县| 永定县| 平谷区| 奉节县| 资溪县| 维西| 龙川县| 务川| 温州市| 石泉县| 绥化市| 武威市| 武穴市| 腾冲县| 鲜城| 禹城市| 石棉县| 麟游县|