Does the factory design pattern contradict the second law of SOLID principles

In a dense approach to the use of templates in design, I came across a contradiction between the factory template and the 2 law of SOLID principles, which says about open / closed (Open-closed):"software entities should be open for expansion, but closed for modification."

Let's say we already have a code (listed below) based on the Factory pattern(template), which stores data and returns it in Json format. Let's say we still need to give the data in another format. We create a CsvEncode class based on the interface. But what about the Notepad factory-add the public function getCsvEncode ()

Is adding a method to a class a violation of the second law of SOLID principles?

Or in this case, the programmer should review the code and rewrite it for an abstract factory? I'm just starting to use design patterns, and that they are appropriate to apply only where necessary, i.e. to throw off the responsibility of execution on them code, to be able to create independent libraries, I need understanding. Maybe I misunderstood the factory template in the context of SOLID principles? I have seen that the factory method is sometimes set to switch case.And depending on the parameter, they return an instance of the corresponding class - here I found a contradiction, if you need to create another instance of the class , you will have to get into the code and hardcode

<?php
//я так понимаю что шаблон фабрика это как правило связь один к одному обоих классов проиходящих от заданных интерфейсов

//итак задача - у нас есть данные в виде массива
//необходимо получить из этого массива заголовок,подвал, контент
//массив отдать в формате json  и csv


/*
*это interface Factory  создающий другие классы
*
*/
interface Factory
{
    /*
    *
    *@return string| NULL возвращает заголовок 
    */
    public function getHeader();
    /*
    *
    *@return string| NULL возвращает содержимое 
    */
    public function getContent();
    /*
    *
    *@return string| NULL возвращает подвал|нижний колонтитул
    */
    public function getFooter();
    /*
    *
    *@return Encode возвращает объект для управления данными в  формате Json
    */
    public function getJsonEncode();
}

/*
*это interfaceс Encode для перекодировки
*
*/
interface Encode
{
    public function getEncode();
}

/*
*класс для перкодировки данных исходящи от класса,поддерживающего интерфейс Factory
*
*@property Factory $data
*/
class JsonEncode implements Encode
{
    protected $data;
    public function __construct(Factory $obj)
    {
        $this->data=$obj;
    }
    /*
    *преобразует данные в json формат
    *
    *@return string
    */
    public function getEncode()
    {
        return  json_encode( ['head'=>$this->data->getHeader(),'content'=>$this->data->getContent(),'footer'=>$this->data->getFooter()] );
    }
}
class CsvEncode implements Encode
{
    protected $data;
    public function __construct(Factory $obj)
    {
        $this->data=$obj;
    }
    /*
    *преобразует данные в csv формат
    *
    *@return string
    */
    public function getEncode()
    {
        return  $this->data->getHeader(). PHP_EOL .$this->data->getContent(). PHP_EOL .$this->data->getFooter();
    }
}
/*
*класс для перкодировки данных исходящи от класса,поддерживающего интерфейс Factory
*
*@property string $head
*@property string $content
*@property string $footer
*/
class Notepad implements Factory{
    protected $head;
    protected $content;
    protected $footer;
    /*
    *
    *@param array $arr
    */
    public function __construct($arr){
        $this->head=$arr['head'];
        $this->content=$arr['content'];
        $this->footer=$arr['footer'];
    }
    /*
    *
    *@return string| NULL возвращает заголовок 
    */
    public function getHeader()
    {
        return $this->head;
    }
    /*
    *
    *@return string| NULL возвращает содержимое 
    */
    public function getContent()
    {
        return $this->content;
    }
    /*
    *
    *@return string| NULL возвращает подвал|нижний колонтитул
    */
    public function getFooter()
    {
        return $this->footer;
    }
    /*
    *
    *@return Encode возвращает объект для управления данными в  формате Json
    */
    public function getJsonEncode()
    {
        return new JsonEncode($this);
    }
}

$arr=['head'=>'заголовок','content'=>'содержимое','footer'=>'колонтитул'];
$a=new Notepad($arr);
echo $a->getJsonEncode()->getEncode();//выводим массив в формате json

I'm still just learning to use PHPDoc , for simplicity I didn't use it in example of checking for values, catching errors(try {} catch () {}), and automatically connecting classes :) thank you for finishing reading

Author: Mcile, 2017-02-06

1 answers

To begin with, I will make a small lyrical digression and tell you a little about the terminology.


Generally speaking, you are misinterpreting the"Factory" pattern. Factory-a generating pattern in which the creation of objects is assigned to a special object. This is what the simplest implementation of this pattern looks like:

interface FactoryInterface {
    public function createInstance();
}

class FooFactory implements FactoryInterface {
    public function createInstance() {
        return new Foo();
    }
}

class GooFactory implements FactoryInterface {
    public function createInstance() {
        return new Goo();
    }
}


class Client {
    private $factory;

    public function __construct(FactoryInterface $factory) {
        $this->factory = factory;
    }

    public function doSomething() {
        return $this->factory->createInstace()->doSomething();
    }
}

There are several modifications of this pattern at once:

The main task of the "Factory" pattern (as well as its modifications) is to encapsulate the logic of creating objects in one place.


In your case, I would not talk about the factory (and other patterns), but instead would just break down the objects by type of responsibility, according to The Principle Of Sole Responsibility (Single Responsibility Principle). For example like this:

class Document {
    public function getHeader() {/* ... */}
    public function getBody() {/* ... */}
    public function getFooter() {/* ... */}
}

interface Formatter {
    public function format(Document $doc);
}

class JsonFormatter {
    public function format(Document $doc) {
        return json_encode([
            'header' => $doc->getHeader(),
            'body' => $doc->getBody(),
            'footer' => $doc->getFooter(),
        ]);
    }
}

class CsvFormatter {
    public function format(Document $doc) {
        return sprintf(
            "header;body;footer\n%s;%s;%s",
            $doc->getHeader(),
            $doc->getBody(),
            $doc->getFooter()
        );
    }
}

$doc = new Document();
$formatter = new JsonFormatter();

echo $formatter->format($doc);

It seems to me to solve the main problem this will be more than enough for the task. In addition, such an implementation follows the Open-Closed Principal you mentioned: to create another formatting class, you do not need to change existing classes.

(As an exercise, you can check for yourself whether this code follows the rest of the SOLID principles.)

When designing, the main thing to remember is the following: patterns for patterns ' sake-the way to a big headache!

 3
Author: Dmitriy Simushev, 2017-02-06 19:20:43