Если немного погуглить, то про паттерны можно найти довольно много информации. Например, на хабре тут, тут, и тут, в большой советской энциклопедии вики и книжках GOF, Фаулер и Кериевски (последняя, кстати, наименее известная, но на мой взгляд самая интересная), .... Зачем нужна еще одна статья?

В большинстве источников рассказывается какие есть паттерны и как их реализовывать, но после прочтения остается неясным когда и как их применять, что в итоге приводит к аду синглтонов неправильному их использованию.

Зачем нужны паттерны?


Согласно одному из определений «это лишь пример решения задачи, который можно использовать в различных ситуациях.» Неясно только какое решение каких задач и в каких ситуациях его использовать делаем все синглтонами!.

На мой взгляд, главная задача шаблонов проектирования — это определение и выделение ответственности. Для порождающих паттернов — это создание объектов. Таким образом, независимо от того, какая поставлена задача, если необходимо при ее решении создавать объекты, то можно применять порождающие паттерны.

Фабричный метод, фабрики, абстрактные фабрики


Самое четкое и понятное определение, которое я нашел, описано в книге Кириевски:
  • Creation method (порождающий метод) — статический или нестатический метод, который создает и возвращает объекты.
  • Factory method — это нестатический метод, возвращающий тип базового класса или интерфейса и реализованный в иерархии классов для обеспечения возможности полиморфного создания.
  • Factory (Фабрика) — это класс, реализующий один или несколько методов создания.
  • Abstract Factory (абстрактная фабрика) — это интерфейс для создания семейств связанных или зависимых объектов без указания их конкретных классов.

Все. Никаких диаграм и запутанных примеров.
  • Если нам нужно создавать объект, то используем порождающий метод.
  • Если нам нужно создавать другие объекты (реализующие тот же интерфейс или унаследованные от базового класса) или те же объекты, но другим способом в дочерних классах, то делаем метод виртуальным и переопределяем в дочерних классах (реализуем фабричный метод).
  • Если нам нужно использовать порождающий метод из нескольких мест, то выделяем его в отдельный класс (фабрику).
  • Если нам нужна возможность создавать объекты разными способами, то выделяем из фабрики интерфейс (абстрактная фабрика) и используем его.

Получилось немного утрированно. Есть более хитрые случаи использования этих паттернов и способы их реализации, но в большинстве случаев, особенно начинающим программистам, можно действовать по этой схеме.

Хочу еще отметить, что я видел как абстрактную фабрику иногда называют фабрикой фабрик, что неправильно. Абстрактная фабрика позволяет использовать интерфейс и не привязываться к конкретной реализации, а не создавать объект создающий объекты.

Порождающий метод


Все-таки на примерах понятнее.

Допустим, наше приложение позволяет генерировать сообщения, причем сообщения могут быть двух типов: текст и картинка с подписью. Для того, чтобы создавать сообщения этих двух типов, мы можем использовать два конструктора.

public class Message
{
    public String Text{get; set;}
    public Image Picture{get; set;}

    public Message(String text)
    {
        Text = text;
    }

    public Message(String text, Image image)
    {
        Text = text;
        Image = image;
    }
}

У этого подхода есть масса недостатков. Из названия конструкторов непонятно чем они отличаются. Есть ли разница вызывать конструктор без картинки или передать null вместо картинки? Необходима дополнительная документация. Если мы захотим поменять поведение конструктора (например, добавить какой-нибудь префикс в сообщение) нам нужно менять либо все вызовы этого конструктора либо вводить дополнительные параметры, что усложняет понимание кода и повышает вероятность ошибок.

С другой стороны, у нас есть явная ответственность — «создание новых сообщений разных типов». Выделяем ее в отдельные методы.

public class Message
{
    public String Text{get; set;}
    public Image Picture{get; set;}

    public Message(String text, Image image)
    {
        Text = text;
        Image = image;
    }

    public static Message CreateTextMessage(String text)
    {
        return new Message(text, null);
    }

    public static Message CreateImageMessageWithSubtitle(String text, Image image)
    {
        return new Message(text, image);
    }
}

Теперь из названия методов ясно для чего они нужны и какой из них использовать. Если мы захотим создавать сообщения с префиксом, то мы можем добавить новый метод.

Фабрика


Сразу видно, что ответственность создавать объекты никак не относится к основной ответственности класса Message. Если это не создает никаких трудностей, то можно оставить так, в противном случае выделяем эту ответственность в отдельный класс.

public class Message
{
    public String Text{get; set;}
    public Image Picture{get; set;}

    public Message(String text, Image image)
    {
        Text = text;
        Image = image;
    }
}

static class MessageFactory
{   
    public static Message CreateTextMessage(String text)
    {
        return new Message(text, null);
    }

    public static Message CreateImageMessageWithSubtitle(String text, Image image)
    {
        return new Message(text, image);
    }
}

Абстрактная Фабрика


У нас осталась жесткая зависимость на конкретную реализацию фабрики. Это вызовет затруднения, если мы захотим создавать сообщения каким-либо другим способом или подменять реализацию моками при тестировании.

Если нам важна сама ответственность создания объектов и мы не зависим от того, как эта ответственность реализована, используем абстрактную фабрику.
interface IMessageFactory
{
    Message CreateMessage();   
}

class TextMessageFactory: IMessageFactory
{
    private String text;
    public TextMessageFactory(String text)
    {
        this.text = text;
    }

    public Message CreateMessage()
    {
        return new Message(text, null);
    }
}

class ImageWithSubtitleMessageFactory: IMessageFactory
{
    private String text;
    private Image image;
    public TextMessageFactory(String text, Image image)
    {
        this.text = text;
        this.image = image;
    }
    public static Message CreateMessage()
    {
        return new Message(text, image);
    }
}

Пример с сообщениями не самый подходящий для абстрактной фабрики, но надеюсь, что он дает представление о том как она работает.

P.S. Это моя первая статья и она заняла больше времени, чем я рассчитывал. Поэтому я не включил в нее паттерны Builder и синглтон, которые собирался включить. Если статья окажется интересной, то напишу продолжение.

Комментарии (53)


  1. agent10
    25.08.2015 15:39
    +1

    Без обид… но так и не получил ответ на ваш же вопрос «Зачем нужна еще одна статья?»
    К тому же довольно в большом кол-ве источников имеются детальные примеры когда и как их применять. И не вакуумный Message, а пример реальных программ и систем.


    1. pssam Автор
      25.08.2015 15:54

      Я попытался максимально простым языком объяснить когда используются порождающие паттерны и проследить цепочку порождающий метод -> фабрика -> абстрактная фабрика. Примеры реальных программ как правило слишком сложные для начинающих программистов. В том же Гофе вначале нужно пробиться через UML, чтобы понять что вообще происходит и остается непонятным когда эти паттерны применять в реальной жизни.


  1. lair
    25.08.2015 15:42
    +2

    Из вашего примера так и не понятно, зачем же вместо простого и понятного конструктора делать фабричный метод.

    (А уж делать фабрику под лозунгом «создание объектов не является обязанностью объекта» — это вообще прекрасно. А инварианты кто отслеживать будет?)


    1. pssam Автор
      25.08.2015 15:57

      «зачем же вместо простого и понятного конструктора делать фабричный метод». Ради именования. Ради того, чтобы было понятно почему создается именно два конструктора и чем они отличаются.

      «создание объектов не является обязанностью объекта». По-моему вполне соответствует SOLID. Один объект — одна ответсвенность. В данном случае ответсвенность объекта — хранение данных.


      1. lair
        25.08.2015 16:05
        +1

        Наглядный пример плохого дизайна.

        Во-первых, если (единственная) ответственность объекта — хранение данных, то это DTO. Придумывать для них фабрики нет никакого смысла вообще, там достаточно геттеров-сеттеров.

        Во-вторых, для того, чтобы понять, чем отличаются конструкторы, достаточно правильно поименовать параметры.

        Наконец, в-третьих и последних — а почему у вас вообще один и тот же класс отвечает одновременно за текстовые и графические сообщения? Разбиваете на два класса, решаете все проблемы одним махом.

        PS Если вы считаете, что создание объекта — не его ответственность, то как вы решаете проблему поддержания инвариантов?


        1. pssam Автор
          25.08.2015 16:34

          Придумывать для них фабрики нет никакого смысла вообще

          Как раз наоборот. Если вам приходится много раз использовать геттеры и сеттеры, то гораздо проще изолировать эту логику в методе, а не размазывать по всей программе.

          Достаточно правильно поименовать параметры.

          Не всегда это получается. Иногда появляется по 5 и больше конструкторов.

          а почему у вас вообще один и тот же класс отвечает одновременно за текстовые и графические сообщения

          Исключительно ради сферичности примера в вакууме.

          то как вы решаете проблему поддержания инвариантов?

          Честно признаюсь, что не понимаю про какую проблему Вы говорите.


          1. lair
            25.08.2015 16:40

            Если вам приходится много раз использовать геттеры и сеттеры, то гораздо проще изолировать эту логику в методе, а не размазывать по всей программе.

            … а потом делать кучу оверлоадов, чтобы инициализировать только нужные свойства, или же передаете null, получая гигантские вызовы. А уж с геттерами и вовсе методы никак не помогают.

            Иногда появляется по 5 и больше конструкторов.

            Ну и что? Реальные проблемы возникают только тогда, когда у вас пересекаются типы параметров, и не работает оверлоадинг.

            Исключительно ради сферичности примера в вакууме.

            Вот этот сферический пример — это сферический пример плохой архитектуры, как делать не надо.

            Честно признаюсь, что не понимаю про какую проблему Вы говорите.

            Очень просто. Представьте себе класс

            class Email
            {
            public string To;
            public string Subject;
            public string Text;
            }
            


            Любой его экземпляр в любой момент жизни должен удовлетворять двум требованиям (собственно, это и есть инвариант):
            1. To и Text не содержат null или пустой строки
            2. ни одно из свойств не может быть изменено после создания объекта


            Внимание, вопрос: как вы удовлетворите этим требованиям, создавая объекты этого класса через фабрику?


            1. pssam Автор
              25.08.2015 16:58

              Внимание, вопрос: как вы удовлетворите этим требованиям, создавая объекты этого класса через фабрику?

              Если без извращений, то никак. Есть примеры, когда паттерны не получается применять, согласен. Но опять же, если нужно будет создавать письма с заранее предопределенным шаблоном (набором авторов, предопределенным заголовком или текстом или с заголовком, формирующимся каким-то хитрым способ), то ничто не мешает написать для этого отдельные методы, у которых из названия будет понятно какие именно письма они создают. А потом при необходимости выделить эти методы в отдельный класс.


              1. lair
                25.08.2015 17:02

                то ничто не мешает написать для этого отдельные методы, у которых из названия будет понятно какие именно письма они создают. А потом при необходимости выделить эти методы в отдельный класс.

                Вот вы и нарушили SRP.

                Методы по формированию содержимого письма изначально не имеют отношения к ответственности класса, передающего сообщения. Поэтому им не место ни в нем, ни в его потенциальной (и не нужной) фабрике.

                Где им место? А вы декомпонуйте тот бизнес-процесс, который у вас используется, и поймете. В худшем случае у вас там будет набор шаблонов сообщений, который, конечно, по всем признакам похож на фабрику, только семантически с сообщениями не связан никак.


                1. pssam Автор
                  25.08.2015 17:09

                  Мы сейчас говорим об обстрактных примерах. В любом случае, нужно будет выделять эту логику в метод. Согласно определению в моей статье, класс, в котором он будет находиться, называется фабрикой. Если возникнет необходимость, то эта логика выделится в отдельный класс. Я тут не вижу каких-то проблем.


                  1. lair
                    25.08.2015 17:11

                    Проблема в целеполагании.

                    У вас фабрика возникает путем выделения ответственности из конструктора. В моем же описании она возникает путем выделения повторяющейся реализации из потребителя класса. Совершенно разные задачи и ход мысли.


                    1. pssam Автор
                      25.08.2015 17:24

                      Я не писал код, который использует этот класс. Поэтому методы остались именно в классе сообщения.


                      1. lair
                        25.08.2015 17:26

                        Вот поэтому это и некорректный пример, на который не надо ориентироваться «начинающим программистам».


                        1. pssam Автор
                          25.08.2015 17:28
                          -1

                          На примеры вообще не нужно ориентироваться. Нужно понимать что делают паттерны. А они управляют ответсвенностями.


                          1. lair
                            25.08.2015 17:29

                            А зачем тогда нужны примеры, если на них не надо ориентироваться, и они неправильно показывают движение ответственностей?


                            1. pssam Автор
                              25.08.2015 17:38

                              Примеры нужны для пояснения.

                              Этот пример правильно показывает движение ответсвенности. От конструктора в метод и в фабрику. Допустим, есть два класса, которые используют эти конструкторы и дублируют логику создания объекта, тогда возможно два пути выделения этой логики. Один — через класс сообщения, другой — через один из создающих методов. Я, лично, вполне допускаю оставить создающие методы в классе сообщения.

                              У нас есть сообщение и у нас есть возможность создавать предопределенные виды этого сообщения, пусть эта информация лежит в одном месте. Тем более, если мы заменяем конструкторы методами, которые поясняют, что эти конструкторы создают.


                              1. lair
                                25.08.2015 17:40

                                От конструктора в метод и в фабрику.

                                Вот это я и считаю неправильным.

                                А чтобы было понятно, почему, давайте на конкретных примерах разбираться, что у вас откуда и куда переходит. Хотя бы на тех же емейл-сообщениях с инвариантностью и шаблонами.


                                1. pssam Автор
                                  25.08.2015 17:51

                                  Давайте попробуем. В примере с инвариантностью неясно откуда это требование берется и как оно используется. Допустим, нам нужен неизменяемый объект, т.е. выполняем второе требование.
                                  class Email
                                  {
                                  public string To{get; private set;}
                                  public string Subject{get; private set;}
                                  public string Text{get; private set;}
                                  }

                                  Если первое требование справедливо всегда для этого класса, то просто создаем в нем конструктор.
                                  Если оно справедливо, в рамках какого-то другого класса, то добавляем в этом классе метод, который будет создавать имейлы.
                                  class ClassUsingNonEmptyEmails
                                  {
                                  private Email CreateEmail(){...}
                                  }
                                  Если у нас по всему приложению используются как имейлы, для которых правило 1 выполняется так и нет, то прямо в классе Email можем написать
                                  class Email
                                  {
                                  static Email CreateValidEmail(){...}
                                  }


                                  1. lair
                                    25.08.2015 17:55

                                    Инварианты — они на то и инварианты, что верны всегда.


                              1. lair
                                25.08.2015 17:52

                                Вот стартовый пример класса сообщения:

                                public class EmailMessage
                                {
                                  public EmailMessage(string to, string body)
                                  {
                                    if (string.IsNullOrWhiteSpace(to)) throw new ArgumentOutOfRangeException(nameof(to));
                                    if (string.IsNullOrWhiteSpace(body)) throw new ArgumentOutOfRangeException(nameof(body));
                                
                                    To = to;
                                    Body = body;
                                  }
                                
                                  public string To { get; }
                                  public string Subject { get; }
                                  public string Body { get; }
                                }
                                


                                1. pssam Автор
                                  25.08.2015 17:52

                                  И что Вы с ним хотите сделать?


                                  1. lair
                                    25.08.2015 17:54

                                    А теперь давайте разбираться, как из этого класса может получиться фабрика, и зачем.

                                    Вы утверждаете:

                                    Но опять же, если нужно будет создавать письма с заранее предопределенным шаблоном (набором авторов, предопределенным заголовком или текстом или с заголовком, формирующимся каким-то хитрым способ), то ничто не мешает написать для этого отдельные методы, у которых из названия будет понятно какие именно письма они создают. А потом при необходимости выделить эти методы в отдельный класс.


                                    Я хочу увидеть, как у вас это происходит.


                                    1. pssam Автор
                                      25.08.2015 18:11

                                      public class EmailMessage
                                      {
                                      public EmailMessage(string to, string body)
                                      {
                                      if (string.IsNullOrWhiteSpace(to)) throw new ArgumentOutOfRangeException(nameof(to));
                                      if (string.IsNullOrWhiteSpace(body)) throw new ArgumentOutOfRangeException(nameof(body));

                                      To = to;
                                      Body = body;
                                      }

                                      public string To { get; private set} //Убрал заголовок, вроде все равное не используется и к делу не относится.
                                      public string Body { get; private set;}

                                      public static CreateSpamEmail(string to)
                                      {
                                      return new Email(to, «Buy Me»);
                                      }
                                      public static CreateEmailToDaddy(string message)
                                      {
                                      return new Email(«Dad@dy», message);
                                      }
                                      public static CreateDecoratedEmail(string to, string body)
                                      {
                                      return new Email(to, "!!!" + body + "!!!");
                                      }
                                      }

                                      Какой-то класс генерирует сообщения, но не знает кому их надо отправлять

                                      class EmailGenerator
                                      {
                                      private IEmailFactory factory;
                                      public EmailGenerator(IEmailFactory factory)
                                      {
                                      this.factory = factory;
                                      }

                                      public Email Generate()
                                      {
                                      return new Email(factory.Create(«RandomMessage»));
                                      }
                                      }

                                      interface IEmailFactory
                                      {
                                      Email Create(string message);
                                      }

                                      class EmailToPersonFactory:IEmailFactory
                                      {
                                      private string to;
                                      public EmailToPersonFactory(string to)
                                      {
                                      this.to = to;
                                      }

                                      public Email Create(string message)
                                      {
                                      Email.CreateDecoratedEmail(to, message);
                                      }
                                      }

                                      class Spamer
                                      {
                                      void Run()
                                      {
                                      var factory = new EmailToPersonFactory(«pers@son»);
                                      var generator = new EmailGenerator(factory);
                                      while (true)
                                      {
                                      Send(generator.Generate());
                                      }
                                      }
                                      }

                                      Без конечной цели можно извращаться сколько угодно.


                                      1. lair
                                        25.08.2015 18:20

                                        (какая религия вам не позволяет код форматировать?)

                                        public class EmailMessage
                                        {
                                        //...
                                        public static CreateSpamEmail(string to) {return new Email(to, "Buy Me");}
                                        public static CreateEmailToDaddy(string message) {return new Email("Dad@dy", message);}
                                        public static CreateDecoratedEmail(string to, string body) {return new Email(to, "!!!" + body + "!!!");}
                                        //...
                                        }
                                        


                                        Вот это — нарушение SRP. Класс EmailMessage предназначен для того, чтобы хранить информацию о сообщении (помните, вы в начале дискуссии как раз на это упирали как на повод для вынесения фабричных методов в отдельный класс?). Откуда он знает о том, что такое «спам» или кто такой «папа»?

                                        А должно быть вот так:
                                        public class Order
                                        {
                                          //...
                                          private void SendEmailToCustomer(string subject, string body)
                                          {
                                            var email = new EmailMessage(Customer.GetOrderNotificationEmail(), subject, body);
                                            _emailService.Send(email);
                                          }
                                          //...
                                          public void ApproveOrder()
                                          {
                                            //...
                                            SendEmailToCustomer("Your order has been approved", "Dear customer, your order has been approved and will be processed shortly".);
                                            //...
                                          }
                                        }
                                        


                                        1. pssam Автор
                                          25.08.2015 18:25

                                          Я пока не нашел где его форматировать(
                                          Ну, замечательно. Привели бы пример требования, тогда сразу. А что мешает выделить метод?
                                          private Email CreateEmailToCustomer(Customer cutomer, string subject, string body)
                                          {
                                          return new EmailMessage(Customer.GetOrderNotificationEmail(), subject, body);
                                          }


                                          1. lair
                                            25.08.2015 18:26

                                            Он уже выделен, зачем его дальше выделять?


                                            1. pssam Автор
                                              25.08.2015 18:31

                                              Вы берете конкретный случай, где его не нужно выделять и просите, чтобы я на нем продемонстрировал, что его можно выделить. Я же не знаю всего кода и всех требований. Допустим, Вы создаете имейлы точно таким же образом в нескольких местах этого класса. А потом оказывается, что надо отправлять не по Customer.GetOrderNotificationEmail(), а по Customer.GetPrivateEmail(). Придется проверять все вызовы GetOrderNotificationEmail. Или хотите отправлять несколькими способами.


                                              1. lair
                                                25.08.2015 18:33

                                                Допустим, Вы создаете имейлы точно таким же образом в нескольких местах этого класса.

                                                Метод, который отвечает за отправку емейла, уже выделен (и есть в видимом вам коде). Программист, который его не использует, получает вежливое предупреждение и переписывает код на правильный.

                                                (вот метод GetOrderNotificationEmail — действительно не на своем месте, но мне было лень писать еще один метод с поиском нужного емейла среди всех емейлов покупателя, уж простите мне это)


                                      1. pssam Автор
                                        25.08.2015 18:20

                                        Ошибся в классе EmailGenerator
                                        public Email Generate()
                                        {
                                        return factory.Create(«RandomMessage»);
                                        }
                                        }


                                1. lair
                                  25.08.2015 17:59

                                  (ошибся уровнем)


                                1. lair
                                  25.08.2015 18:03

                                  Забыл конструктор с Subject, конечно же:

                                  public EmailMessage(string to, string subject, string body): this(to, body)
                                  {
                                    Subject = subject;
                                  }
                                  


            1. KReal
              25.08.2015 17:43

              • internal конструктор, например, который вызывается в фабрике, которая находится в той же сборке
              • reflection


              1. lair
                25.08.2015 17:46

                internal конструктор, например, который вызывается в фабрике, которая находится в той же сборке

                В таком случае внутри сборки инварианты не будут гарантированы. Это не всегда допустимо.

                reflection

                Во-первых, через reflection можно тогда и обратно сломать. А во-вторых, если нам для такой простой задачи нужен reflection, то что-то у нас не так с дизайном.


                1. KReal
                  25.08.2015 19:07

                  В таком случае внутри сборки инварианты не будут гарантированы. Это не всегда допустимо.


                  Почему будут? Свойства пусть будут с приватными сеттерами и выставляться в конструкторе. А вот снаружи конструктора уже не будет видно, только фабрика.


                  1. lair
                    25.08.2015 19:11

                    Если свойства с приватными сеттерами, то инварианты гарантирует объект. Зачем тогда делать конструктор интернал, и какой смысл в фабрике?


                    1. KReal
                      25.08.2015 19:14

                      Никакого, конечно, просто ответил на ваш изначальный вопрос)


  1. lair
    25.08.2015 15:56

    Если нам нужно создавать другие объекты (реализующие тот же интерфейс или унаследованные от базового класса) или те же объекты, но другим способом в дочерних классах, то делаем метод виртуальным и переопределяем в дочерних классах (реализуем фабричный метод).


    Кстати, пример в студию. У вас в коде такого нет.

    Если нам нужно использовать порождающий метод из нескольких мест, то выделяем его в отдельный класс (фабрику).

    То есть если из одного метода — то можно использовать порождающий метод на классе, а если из нескольких — то обязательно на фабрике? Но почему?


    1. pssam Автор
      25.08.2015 16:03

      Кстати, пример в студию. У вас в коде такого нет.

      Вы правы. Обязательно добавлю.

      То есть если из одного метода — то можно использовать порождающий метод на классе, а если из нескольких — то обязательно на фабрике? Но почему?

      Имелось ввиду, что порождающий метод может быть приватным. Если он вылазит наружу и начинает использоваться из разных мест программы, то имеет смысл подумать о выделении этой ответсвенности в другой класс по тому же принципу SOLID.


      1. lair
        25.08.2015 16:06

        Имелось ввиду, что порождающий метод может быть приватным.

        Какой в нем тогда смысл? Он противоречит всему, что вы описали выше.

        Если он вылазит наружу и начинает использоваться из разных мест программы, то имеет смысл подумать о выделении этой ответсвенности в другой класс по тому же принципу SOLID.

        Во-первых, не SOLID, а SRP. А во-вторых, я продолжаю не понимать, каким образом корректная инициализация объекта не является его ответственностью.


        1. pssam Автор
          25.08.2015 16:30

          Какой в нем тогда смысл?

          Изабавление от дублирования, например. Именование метода. Причины для выделения метода могут быть разные.

          Каким образом корректная инициализация объекта не является его ответственностью./blockquote> Допустим, у нас есть класс калькулятора и есть разные калькуляторы. Его ответственность — это делать подсчеты. Выбирать и создавать калькуляторы — ответственность другого класса.


          1. lair
            25.08.2015 16:33

            Изабавление от дублирования, например. Именование метода. Причины для выделения метода могут быть разные.

            А вызывать-то вы его откуда будете, статический приватный метод?

            Допустим, у нас есть класс калькулятора и есть разные калькуляторы. Его ответственность — это делать подсчеты. Выбирать и создавать калькуляторы — ответственность другого класса.

            Выбирать — да. Это не ицинициализация, это выбор реализации (здравствуй, DIP). А вот инициализацию он должен делать самостоятельно, потому что иначе вы получите два сильно связанных класса (фабрика и собственно калькулятор).

            И кстати, как только вы начинаете использовать DIP, количество фабрик (в любом виде) в вашем коде резко падает.


            1. pssam Автор
              25.08.2015 16:52

              А вызывать-то вы его откуда будете, статический приватный метод?

              Вполне возможен такой вариант. Зависит от ситуации.

              А вот инициализацию он должен делать самостоятельно, потому что иначе вы получите два сильно связанных класса (фабрика и собственно калькулятор).

              Не всегда объект знает, чем его инициализирует. Допустим класс принимает какое-то значение в конструкторе, а это значение вычисляется несколькими способами в разных частях программы, причем эти вычисления дублируются. Эту логику Вы никак в конструктор не запихнете. Или запихнете, используя дополнительные ненужные параметры.

              как только вы начинаете использовать DIP, количество фабрик (в любом виде) в вашем коде резко падает.

              При условии, что Вам нужен готовый объект, а не нужно создать его в процессе выполнения.


              1. lair
                25.08.2015 16:57

                Вполне возможен такой вариант. Зависит от ситуации.

                Так откуда вы будете его вызывать?

                Не всегда объект знает, чем его инициализирует.

                Всегда. Это описано в его сигнатуре.

                Допустим класс принимает какое-то значение в конструкторе, а это значение вычисляется несколькими способами в разных частях программы, причем эти вычисления дублируются.

                Есть два варианта.

                • Это значение не имеют связи с семантикой объекта (пример: объект представляет собой емейл, а передается в него текст). Тогда вам и фабрика не нужна, а должен быть бизнес-объект, вычисляющий такие значения.
                • Это значение, на самом деле, связано с семантикой объекта (пример: «правильная» конкатенация списка адресатов для емейла). Тогда это значение лучше вычислять внутри объекта


                При условии, что Вам нужен готовый объект, а не нужно создать его в процессе выполнения.

                И если нужно создать в процессе выполнения — тоже. Func<T> творит чудеса.

                (и да, я понимаю, что это неявная фабрика, но вся радость-то в том, что она неявная, нигде в коде этого метода нет)


                1. pssam Автор
                  25.08.2015 17:26

                  — Видишь метод? — нет — и я нет, а он есть)
                  Лямда, конечно, творит чудеса, но сути вещей она не меняет.


                  1. lair
                    25.08.2015 17:28

                    Меняет. Когда я, как программист, не написал класса фабрики, то его действительно нет. Я не трачу сил на его создание или поддержку, он не добавляет семантических сущностей, которые я должен учитывать.

                    (и нет, лямбды тут совсем ни при чем)


                    1. pssam Автор
                      25.08.2015 17:31

                      За Вас это сделал компилятор. Создавать и поддерживать лямбду, которую Вы передадите все равно придется. И иногда это даже сложнее, чем поддерживать выделенный класс (у нас такое на прокете такое было), хотя бы потому, что лямбда не имеет названия и может быть непонятно, почему она делает именно то, что делает.


                      1. lair
                        25.08.2015 17:35

                        Создавать и поддерживать лямбду, которую Вы передадите все равно придется.

                        Ээээ… нет. Потому что ее за меня создаст DI-контейнер, которым я пользуюсь.

                        лямбда не имеет названия и может быть непонятно, почему она делает именно то, что делает.

                        … а в тех случаях, когда DI-контейнер не справляется, можно сделать именованный делегат, из названия которого будет понятно, что он делает. Но это единицы случаев (даже не единицы процентов).


        1. eaa
          25.08.2015 16:32
          +1

          Проблема в том, что на простых примерах показать паттерны нереально, потому что паттерны нужны как раз тогда, когда все становится уже не так тривиально. Именно поэтому в больших книгах все начинается с UML.
          А так да, в статье есть примеры, но они не дают ответы на вопросы, которые возникают… хотя есть попытка. В тексте поминается, например, что фабричный метод отдает интерфейс, а в примерах указывается только один конкретный класс. Тогда действительно не понятно, нафига эти паттерны. Ну и т.д.


          1. lair
            25.08.2015 16:34
            +2

            Вполне можно придумать пример под данные конкретные паттерны. Проблема в том, что на этих примерах становится видно не только сильные стороны, но и слабые — а их автор поста обходит стороной.


            1. pssam Автор
              25.08.2015 17:01

              Идеальный код — ненаписанный.


              1. lair
                25.08.2015 17:02

                Да нет, просто давая примеры применения шаблонов, особенно неоднозначных, надо очень четко оговаривать, какие недостатки они с собой приносят, и когда их не надо применять.


  1. Fireball222
    25.08.2015 16:43
    +2

    Если нам нужно создавать объект, то используем порождающий метод.

    Не хватает критериев для понимания того, когда все же можно использовать классический способ с вызовом конструктора. Если статья позиционируется для начинающих разработчиков, это просто необходимо. Мы же не хотим любой объект создавать, используя как минимум порождающий метод.


    1. pssam Автор
      25.08.2015 17:00

      Да, согласен. Нужно добавить примеры.