Книга «Элегантные объекты. Java Edition»

    imageПривет, Хаброжители! Эта книга всерьез пересматривает суть и принципы объектно-ориентированного программирования (ООП) и может быть метафорически названа «ООП Лобачевского». Егор Бугаенко, разработчик с 20-летним стажем, критически анализирует догмы ООП и предлагает взглянуть на эту парадигму совершенно по-новому. Так, он клеймит статические методы, геттеры, сеттеры, изменяемые методы, считая, что это — зло. Для начинающего программиста этот томик может стать просветлением или шоком, а для опытного является обязательным чтением.

    Отрывок «Не используйте статические методы»


    Ах, статические методы… Одна из моих любимых тем. Мне понадобилось несколько лет, чтобы осознать, насколько важна эта проблема. Теперь я сожалею обо всем том времени, которое потратил на написание процедурного, а не объектно-ориентированного программного обеспечения. Я был слеп, но теперь прозрел. Статические методы — настолько же большая, если не еще большая проблема в ООП, чем наличие константы NULL. Статических методов в принципе не должно было быть в Java, да и в других объектно-ориентированных языках, но, увы, они там есть. Мы не должны знать о таких вещах, как ключевое слово static в Java, но, увы, вынуждены.. Я не знаю, кто именно привнес их в Java, но они — чистейшее зло.. Статические методы, а не авторы этой возможности. Я надеюсь.

    Посмотрим, что такое статические методы и почему мы до сих пор создаем их. Скажем, мне нужна функциональность загрузки веб-страницы посредством HTTP-запросов. Я создаю такой «класс»:

    class WebPage {
      public static String read(String uri) {
         // выполнить HTTP-запрос
         // и конвертировать ответ в UTF8-строку
      }
    }

    Пользоваться им очень удобно:

    String html = WebPage.read("http://www.java.com");

    Метод read() относится к тому классу методов, против которого я выступаю. Предлагаю вместо этого использовать объект (также я поменял имя метода в соответствии с рекомендациями из раздела 2.4):

    class WebPage {
      private final String uri;
      public String content() {
         // выполнить HTTP-запрос
         // и конвертировать ответ в UTF8-строку
      }
    }

    Вот как им пользоваться:

    String html = new WebPage("http://www.java.com")
       .content();

    Вы можете сказать, что между ними нет особой разницы. Статические методы работают даже быстрее, потому что нам нет необходимости создавать новый объект каждый раз, когда нужно скачать веб-страницу. Просто вызовите статический метод, он сделает дело, вы получите результат и будете работать дальше.. Нет необходимости возиться с объектами и сборщиком мусора. Кроме того, мы можем сгруппировать несколько статических методов в класс-утилиту и назвать его, скажем, WebUtils.

    Эти методы помогут загружать веб-страницы, получать статистическую информацию, определять время отклика и т. п. В них будет много методов, а использовать их просто и интуитивно понятно. Кроме того, как применять статические методы, тоже интуитивно понятно. Все понимают, как они работают. Просто напишите WebPage.read(), и — вы догадались! — будет прочитана страница. Мы дали компьютеру инструкцию, и он ее выполняет.. Просто и понятно, так ведь? А вот и нет!

    Статические методы в любом контексте — безошибочный индикатор плохого программиста, понятия не имеющего об ООП. Для применения статических методов нет ни единого оправдания ни в одной ситуации. Забота о производительности не считается. Статические методы — издевательство над объектно-ориентированной парадигмой. Они существуют в Java, Ruby, C++, PHP и других языках. К несчастью. Мы не можем их оттуда выбросить, не можем переписать все библиотеки с открытым исходным кодом, полные статических методов, но можем прекратить использовать их в своем коде.

    Мы должны прекратить применять статические методы.

    Теперь посмотрим на них с нескольких разных позиций и обсудим их практические недостатки. Я могу заранее обобщить их для вас: статические методы ухудшают сопровождаемость программного обеспечения. Это не должно вас удивлять. Все сводится к сопровождаемости.

    Объектное мышление против компьютерного


    Изначально я назвал этот подраздел «Объектное мышление против процедурного», но потом переименовал. «Процедурное мышление» означает почти то же самое, но словосочетание «мыслить как компьютер» лучше описывает проблему.. Мы унаследовали этот образ мышления из ранних языков программирования, таких как Assembly, C, COBOL, Basic, Pascal, и многих других. Основа парадигмы в том, что компьютер работает на нас, а мы указываем ему, что делать, давая ему явные инструкции, например:

       CMP AX, BX
       JNAE greater
       MOV CX, BX
       RET
    greater:
       MOV CX, AX
       RET

    Это ассемблерная «подпрограмма» для процессора Intel 8086.. Она находит и возвращает большее из двух чисел. Мы помещаем их в регистры AX и BX соответственно, а результат попадает в регистр CX. Вот точно такой же код на языке С:

    int max(int a, int b) {
       if (a > b) {
         return a;
       }
       return b;
    }

    «Что же с этим настолько не так?» — спросите вы. Ничего.. Все с этим кодом в порядке — он работает, как и положено.. Именно так работают все компьютеры. Они ожидают, что мы дадим им инструкции, которые они будут исполнять одну за другой.. Многие годы мы писали программы именно так. Преимущество данного подхода в том, что мы остаемся вблизи процессора, направляя его дальнейшее движение. Мы у руля, а компьютер следует нашим инструкциям. Мы указываем компьютеру, как найти большее из двух чисел. Мы принимаем решения, он им следует. Поток исполнения всегда последователен, от начала сценария до его конца.

    Такой линейный тип мышления называется «думать как компьютер». Компьютер в какой-то момент начнет исполнять инструкции и в какой-то момент закончит делать это. При написании кода на языке С мы вынуждены думать таким образом. Операторы, разделенные точками с запятыми, идут сверху вниз. Такой стиль унаследован из ассемблера.
    Хотя языки более высокого уровня, чем ассемблер, имеют процедуры, подпрограммы и другие механизмы абстракции, они не устраняют последовательный образ мышления.. Программа все равно проходится сверху вниз. В таком подходе нет ничего зазорного при написании небольших программ, но в более крупных масштабах так мыслить трудно.

    Взглянем на тот же код, записанный на функциональном языке программирования Lisp:

    (defun max (a b)
         (if (> a b) a b))

    Можете ли вы сказать, где начинается и заканчивается исполнение этого кода? Нет. Мы не знаем, ни каким образом процессор получит результат, ни то, как конкретно будет работать функция if. Мы очень отстранены от процессора. Мы мыслим как функция, а не как компьютер. Когда нам нужна новая вещь, мы определяем ее:

    (def x (max 5 9))

    Мы определяем, а не даем инструкции процессору. Этой строчкой мы привязываем x к (max 5 9). Мы не просим компьютер вычислить большее из двух чисел. Мы просто говорим, что х есть большее из двух чисел. Мы не управляем тем, как и когда это будет вычислено. Обратите внимание, это важно: x есть большее из чисел. Отношение «есть» («быть», «являться») — то, чем отличается функциональная, логическая и объектно-ориентированная парадигма программирования от процедурной.

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

    class Max implements Number {
      private final Number a;
      private final Number b;
      public Max(Number left, Number right) {
          this.a = left;
          this.b = right;
      }
    }

    А так я буду его использовать:

    Number x = new Max(5, 9);

    Смотрите, я не вычисляю большее из двух чисел. Я определяю, что х есть большее из двух чисел. Меня не особо беспокоит, что находится внутри объекта класса Max и как именно он реализует интерфейс Number. Я не даю процессору инструкции относительно этого вычисления. Я просто инстанцирую объект. Это очень похоже на def в Lisp.. В этом смысле ООП очень похоже на функциональное программирование.

    Напротив, статические методы в ООП — то же самое, что подпрограммы в С или ассемблере. Они не имеют отношения к ООП и заставляют нас писать процедурный код в объектно-ориентированном синтаксисе. Вот код на Java:

    int x = Math.max(5, 9);

    Это совершенно неправильно и не должно использоваться в настоящем объектно-ориентированном проектировании.

    Декларативный стиль против императивного


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

    Какое отношение все это имеет к статическим методам? Неважно, статический это метод или объект, мы все еще должны где-то написать if (a > b), так ведь? Да, именно так. Как статический метод, так и объект — всего лишь обертка над оператором if, который выполняет задачу сравнения a с b. Разница в том, как эта функциональность используется другими классами, объектами и методами. И это существенная разница. Рассмотрим ее на примере.
    Скажем, у меня есть интервал, ограниченный двумя целыми числами, и целое число, которое должно в него попадать.. Я должен убедиться, что это так. Вот что мне придется сделать, если метод max() — статический:

    public static int between(int l, int r, int x) {
        return Math.min(Math.max(l, x), r);
    }

    Нужно создать еще один статический метод, between(), который использует два имеющихся статических метода, Math.min() и Math.max(). Есть только один способ это сделать — императивный подход, поскольку значение вычисляется сразу же. Когда я делаю вызов, я немедленно получаю результат:

    int y = Math.between(5, 9, 13); // возвращает 9

    Я получаю число 9 сразу же после вызова between(). Когда будет сделан вызов, мой процессор тут же начнет работать над этим вычислением. Это императивный подход. А как тогда выглядит декларативный подход?

    Вот, взгляните:

    class Between implements Number {
      private final Number num;
      Between(Number left, Number right, Number x) {
          this.num = new Min(new Max(left, x), right);
      }
      @Override
      public int intValue() {
          return this.num.intValue();
       }
    }

    Вот как я его буду использовать:

    Number y = new Between(5, 9, 13); // еще не вычисляется!

    Чувствуете разницу? Она чрезвычайно важна. Такой стиль будет декларативным, поскольку я не указываю процессору, что вычисления нужно выполнить сразу. Я просто определил, что это такое, и оставил на усмотрение пользователя решение о том, когда (и нужно ли вообще) вычислять переменную y методом intValue(). Может, она никогда не будет вычислена и мой процессор никогда не узнает, что это число 9.. Все, что я сделал, — объявил, что такое y. Просто объявил. Я еще не дал никакой работы процессору. Как указано в определении, выразил логику, не описывая процесс.

    Я уже слышу: «О’кей, понял вас. Есть два подхода — декларативный и процедурный, но почему первый лучше второго?» Ранее я упомянул, что очевидно, что декларативный подход более мощный, но не объяснил почему. Теперь, когда мы рассмотрели оба подхода на примерах, обсудим преимущества декларативного подхода.

    Во-первых, он быстрее. На первый взгляд он может показаться более медленным. Но если присмотреться внимательнее, станет видно, что на деле он быстрее, поскольку оптимизация производительности полностью в наших руках. Действительно, на создание экземпляра класса Between потребуется больше времени, чем на вызов статического метода between(), по крайней мере в большинстве языков программирования, доступных на момент написания этой книги.. Я очень надеюсь на то, что в ближайшем будущем у нас появится язык, в котором инстанцирование объекта будет столь же быстрым, как и вызов метода. Но мы еще не пришли к нему. Вот почему декларативный подход медленнее… когда путь исполнения прост и прямолинеен.

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

    public void doIt() {
        int x = Math.between(5, 9, 13);
        if (/* Надо ли? */) {
          System.out.println("x=" + x);
        }
    }

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

    public void doIt() {
        Integer x = new Between(5, 9, 13);
        if (/* Надо ли? */) {
          System.out.println("x=" + x);
        }
    }

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

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

    Второй аргумент — полиморфизм. Если говорить просто, то полиморфизм — это возможность разрывать зависимости между блоками кода. Допустим, я хочу поменять алгоритм определения того, попадает ли число в определенный интервал. Он довольно примитивен сам по себе, но я хочу его изменить. Я не хочу использовать классы Max и Min. А хочу, чтобы он выполнял сравнение с применением операторов if-then-else.. Вот как сделать это декларативно:

    class Between implements Number {
      private final Number num;
      Between(int left, int right, int x) {
          this(new Min(new Max(left, x), right));
      }
      Between(Number number) {
          this.num = number;
      }
    }

    Это тот же класс Between, что и в предыдущем примере, но с дополнительным конструктором. Теперь я могу использовать его с другим алгоритмом:

    Integer x = new Between(
       new IntegerWithMyOwnAlgorithm(5, 9, 13)
    );

    Это, наверное, не лучший пример, поскольку класс Between очень примитивен, но, надеюсь, вы понимаете, о чем я. Класс Between очень просто отделить от классов Max и Min, поскольку они являются классами. В объектно-ориентированном программировании объект является полноправным гражданином, а статический метод — нет. Мы можем передать объект в качестве аргумента конструктору, но не можем сделать то же самое со статическим методом. В ООП объекты связаны с объектами, общаются с объектами, обмениваются с ними данными. Чтобы полностью отвязать объект от остальных объектов, мы должны убедиться, что он не использует оператор new ни в одном из своих методов (см. раздел 3.6), а также в главном конструкторе.

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

    Можете ли вы проделать такую же отвязку и рефакторинг с императивным фрагментом кода?

    int y = Math.between(5, 9, 13);

    Нет, не можете. Статический метод between() использует два статических метода, min() и max(), и вы ничего не сможете сделать, пока не перепишете его полностью. А как вы сможете его переписать? Передадите четвертым параметром новый статический метод?

    Насколько уродливо это будет выглядеть? Думаю, весьма.

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

    Третий довод в пользу превосходства декларативного подхода над императивным — декларативный подход говорит о результатах, а императивный объясняет единственный способ их получения. Второй подход намного менее интуитивно понятен, чем первый. Я должен сперва «выполнить» код в голове, чтобы понять, какого результата ожидать. Вот императивный подход:

    Collection<Integer> evens = new LinkedList<>();
    for (int number : numbers) {
       if (number % 2 == 0) {
         evens.add(number);
       }
    }

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

    Collection<Integer> evens = new Filtered(
        numbers,
        new Predicate<Integer>() {
           @Override
           public boolean suitable(Integer number) {
               return number % 2 == 0;
           }
         }
    );

    Этот фрагмент кода намного ближе к английскому языку, чем предыдущий. Он читается следующим образом: «evens — это фильтрованная коллекция, включающая только те элементы, которые являются четными». Я не знаю, как именно класс Filtered создает коллекцию — использует ли он оператор for или что-то еще. Все, что я должен знать, читая этот код, — то, что коллекция была отфильтрована. Детали реализации скрыты, а поведение выражено.

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

    Если вы считаете этот код уродливым, попробуйте, например, Groovy:

    def evens = new Filtered(
       numbers,
       { Integer number -> number % 2 == 0 }
    );

    Четвертый довод — цельность кода. Взгляните еще раз на предыдущие два фрагмента. Обратите внимание на то, что во втором фрагменте мы объявляем evens одним оператором — evens = Filtered(…). Это значит, что все строки кода, ответственные за вычисление данной коллекции, находятся рядом друг с другом и не могут быть по ошибке разделены. Напротив, в первом фрагменте нет очевидной «склейки» строк. Можно с легкостью поменять их порядок по ошибке, и алгоритм сломается.

    В таком простом фрагменте кода это небольшая проблема, поскольку алгоритм очевиден. Но если фрагмент императивного кода более крупный — скажем, 50 строк, может оказаться трудно понять, какие строки кода связаны друг с другом.. Мы обсудили проблему темпорального сцепления чуть раньше — во время обсуждения неизменяемых объектов.. Декларативный стиль программирования также помогает устранить это сцепление, благодаря чему улучшается сопровождаемость.

    Вероятно, есть еще доводы, но я привел самые важные, с моей точки зрения, из относящихся к ООП. Надеюсь, я смог убедить вас в том, что декларативный стиль — это то, что надо. Некоторые из вас могут сказать: «Да, я понимаю, о чем вы. Я буду совмещать декларативный и императивный подходы там, где это уместно. Я буду использовать объекты там, где это имеет смысл, а статические методы — тогда, когда мне надо быстро сделать что-то несложное вроде вычисления большего из двух чисел».. «Нет, вы неправы!» — отвечу вам я. Вы не должны их совмещать.. Никогда не применяйте императивный стиль. Это не догма.. У этого есть вполне прагматичное объяснение.

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

    Допустим, у нас есть два статических метода — max() и min(). Они выполняют небольшие быстрые вычисления, поэтому мы делаем их статическими. Теперь нам нужно создать больший алгоритм, чтобы определить, принадлежит ли число интервалу.. На сей раз мы хотим пойти декларативным путем — создать класс Between, а не статический метод between(). Можем ли мы так сделать? Наверное, да, но суррогатным способом, а не так, как положено. Мы не можем использовать конструкторы и инкапсуляцию. И вынуждены делать непосредственные, явные вызовы статических методов прямо внутри класса Between. Иными словами, мы не сможем написать чисто объектно-ориентированный код, если повторно применяемые компоненты представляют собой статические методы.

    Статические методы напоминают раковую болезнь объектно-ориентированного ПО: однажды позволив им поселиться в коде, мы не сможем избавиться от них — их колония будет только расти. Просто обходите их стороной в принципе.

    «Но они у меня повсюду! — воскликнете вы. — Что же делать?» Что я могу сказать… у вас проблемы, как и у всех нас. Существуют тысячи объектно-ориентированных библиотек, практически полностью состоящих из классов-утилит (мы обсудим их в следующем разделе). Здесь, как и с опухолью, лучшее средство — нож. Не используйте такие программы, если можете это себе позволить.. Однако в большинстве случаев вы не сможете позволить себе воспользоваться ножом, поскольку эти библиотеки весьма популярны и предоставляют полезную функциональность. В данном случае лучшее, что вы можете сделать, — изолировать опухоль, создав собственные классы, которые оборачивают статические методы так, чтобы ваш код работал исключительно с объектами. К примеру, в библиотеке Apache Commons есть статический метод FileUtils.readLines(), который считывает все строки из текстового файла. Вот как мы можем превратить его в объект:

    class FileLines implements Iterable<String> {
      private final File file;
      public Iterator<String> iterator() {
          return Arrays.asList(
             FileUtils.readLines(this.file)
          ).iterator();
      }
    }

    Теперь, чтобы прочесть все строки из текстового файла, наше приложение должно будет сделать следующее:

    Iterable<String> lines = new FileLines(f);

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

    Классы-утилиты


    Так называемые классы-утилиты на самом деле являются не классами, а лишь набором статических методов, используемых другими классами для удобства (они известны также как методы-помощники).. К примеру, класс java.lang.Math — классический образец класса-утилиты. Такие порождения очень популярны в Java, Ruby и, к сожалению, почти во всех современных языках программирования. Почему они не являются классами? Потому что из них нельзя инстанцировать объекты. В разделе 1.1 мы обсудили разницу между объектом и классом и пришли к тому, что класс — это фабрика объектов. Класс-утилита не является фабрикой, например:

    class Math {
      private Math() {
         // намеренно пустой
      }
    
      public static int max(int a, int b) {
          if (a < b) {
            return b;
          }
            return a;
      }
    }

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

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

    Паттерн «Синглтон»


    Паттерн «Синглтон» — популярный прием, претендующий на то, чтобы стать заменой статических методов. Действительно, в классе будет только один статический метод, а синглтон при этом будет выглядеть почти как настоящий объект. Однако он им не является:

    class Math {
      private static Math INSTANCE = new Math();
      private Math() {}
      public static Math getInstance() {
          return Math.INSTANCE;
      }
      public int max(int a, int b) {
          if (a < b) {
            return b;
          }
          return a;
      }
    }

    Выше приведен типичный пример синглтона. Существует единственный экземпляр класса Math, который называется INSTANCE.. Каждый может получить к нему доступ, просто вызвав getInstance(). Конструктор сделан приватным, чтобы предотвратить прямое инстанцирование объектов данного класса. Единственный способ получить доступ к INSTANCE — вызвать getInstance().

    «Синглтон» известен как паттерн проектирования, но в действительности это ужасный антипаттерн. Есть масса причин того, почему это плохой прием программирования. Я приведу лишь некоторые из них, касающиеся статических методов. Было бы, конечно, проще, если бы мы сначала обсудили то, чем синглтон отличается от класса-утилиты, о котором мы только что говорили. Вот как выглядел бы класс-утилита Math, который делает то же, что и приведенный ранее синглтон:

    class Math {
      private Math() {}
      public static int max(int a, int b) {
          if (a < b) {
            return b;
          }
          return a;
      }
    }

    Вот так будет использоваться метод max():

    Math.max(5, 9); // класс-утилита
    Math.getInstance().max(5, 9); // синглтон

    В чем разница? Выглядит, будто вторая строка просто длиннее, а делает то же самое. Зачем было изобретать синглтон, если у нас уже были статические методы и классы-утилиты? Я часто задаю этот вопрос на собеседованиях с Java-программистами. Первое, что я обычно слышу в ответ: «Синглтон позволяет инкапсулировать состояние». Например:

    class User {
      private static User INSTANCE = new User();
      private String name;
      private User() {}
      public static User getInstance() {
          return User.INSTANCE;
      }
      public String getName() {
          return this.name;
      }
      public String setName(String txt) {
          this.name = txt;
      }
    }

    Это ужасный фрагмент кода, но я вынужден привести его в качестве иллюстрации к своим доводам. Этот синглтон значит буквально «пользователь, в данный момент применяющий систему». Этот подход очень популярен во многих веб-фреймворках, где существуют синглтоны пользователей, веб-сессий и т. п.. Итак, типичный ответ на мой вопрос о разнице между синглтоном и классом-утилитой: «Синглтон инкапсулирует состояние».. Но это неверный ответ. Цель синглтона не в хранении состояния.. Вот класс-утилита, который делает то же, что и упомянутый ранее синглтон:

    class User {
      private static String name;
      private User() {}
      public static String getName() {
          return User.name;
      }
      public static String setName(String txt) {
          User.name = txt;
      }
    }

    Этот класс-утилита хранит состояние, и между ним и упомянутым синглтоном нет никакой разницы. Итак, в чем же проблема? И каков же правильный ответ? Единственно верный ответ состоит в том, что синглтон — это зависимость, которую можно разорвать, а класс-утилита — жестко запрограммированная тесная связь, которую разорвать невозможно. Иными словами, преимущество синглтонов в том, что в них можно добавить метод setInstance() наряду с getInstance(). Этот ответ верен, хотя я слышу его нечасто. Допустим, я использую синглтон следующим образом:

    Math.getInstance().max(5, 9);

    Мой код сцеплен с классом Math. Иными словами, класс Math — зависимость, на которую я полагаюсь. Без этого класса код не будет работать, и для его тестирования мне придется оставлять класс Math доступным, чтобы иметь возможность выполнять запросы. В случае с данным конкретным классом эта проблема невелика, поскольку он весьма примитивен. Однако если синглтон большой, то мне, возможно, придется применять мокинг или заменять его чем-то, что лучше подходит для тестирования. Проще говоря, я не хочу, чтобы метод Math.max() выполнялся во время работы юнит-теста. Как мне это сделать? А вот как:

    Math math = new FakeMath();
    Math.setInstance(math);

    Паттерн «Синглтон» обеспечивает возможность заменить инкапсулированный статический объект, что позволяет тестировать объект. Правда в следующем: синглтон намного лучше класса-утилиты только потому, что позволяет заменить инкапсулируемый объект. В классе-утилите нет объекта — мы не можем ничего изменить. Класс-утилита — неразрывная жестко запрограммированная зависимость — чистейшее зло в ООП.

    Итак, о чем я? Синглтон лучше класса-утилиты, но все же является антипаттерном, причем довольно плохим. Почему? Потому, что логически и технически синглтон — глобальная переменная, ни больше, ни меньше.. А в ООП нет глобальной области видимости. Поэтому глобальным переменным здесь не место. Вот программа на С, в которой переменная объявлена в глобальной области видимости:

    #include <stdio>
    int line = 0;
    void echo(char* text) {
       printf("[%d] %s\n", ++line, text);
    }

    Всякий раз когда мы вызываем echo(), инкрементируется глобальная переменная line. Чисто технически переменная line видна из каждой функции и каждой строки кода в *.с-файле.. Она видна глобально. Хвала разработчикам Java за то, что они не скопировали эту возможность из языка С. В Java, как и в Ruby и во многих других недо-ООП-языках, глобальные переменные запрещены. Почему? Потому что они не имеют никакого отношения к ООП. Это чисто процедурная возможность. Глобальные переменные однозначно нарушают принцип инкапсуляции. Они просто ужасны. Надеюсь, мне больше не придется объяснять это в данной книге. Мне кажется очевидным, что глобальные переменные настолько же плохи, насколько плох оператор GOTO.

    Однако, несмотря на все доводы против глобальных переменных, кто-то нашел способ привнести их в Java, создав тем самым паттерн «Синглтон».. Это попросту издевательство над принципами объектно-ориентированного проектирования, ставшее возможным благодаря наличию статических методов. Эти методы технически позволяют такое жульничество.
    Никогда не используйте синглтоны. Даже не думайте.

    «Чем их заменить? — спросите вы. — Если нам нужно, чтобы нечто было доступно многим классам в рамках всего программного продукта, что мы можем сделать?» Скажем, нам очень надо, чтобы большинство классов знало о том, какой пользователь в данный момент вошел в систему. У нас нет классов-утилит и синглтонов. Что у нас есть? Инкапсуляция!

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

    Все, что нужно вашему классу для работы, должно быть передано посредством конструктора и инкапсулировано внутри класса.. Вот и все. Без исключения. Объект не должен затрагивать ничего, кроме своих инкапсулированных свойств. Вы можете сказать, что придется инкапсулировать слишком много: подключения к базам данных, вошедшего в систему пользователя, аргументы командной строки и т. п. Да, действительно, всего этого может оказаться слишком много, если класс чересчур большой и недостаточно цельный. Если вам нужно инкапсулировать слишком много, переработайте класс — уменьшите его, о чем говорилось в разделе 2.1.

    Но никогда не применяйте синглтон. Для этого правила нет исключений.

    Функциональное программирование


    Я часто слышу такой довод: если объекты небольшие и неизменяемые и при этом не задействуются статические методы, то почему бы не использовать функциональное программирование (ФП)? Действительно, если объекты элегантны настолько, насколько рекомендуется в данной книге, то они весьма похожи на функции.. Итак, зачем нам нужны объекты? Почему бы просто не использовать Lisp, Clojure или Haskell вместо Java или C++?

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

    class Max implements Number {
      private final int a;
      private final int b;
      public Max(int left, int right) {
          this.a = left;
          this.b = right;
      }
      @Override
      public int intValue() {
          return this.a > this.b ? this.a : this.b;
      }
    }

    Вот как мы должны его применять:

    Number x = new Max(5, 9);

    А вот как мы задали бы в Lisp функцию, которая делала бы то же самое:

    (defn max
         (a b)
         (if (> a b) a b))

    Итак, зачем же использовать объекты? Код на Lisp намного короче.

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

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

    Компонуемые декораторы


    Кажется, этот термин я придумал. Компонуемые декораторы — просто объекты-обертки над другими объектами. Они являются декораторами — известным паттерном объектно-ориентированного проектирования, — но становятся компонуемыми, когда мы объединяем их в многослойные структуры, к примеру:

    names = new Sorted(
        new Unique(
            new Capitalized(
                new Replaced(
                    new FileNames(
                        new Directory(
                            "/var/users/*.xml"
                        )
                     ),
                     "([^.]+)\\.xml",
                     "$1"
                 )
             )
         )
    );

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

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

    Это то, что я называю компонуемыми декораторами. Классы Directory, FileNames, Replaced, Capitalized, Unique и Sorted — декораторы, поскольку их поведение полностью обусловлено инкапсулируемыми ими объектами. Они добавляют некоторое поведение инкапсулированным объектам. Их состояние совпадает с состоянием инкапсулированных объектов.

    Иногда они предоставляют тот же интерфейс, что и инкапсулируемые ими объекты (но это не обязательно). К примеру, Unique — это Iterable, также инкапсулирующий итератор по строкам. Однако FileNames — это итератор по строкам, инкапсулирующий итератор по файлам.
    Большая часть кода в чистом объектно-ориентированном ПО должна быть похожа на приведенный ранее. Мы должны композировать декораторы друг в друга, и даже чуть более того.. В какой-то момент мы вызываем app.run(), и вся пирамида объектов начинает реагировать. В коде совсем не должно быть процедурных операторов вроде if, for, switch и while. Звучит как утопия, но это не утопия.

    Оператор if предоставляется языком Java и используется нами в процедурном ключе, оператор за оператором. Почему бы не создать на замену Java язык, в котором был бы класс If? Тогда вместо следующего процедурного кода:

    float rate;
    if (client.age() > 65){
      rate = 2.5;
    }
    else {
       rate = 3.0;
    }

    мы бы писали такой объектно-ориентированный код:

    float rate = new If(
      client.age() > 65,
      2.5, 3.0
    );

    А как насчет такого?

    float rate = new If(
      new Greater(client.age(), 65),
      2.5, 3.0
    );

    И наконец, последнее улучшение:

    float rate = new If(
      new GreaterThan(
         new AgeOf(client),
         65
      ),
      2.5, 3.0
    );

    Так выглядит чистый объектно-ориентированный и декларативный код. Он не делает ничего — просто объявляет, чем является rate.

    С моей точки зрения, в чистом ООП не нужны операторы, унаследованные из процедурных языков вроде С. Не нужны if, for, switch и while. Нам нужны классы If, For, Switch и While. Чувствуете разницу?

    Мы еще не дошли до таких языков, но рано или поздно обязательно дойдем. Я в этом уверен. А пока что старайтесь держаться подальше от длинных методов и сложных процедур. Проектируйте микроклассы так, чтобы они были компонуемыми.. Убедитесь, что они могут повторно использоваться как элементы композиции в более крупных объектах.
    Я бы сказал, что объектно-ориентированное программирование — это сборка крупных объектов из более мелких.

    Какое отношение это имеет к статическим методам? Я уверен, вы уже поняли: статические методы не могут быть скомпонованы никоим образом. Они делают невозможным все то, о чем я говорил и что показывал ранее. Мы не можем собирать крупные объекты из более мелких с применением статических методов. Эти методы противоречат идее компоновки. Вот вам еще одна причина того, что статические методы — чистое зло.

    В заключение: нигде и никогда не задействуйте в своем коде ключевое слово static — этим вы окажете себе и тем, кто будет использовать ваш код, большую услугу.

    » Более подробно с книгой можно ознакомиться на сайте издательства

    Для Хаброжителей скидка 20% по купону — Java
    Издательский дом «Питер»
    201,00
    Компания
    Поделиться публикацией

    Похожие публикации

    Комментарии 105

      +3
      Для применения статических методов нет ни единого оправдания ни в одной ситуации. Забота о производительности не считается
      Вот как вычисление большего из двух чисел должно выглядеть в ООП. Number x = new Max(5, 9);
      Т.е. вместо вызова быстрого статического метода для нахождения максимума автор предлагает создавать новый объект? Я ничего не упускаю?
        +2

        Ничего не упускаете )). Ну, может быть, за исключением того, что нужна новая виртуальная машина, которая будет выполнять new Max быстро.

          +1

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


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

        +11
        Фанатик.
          +4
          Почему бы не использовать всю палитру взможностей языка а не ограничивать себя лишь объектами? Не говоря уж о том, что большинство современных языков сознательно делают мультипарадигменными, а вы предлагаете сделать шаг назад и ограничиваться только объектно ориентированной парадигмой.
            +9
            Неокрепшие умы теперь буду аргументировать адовые подходы тем, что «про это же в умной книжке написано!».
              0
              Второй том планируете?
                +1
                Автору надо писать код на Smalltalk либо на Ruby там ООП реализовано полноценно, но лучше конечно Smalltalk ибо это отец ))
                  0
                  Автор на Ruby в том числе очень много пишет. Посмотрите его github.
                    0
                    Просто довольно странно жаловаться на реализацию ООП в языке который по факту не когда не имела полноценную поддержку ООП. Это тоже самое жаловаться на самокат потому что у него нет двигателя
                      0

                      Это какой язык никогда не имел полноценной поддержки ООП?

                        +1
                        Лисп, не?
                          0
                          А вы про какой ООП говорите? общепринятый или тот который от Алана Кея? Все же есть разница. Разница на уровне что ООП Кея было все же интереснее чем ООП которое было в Simula (да даже ооп в симуле было интереснее чем ООП в java), а поскольку из выживших и используемых языков из тех что подходят под определения Кея остался только Erlang… то лучше просто забить и смотреть в сторону всяких там F# и подобных (что-нибудь с сильной системой типов и упором на функциональную абстракцию а не на структурное программирование).
                            0
                            А вы про какой ООП говорите?

                            Ну в основном мне, конечно, интересно какой ООП имел в виду комментатор, которому я отвечал.

                              0
                              «общепринятый или тот который от Алана Кея» — Хмм, ну походу тут второй вариант ответа я имел ввиду исходя из моего комментария где я советовал Автору статьи писать код на Smalltalk and Ruby.
                              Smalltalk досих пор жив, вам не кто не запрещает его использовать, даже веб фреймы есть ))
                              + Ruby хоть и не на 100% канон, но очень близок.
                            +1
                            У моего самоката двигатель все же есть.
                        +5
                        Много лет использую статические методы в средних и больших проектах. Никогда не выхватывал проблем из-за этого. Видимо автор живет в каком-то своем, особом мире. Да и фанатичностью от текста сильно попахивает.
                          0
                          Абсолютно все пишут в процедурном стиле. Этот же стиль пропагандируется создателями популярнейших фреймворков, типичный пример Spring, где у тебя есть контроллер, который вызывает внутри себя сервис который внутри допустим создает объект с геттерами и сеттерами и сохраняет его используя репозиторий.
                          Весь этот код от начала до конца процедурный, ООП там и не пахнет.

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

                          В общем, рекомендую один раз попробовать что-нибудь написать в таком стиле, как минимум на б-гомерзкие сервисы с репозиториями потом уже будут не так милы.
                            +1
                            disclaimer: Это не камень в огород ООП, а размышление на тему «как и почему»

                            Абсолютно все пишут в процедурном стиле.

                            Потому, что он воспринимается более декларативно. А человек — такое существо, мышление которого заточено под простые, линейные алгоритмы.
                            Сначала линейный (процедурный)
                            Открыть машину              [openCarDoor()]
                            Сесть на место водителя     [takeADriversSeat()]
                            Завести машину              [startEngine()]
                            

                            Теперь ООП
                            "Попросить машину" открыть дверь                     [car.openDoor()]
                            "Попросить машину" разместить меня на месте водителя [car.setDriver(myself)]
                            "Попросить машину" завестись                         [car.startEngine()]
                            

                            Первое гораздо ближе к тому, как думает человек.

                            То же самое, кстати, касается и асинхронности. Посмотрите как создатели Kotlin'а подошли к внедрению корутин и чем аргументируют — ровно тем, же, что линейная структура кода (который под капотам ни черта не линейно ессно) понимается и воспринимается на порядки проще, чем callback-hell или нагромождение async-await'ов
                              –1
                              Мне кажется на ООП этот код мог выглядеть немного подругому, например:
                              new Машина_С_Пассажирами(машина, [коля, вася, петя])


                              А где-то в недрах класса «Машина_С_Пассажирами» должна быть проверка на открыть дверей в объекте «машина» (ну или если абстрактно — вообще на доступность посадки).
                                +1
                                вы сейчас привели в пример оркестрацию, без условий и какой-либо логики. тупо последовательность действий. Этот код будет выглядеть идентично вне зависимости от выбранного стиля.

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

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


                                    зачем вы тут искали сложную логику.

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


                                    Я предположу что у того как выглядит большая часть кода причина несколько иная нежели "нам так думать естественнее". Когда люди учатся они обычно смотрят на то, как это делают другие. Так или иначе формируется представление о том как надо делать дела, за счет практики это представление формируется в привычку, ну и опять же — никто не думает что делает. Так растет случайная сложность, связанность и т.д.


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

                                0

                                Можете привести пример "православного ООП" и на примере же показать почему Спринг это не ООП?

                                  0

                                  Главная фича ООП это полиморфизм, который в спринге считай не используется. Там как правило контроллер не реализует интерфейсы. В крайнем случае есть тестовая реализация и основная.


                                  Кроме того контроллеры и всё остальное как правило не имеют состояния и ничего не инкапсулируют.


                                  Православное ООП это интерфейс List в джаве, под который можно написать свою реализацию и передать её везде, где нужен List.

                                    +1
                                    Главная фича ООП это полиморфизм, который в спринге считай не используется. Там как правило контроллер не реализует интерфейсы. В крайнем случае есть тестовая реализация и основная.

                                    Простите, а зачем вам полиморфизм в контроллерах? Как выглядят контроллеры в православных ООП языках и в чем их фишка?


                                    Кроме того контроллеры и всё остальное как правило не имеют состояния и ничего не инкапсулируют.

                                    Ну, такая уж идеология фреймворка — данные (стейт) храните в базе. А вообще — делайте как хотите: As a rule of thumb, you should use the prototype scope for all beans that are stateful, while the singleton scope should be used for stateless beans


                                    Православное ООП это интерфейс List в джаве, под который можно написать свою реализацию и передать её везде, где нужен List.

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

                                      +1
                                      Простите, а зачем вам полиморфизм в контроллерах?

                                      В спринговых контроллерах не нужен и не используется. Собственно поэтому и говорят, что спринг не использует ООП.


                                      Ну, такая уж идеология фреймворка — данные (стейт) храните в базе.

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


                                      Вы можете написать интерфейс и использовать его везде

                                      Можем, но в этом как правило нет особенной необходимости и особенного смысла.

                                        +1
                                        Иделология ООП в том, чтобы создавать отдельные объекты, которые инкапсулируют поведение, а идеология спринга в том, чтобы делать методы, которые обрабатывают запросы

                                        Ну так веб-сервер (а мы говорим о спринге в контексте веб серверов в данный момент насколько я понимаю) это приложение, которое обрабатывает запросы. Возможно тогда дело не в том, что Спринг не ООП (потому что это не так), а в том что обсуждаемый нами use-case не требует применения ООП в типичных сценариях?

                                          0
                                          Возможно тогда дело не в том, что Спринг не ООП

                                          "Спринг не ООП" звучит не очень хорошо. Лучше сказать, что при использовании спринга не нужно применять ООП. Можно применять, но необходимости нет, API спринга ООП почти не утилизирует.


                                          а в том что обсуждаемый нами use-case не требует применения ООП в типичных сценариях?

                                          А в каких сценариях при использовании спринга применяется ООП?

                              +8
                              Учитывая обложку книги… А это точно не глобальный троллинг? :D

                              А вообще это просто кладезь хвостатыхкрылатых фраз.
                              Мы мыслим как функция, а не как компьютер.
                              Я просто инстанцирую объект. Это очень похоже на def в Lisp… В этом смысле ООП очень похоже на функциональное программирование.
                              Есть два подхода — декларативный и процедурный
                              Статические методы напоминают раковую болезнь объектно-ориентированного ПО
                              Просто инкапсулируйте пользователя во все объекты, в которых он может пригодиться.
                              Не нужны if, for, switch и while. Нам нужны классы If, For, Switch и While. Чувствуете разницу?
                              В мемориз, однозначно!
                                0
                                > Есть два подхода — декларативный и процедурный
                                может автор или переводчик ошибся?
                                так как почти везде используется «императивный».

                                А так можно было бы и почитать книженцию ради альтернативного мнения, иногда это подталкивает к интересным мыслям))
                                  0
                                  А книга разве переводная?
                                  В любом случае, «процедурный» используется в тексте очень часто. Вот еще цитатки:
                                  В коде совсем не должно быть процедурных операторов вроде if, for, switch и while.
                                  В Java, как и в Ruby и во многих других недо-ООП-языках, глобальные переменные запрещены. Почему? Потому что они не имеют никакого отношения к ООП. Это чисто процедурная возможность.
                                  Оператор if предоставляется языком Java и используется нами в процедурном ключе, оператор за оператором.
                                  Не похоже на ошибку, если честно. Скорее на кашу в голове, где прочно обосновалась идея «императивный == процедурный».
                                    0
                                    ну изначально он эту книгу на английском написал… а там кто знает, кто знает…
                                    > В Java, как и в Ruby и во многих других недо-ООП-языках, глобальные переменные запрещены. Почему? Потому что они не имеют никакого отношения к ООП. Это чисто процедурная возможность.

                                    вот с этим я наверное соглашусь.
                                    все остальное звучит действительно странновато…
                                    Ну в общем чистое ООП)))
                                      0
                                      Вы правда считаете, что глобальные переменные — это фишка именно процедурного программирования? И что в Java/Ruby они запрещены?
                                        0
                                        Я более чем уверен что они зло, запрещены они или нет это уже другой разговор, и да в процедурных языках они использовались хранения глобального состояния, в ООП этого можно избежать.
                                          0
                                          процедурных языках они использовались хранения глобального состояния, в ООП этого можно избежать.
                                          Ну так никто не мешает сделать тоже самое и при процедурном программировании. Создаете структурку, храните данные в ней, при необходимости проталкиваете ее в подпрограммы через ссылку на стеке (локальную переменную).

                                          Можно посмотреть в определение процедурного (и структурного) программирования. Там нет упоминания глобальных переменных. Как и нет при определении ООП.
                                            0
                                            из вики про Процедурное программирование
                                            с точки зрения программиста имеются программа и память, причем первая последовательно обновляет содержимое последней


                                            Из кники «Чистая архитектура. Искусство разработки программного обеспечения.»
                                            Структурное программирование

                                            Дейкстра показал, что безудержное использование переходов (инструкций
                                            goto ) вредно для структуры программы. Как будет описано в последующих главах, он предложил заменить переходы более понятными конструкциями >if >/ then / else и do / while / until.
                                            Подводя итог, можно сказать, что:
                                            Структурное программирование накладывает ограничение на прямую передачу управления.


                                            то есть глобальную память никто не отменял…

                                              0
                                              с точки зрения программиста имеются программа и память
                                              Так а глобальные переменные то где? :)
                                              Стек ведь тоже хранится в памяти (локальные переменные).
                                              И состояние объектов в ООП.

                                              Структурное программирование накладывает ограничение на прямую передачу управления.
                                              Все так. Но я в упор не понимаю, как это относится к теме глобальных переменных.
                                                –1
                                                А там и не указывается что именно стек… Просто память, она может быть и глобальной.
                                                Структурное программирование добавляет ограничения в виде конструкций «if/ then / else и do / while / until.» и не какого «goto»
                                                ООП в свою очередь добавляет Инкапсуляцию.
                                                А ФП вообще иммутабельность.
                                              0
                                              Как раз такой подход Стивен Прата в книге «Язык программирования C. Лекции и упражнения» приводит как пример написания программ на С в стиле ООП…
                                                0
                                                Не назовете номер главы? В идиале еще и страницы.
                                                Интересно глянуть на пример ;)

                                                А еще ООП подразумевает наследование/полиморфизм. Которые не нужны в упомянутом выше подходе (пробрасывать состояние через ссылки на стеке).
                                                  0
                                                  Не могу вспомнить, я читал ее уж слишком давно…
                                                  И на руках ее нет, а так где-то к концу книге.
                                                  но могу из этой…
                                                  Мартин Р. " Чистая архитектура. Искусство разработки программного обеспечения"

                                                  Объектно-ориентированное
                                                  программирование
                                                  Второй парадигмой, получившей широкое распространение, стала пара-
                                                  дигма, в действительности появившаяся двумя годами ранее, в 1966-м,
                                                  и предложенная Оле-Йоханом Далем и Кристеном Нюгором. Эти два программиста заметили, что в языке ALGOL есть возможность переместить кадр стека вызова функции в динамическую память (кучу), благодаря чему локальные переменные, объявленные внутри функции, могут сохраняться после выхода из нее. В результате функция превращалась в конструктор класса, локальные переменные — в переменные экземпляра, а вложенные функции — в методы. Это привело к открытию полиморфизма через строгое использование указателей на функции.

                                                  Подводя итог, можно сказать, что:
                                                  Объектно-ориентированное программирование накладывает ограничение на
                                                  косвенную передачу управления.


                                                  Там на самом деле много еще написано, в других книгах… но мне уже лень))
                                                    0
                                                    Не могу вспомнить, я читал ее уж слишком давно…
                                                    Яснопонятно.

                                                    но могу из этой…
                                                    Спасибо за цитату, но как она соотносится с темой нашего разговора? Да там явно написано про локальные переменные, мол придумали как их переместить на кучу, сделав некое замыкание.
                                                    благодаря чему локальные переменные, объявленные внутри функции, могут сохраняться после выхода из нее.
                                                      0
                                                      пойдем другим путем))
                                                      где в определениях процедурного(и структурного) программирования говорится
                                                      > Создаете структурку, храните данные в ней, при необходимости проталкиваете ее в подпрограммы через ссылку на стеке (локальную переменную).

                                                      там говорится про память… то есть возможность использовать глобальные переменные

                                                      В ООП инкапусляция говорит что переменные должны быть в объекте и доступ к ним должен осуществляться через интерфейс объекта(то есть глобальные переменные сразу отсекаются)

                                                      таким образом глобальные переменные явно или неявно относятся к процедурному программированию
                                                        0
                                                        там говорится про память… то есть возможность использовать глобальные переменные
                                                        Память != глобальные переменные. Вы упорно продолжаете фантазировать.

                                                        В ООП инкапусляция говорит...
                                                        Только вот она ничего не говорит о том, что не может быть синглтонов.
                                                          0
                                                          Память != глобальные переменные, да, представьте себе.
                                                          Но это не означает что программа не может использовать глобальные переменные, которые хранятся так же в памяти и в определениях не написано что надо обязательно использовать стек.
                                                            0
                                                            Так что вы доказать то пытаетесь?
                                                            Что в процедурном программировании можно использовать глобальные переменные? Можно.
                                                            При ООП тоже можно. Дальше то что?
                                                              0
                                                              я не пытаюсь чего либо доказать.

                                                              > Что в процедурном программировании можно использовать глобальные переменные? Можно.
                                                              При ООП тоже можно. Дальше то что?

                                                              А дальше если использовать глобальные переменные, то это противоречит ООП, то есть это уже процедурный или… или… подход к написанию программы.
                                                                0
                                                                Так а почему противоречит-то?

                                                                И почему это вдруг превращает все в процедурный… или… или… или какой подход? Вы забыли дописать свой комментарий?
                                                                  –1
                                                                  выше писал, я не любитель повторяться.

                                                                  Да и надоела мне эта беседа)
                                                                    0
                                                                    Ох как грубо))
                                                                    Сразу минус))

                                                                    Вы не приводите аргументы, а задаете вопросы, это похоже больше на экзамен чем на беседу…
                                                                    намекну… это противоречит Инкапсуляции...)))
                                                                      0
                                                                      Нисколько не грубо :D
                                                                      Вы просто «слились», достаточно неумело. Минус именно за неумелость.

                                                                      А еще за попытку аргументации к авторитету (спасибо за неуместные ссылки на книги, ага).

                                                                        0
                                                                        >Вы просто «слились», достаточно неумело.
                                                                        предлагаете по 20 раз вам писать одно и тоже?

                                                                        уместные ссылки на книги… они авторитетнее моего мнения и вашего.

                                                                        Ладно… перейдем к самому интересному.
                                                                        где в ООП есть понятие переменная?
                                                                        есть объекты у которых есть методы и свойства.
                                                                        доступ к свойствам должен осуществляться через методы объекта. И не важно синглтон это или нет. (полиморфизм, наследование не рассматриваем, к теме о глобальных переменных это не относиться)
                                                                          0
                                                                          И не важно синглтон это или нет.
                                                                          Как раз важно. Даже в этой статье упоминается что синглтон по сути эквивалентен глобальной переменной. Скажем, у вас есть синглтон 'document', а у него метод 'getRootNode'.

                                                                          Вообще предлагаю расширить вашу логику:
                                                                          Где в ООП есть понятие цикла? Это чисто процедурная возможность.
                                                                          Где в ООП есть понятие подпрограммы? Это чисто процедурная возможность.
                                                                          Где в ООП есть понятие числа? Это чисто процедурная возможность.

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

                                                                            0
                                                                            опять много вопросов…
                                                                            Где в ООП есть понятие числа? — объект?

                                                                            Где в ООП есть понятие цикла? Это чисто процедурная возможность.
                                                                            Где в ООП есть понятие подпрограммы? Это чисто процедурная возможность.
                                                                            разве?

                                                                            А мне вот кажется в ООП убрали понятие перемменая… и перешли на абстракции…

                                                                            >Как раз важно. Даже в этой статье упоминается что синглтон по сути эквивалентен глобальной переменной. Скажем, у вас есть синглтон 'document', а у него метод 'getRootNode'.

                                                                            и как это противоречит «есть объекты у которых есть методы и свойства.
                                                                            доступ к свойствам должен осуществляться через методы объекта.»?

                                                                            >Даже в этой статье упоминается что синглтон по сути эквивалентен глобальной переменной.
                                                                            Вы же сами говорите что статья бредовая)
                                                                              0
                                                                              >Все это время я пытался подвести, что как раз возможность дать объектам-синглтонам глобальный идентификатор (т.е. по сути положить их в глобальную переменную) не противоречит идеи ООП. Т.е. это ортогональные понятия.

                                                                              нету тут понятия глобальной переменной.
                                                                              так как состоянием синглтоноа управляет сам синглтон.

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

                                                                              к глобальной переменой может обратиться любая часть программы и изменить ее.
                                                                                0
                                                                                А мне вот кажется в ООП убрали понятие перемменая… и перешли на абстракции…
                                                                                Спасибо за еще одну сакральную фразу!

                                                                                По поводу сиглтонов и глобальных переменных. Конечно можно настаивать что это «аттрибуты», а не переменные и т.п. Но обычно придерживаются более свободной терминологии. Так гляньте в гайд к Squeak, там упоминается 'global scope' (хотя по факту все как вы написали). Или в Javascript мы создаем глобальную переменную, а под капотом создается атрибут у глобального объекта (window).

                                                                                к глобальной переменой может обратиться любая часть программы и изменить ее.
                                                                                Ну что за бред. Модульность, области видимости, различные скоупы, АТД и все такое?

                                                                                Еще раз подчеркну, что при процедурном программировании вовсе не обязательно иметь глобальные переменные (не более, чем при ООП). Не путайте его со структурным.
                                                                                  0
                                                                                  нет переменных в ООП…
                                                                                  User user = new User();
                                                                                  «Создан объект user» — ООП
                                                                                  «переменой user присвоена ссылка из кучи...» — не ООП
                                                                                  сама терминология разная.
                                                                                  В JAVA без костылей глобальную переменную нельзя создать, можно только создать класс в котором будут открытые статические свойства класса, но это уже костыль.
                                                                                  В Ruby почти все запихнули в объект, как пример
                                                                                  "-1234.abs"…
                                                                                  Не надо путать парадигму с реализацией
                                                                                    0
                                                                                    необязательно != невозможно, парадигма этого не запрещает.

                                                                                    ООП как раз таки запрещает(вернее в ней это невозможно сделать).
                                                                                    Скорее реализация языка дает возможность переключится на другую парадигму
                                              +1
                                              Переводная. Переводил не автор.
                                            0
                                            Просто инкапсулируйте пользователя во все объекты, в которых он может пригодиться.

                                            Вот конкретно с этим пунктом не всё так очевидно. Автор, конечно, отжигает по-полной, но здравые идеи иногда попадаются и среди такого. Неявные зависимости от глобальных переменных/синглтонов и явные зависимости от конкретных библиотек/статических методов действительно может иметь смысл передавать объекту при создании явно. Конечно, речь не о том, чтобы передавать каждому объекту свою реализацию функций вроде max/between, но вот сессии/пользователи, доступ к БД, логгер, контекст текущего запроса — эти значения нужны почти везде, и передавать их всюду явно ручками лениво, но это заметно добавляет коду явности и тестируемости, так что резон в этом есть: A theory of modern Go.

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

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

                                              +2
                                              Я просто инстанцирую объект.
                                              Просто инкапсулируйте пользователя во все объекты, в которых он может пригодиться.
                                              Не нужны if, for, switch и while. Нам нужны классы If, For, Switch и While.

                                              Напомнило телемагазины: вы просто прикрепляете тренажер, все остальное он делает за вас! Не нужны изнуряющие тренировки и диеты!
                                                +1
                                                Есть два подхода — декларативный и процедурный

                                                на какой джуна подсадишь, на какой сам сядешь?
                                                +1
                                                ...
                                                int x = Math.between(5, 9, 13);
                                                if (/* Надо ли? */) { System.out.println("x=" + x); }
                                                ...

                                                Процессор в обоих случаях найдет значение 9.

                                                Рантайм джавы достаточно умный, чтобы произвести оптимизацию и вычислить флаг заранее, поняв при этом, надо ли считать x
                                                  +7

                                                  Предлагаемое напоминает борьбу с ветряными мельницами. Примеры непрактичны.
                                                  Похоже на попытку внести фанатичный ФП-подход и почти ленивые вычисления в императивный чисто-ООП язык. Даже непонятно, почему нет перехода к lambda-функциям, которые появились ещё в java 8.


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

                                                  Достаточно написать статический метод compose, или даже взять существующий Function#compose() или Function#andThen(), и статические (и нестатические) методы чудесным образом начнут превращаться в объекты комбинироваться!


                                                  import java.util.function.*;
                                                  
                                                  class Main{
                                                      public static void main (String[] args) {
                                                          Function<Double,Double> cos_sin = ((Function<Double,Double>)Math::cos)
                                                                  .compose(Math::sin);
                                                          System.out.println(Math.cos(Math.sin(0.5)));
                                                          System.out.println(cos_sin.apply(0.5));
                                                      }
                                                  }

                                                  И получается, вся эта ересь из статьи не нужна.


                                                  Кроме того, фанатичные подходы обычно так сказываются на производительности (и/или читаемости кода), что лучше всё написанное выкинуть, забыть и переписать на ассемблере. Совет "всегда используйте объекты" плох из-за категоричности. Нужно использовать правильные для конкретных ситуаций инструменты. Иногда это объекты, иногда — статические методы.

                                                    0
                                                    Дай функциональщику палец, он и руку откусит. Ни одного внятного аргумента, сплошная вкусовщина. Как можно не видеть уродливых лишних «new» в этих листингах мне не понятно
                                                      +4
                                                      Мда, «ООП головного мозга», оно такое))
                                                        +8
                                                        Это же тот мракобес, который любую проблему решает еще одним декоратором:
                                                        image

                                                        Он в свое время знатно всех в twitter.com/backendsecret повеселил, тоже эту книжку все пиарил.
                                                          0
                                                          names = new Sorted(
                                                              new Unique(
                                                                  new Capitalized(
                                                                      new Replaced(
                                                                          new FileNames(
                                                                              new Directory(
                                                                                  "/var/users/*.xml"
                                                                              )
                                                                           ),
                                                                           "([^.]+)\\.xml",
                                                                           "$1"
                                                                       )
                                                                   )
                                                               )
                                                          );

                                                          Считаете ли вы этот код чистым и простым для понимания?

                                                          Я взгляну на этот код со стороны C#, там такое тоже встречается. Знаете, в чем проблема примера? Он не дает контроля. Например, я не знаю, полные имена выбираются или нет. Не знаю порядок сортировки. Приходится опираться не на дефолты стандартной либы, а на дефолты реализации оберток «Sorted» и «FileNames». Тот же самый пример можно переписать с использованием стандартных средств C#:
                                                          names = Directory.GetFiles()
                                                            .Select(file => file.Name)
                                                            .Select(fileName => fileName.Replace(...))
                                                            .Select(proceedFileName => proceedFileName.ToUpper())
                                                            .Distinct()
                                                            .Sort();


                                                          Другая проблема: зачем называть переменную names? Почему просто не дать осмысленное название переменной и избавить меня от необходимости читать всю эту лесенку?

                                                          Наконец, зачем вставлять регулярки в пример «чистого и простого для понимания кода»? В реале подобная замена будет вынесена в отдельный метод с нормальным названием.

                                                          Думаю, для холивара этих замечаний будет достаточно.

                                                          Самая ценная часть статьи — проблемы, порождаемые статикой. Самая ошибочная: упор на предлагаемые решения и избегание альтернатив. Проблемы статики решаются самыми разными способами, и декларативный подход далеко не всегда самый лучший.
                                                            +4
                                                            Нда… Автор в принципе не понимает функционального программирования. Чем то ему чистые функции не улыбаются.
                                                              +3
                                                              А мне как раз больше нравится именно категоричный стиль изложения, как у Егора.

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

                                                              Для хейтеров такого подхода я рекомендовал бы его же книгу «256 Bloghacks», где он описывает почему он так делает. Пара цитат:

                                                              «I am not afraid of being wrong. That's what attracts readers and provoke them to think. I am not just saying what I think, but I provoke them to agree or disagree with me. Sometimes I am wrong. But it's better to be wrong than to say nothing. If I think something about something I just say it, strongly taking one side of discussion».

                                                              «It's their (the readers) job to find truth in the middle, not mine. My job is to strongly and explicitly defend one side of the argument. They will find another side somewhere else, they'll compare, and they'll draw their own conclusions».
                                                                0
                                                                Главное, чтобы автор не преподавал в ВУЗе где-нибудь
                                                                  0

                                                                  Думаете, там можно что-то испортить?

                                                                    +4
                                                                    Егор способный, он сможет…
                                                                  +3
                                                                  Егор Бугаенко? Тот самый сексист который считает что женщинам нельзя работать в айти?
                                                                    +1

                                                                    Его уже переубедили, и он уже извинился. Вот за EO он еще не извинился, за это можно ругать.

                                                                    0
                                                                       CMP AX, BX
                                                                       JNAE greater
                                                                       MOV CX, BX
                                                                       RET
                                                                    greater:
                                                                       MOV CX, AX
                                                                       RET
                                                                    


                                                                    Автор:
                                                                    … Это ассемблерная «подпрограмма» для процессора Intel 8086… Она находит и возвращает большее из двух чисел. Мы помещаем их в регистры AX и BX соответственно, а результат попадает в регистр CX. ...


                                                                    JNAE — Jump short if not above or equal.
                                                                    В регистре CX будет возвращаться меньшее значение, а не большее.

                                                                    Из практического любопытства проверить это можно здесь: http://carlosrafaelgn.com.br/asm86/
                                                                    предварив ассемблер выше загрузкой в регистры
                                                                       MOV AX,2
                                                                       MOV BX,3
                                                                    

                                                                    и посмотрев что вернется в CX…
                                                                      +1
                                                                      Ни одного аргументированного комментария. Если поискать, то не один Егор говорит о том, что статические методы — это плохо. lmgtfy.com/?q=why+static+method+is+bad
                                                                      Использование статических методов, переменных навязано извне и мы в своем коде, думаем, что это нормально и продолжаем использовать статику. Однако это процедурный стиль из 70-ых годов ( dl.acm.org/citation.cfm?id=953355 — в 73 уже поняли, что это плохо), которому пора на свалку.

                                                                      Вы можете говорить, что у вас в проектах статические методы, глобальные переменные, синглтоны, но тогда расскажите, как вы тестируете свои проекты, как вы их расширяете, как поддерживаете качество кода? Уверен, что вы не писали хорошие тесты на свой проект, вместо расширения существующего, вы переписывали все заново (потому что тяжело расширять или даже невозможно), а о качестве и речи быть не может (ну кроме ревью реквестов, и то не у всех).
                                                                        0
                                                                        Ну например, не вижу больших проблем в веселом классе java.lang.Math, который весь из статиков. И что-то не вижу ему альтернативы. Или у вас таки проекты без математических функций?
                                                                          0
                                                                          Конечно, есть проекты с работой над числами!
                                                                          github.com/yegor256/cactoos/tree/master/src/main/java/org/cactoos/scalar
                                                                          — можно вот это использовать для операций над числами, это и гибче, и быстрее
                                                                            +2
                                                                            Конечно, есть проекты с работой над числами!
                                                                            github.com/yegor256/cactoos/tree/master/src/main/java/org/cactoos/scalar
                                                                            — можно вот это использовать для операций над числами, это и гибче, и быстрее
                                                                            Сильное заявление, проверять его вы конечно не будете?
                                                                          0
                                                                          Уверен, что вы не писали хорошие тесты на свой проект, вместо расширения существующего, вы переписывали все заново (потому что тяжело расширять или даже невозможно), а о качестве и речи быть не может (ну кроме ревью реквестов, и то не у всех).

                                                                          Диагноз по фотографии?
                                                                            0
                                                                            1) Никто не говорит, что статические методы/переменные есть хорошо. Все и так в курсах, что их нужно стараться делать поменьше.
                                                                            2) Хватит уже строить демагогию на ложной связи «глобальные переменные — процедурное программирование».
                                                                            3) Аргументированных коментариев полно. Например вы ответили (судя по всему ошибочно) на комментарий, где указали на явную ошибку в статье. Конкретную. Куда уже аргументированнее я не знаю.
                                                                            4) Сама статья страдает с аргументацией. Как тут выше написали «А мне как раз больше нравится именно категоричный стиль изложения, как у Егора». Достаточно сложно выступать с аргументированной критикой на такие статьи.
                                                                            Вот наиболее эффективный и целесообразный способ спора в таком случае. Замеьте, рейтинг у комментария в разы больше, чем у самой статьи ;)
                                                                              0
                                                                              2) Почему глобальные переменные это не процедурное программирование? Есть доказательство?
                                                                              3) Да, я ошибочно ответил, просто когда я отвечал, это был последний комментарий, и я по ошибке нажал на ответить, а не добавил новый комментарий. Классно, что нашли ошибку в статье, но она никак не связана с огромным неконструктивным негативом в других комментариях.
                                                                              4) В статье каждое утверждение доказывается каким-либо образом, примерами, логическими связями

                                                                              Тот способ, на который вы указали, является эмоциональным выражением и больше ничем. Предполагаю, что слово «фанатик» в этом контексте больше негативное, и, точно уверен, ничего конструктивного в нем нет.
                                                                                0
                                                                                2) Позвольте мне ответить вопросом на вопрос. Почему глобальные переменные это не ООП? Есть доказательство?

                                                                                Только, пожалуйста, не приводите в качестве доказательство обсуждаемую книгу и статью :D

                                                                                3) Ок. Самый первый комментарий к статье — предложенное решение слишком неэффективно в плане скорости. Другими словами, предложенная методика совершенно неприменима на практике в Java. Это достаточно конструктивно для вас?

                                                                                4) Ложь. Вся статья по сути набор ультимативных заблуждений автора в стиле
                                                                                Я думаю, что декларативный код окажется быстрее. Он лучше оптимизирован.
                                                                                Не, ну если для вас «я думаю» это доказательство, ну ок…

                                                                                  0
                                                                                  Потому что глобальная переменная противоречит DI (dependency injection). Использование глобальные переменных снижает возможность переиспользования кода. dl.acm.org/citation.cfm?id=953355 — вот классная статья, почитайте!
                                                                                    0
                                                                                    Как я понимаю вы ответили на пункт 2. Отлично, я рад что по остальным пунктам у вас не осталось комментариев.

                                                                                    2)
                                                                                    Потому что глобальная переменная противоречит DI (dependency injection).
                                                                                    Допустим. Только вот DI != ООП.
                                                                                    Использование глобальные переменных снижает возможность переиспользования кода.
                                                                                    Снижает. Только вот это относится не только к ООП, но и к процедурному программированию.
                                                                                    dl.acm.org/citation.cfm?id=953355 — вот классная статья, почитайте!
                                                                                    Круто, обязательно почитаю!

                                                                                    А по сути то добавить есть что?
                                                                                  +1
                                                                                  Тот способ, на который вы указали, является эмоциональным выражением и больше ничем.

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

                                                                            +5
                                                                            Очень плохо, что такие «книги» выпускаются в свет. Очень, очень плохо.
                                                                              –2
                                                                              Поясните?
                                                                                +3
                                                                                Человек пропихивает в массы крайне мракобесные идеи.
                                                                                Почитайте хотя бы вот.
                                                                                  –3
                                                                                  Я как раз поддерживаю Егора, его идеи и взгляды мне нравятся и я их использую каждый день :)
                                                                                    +2
                                                                                    То есть вам нравится такой вот программный кот???
                                                                                      –1
                                                                                      Только такой код нравится!
                                                                                        0
                                                                                        Мы за вас рады!
                                                                              +1
                                                                              Что такое статические методы?
                                                                              Статические методы подразумевают за собой очень простую вещь — случаи,
                                                                              когда не нужен экземпляр класса (т.е. объект)
                                                                              . Возникновение подобных случаев чаще всего обусловлено работой со встроенными примитивными типами.

                                                                              Статические методы имеют особый выделенный опкод JVM, обрабатываются отдельно, работают быстро, и являются вероятным кандидатом на инлайнинг.
                                                                              Соответственно, смысл использования статических методов такой:
                                                                              1. Отсутствие объекта. (т.е. они не пересекаются с ООП-парадигмой***)
                                                                              2. Скорость.

                                                                              ***
                                                                              Это справедливо и для C++. Причем, ООП подразумевает за собой именно наличие объектов, а не просто размещение функции в class-scope. Для Java последнее обусловлено самой идеологией, когда бинарно/физически ничто не может существовать вне .class-файлов.


                                                                              Как только люди начинают смешивать статические методы и объекты (т.е. в процедурную привносится объектная парадигма) — это приводит к некоторым проблемам.
                                                                              Но это вина не самих статических методов, а смешения.

                                                                              Было бы лучше указать на проблемы смешения/пересечения статических методов и объектов, а не обвинять их в духе что они «зло» или даже «статические методы в любом контексте — безошибочный индикатор плохого программиста, понятия не имеющего об ООП», «Мы должны прекратить применять статические методы.» и т.д.
                                                                                +1
                                                                                int x = Math.max(5, 9);

                                                                                «Это совершенно неправильно и не должно использоваться в настоящем объектно-ориентированном проектировании.»


                                                                                Дело в том, что эта строка не объектно-ориентированная вовсе. Вот именно ООП тут нет.
                                                                                Не надо за него принимать scope функции max.

                                                                                Стоит ли использовать процедурную парадигму для вычислений примитивных типов?
                                                                                Вполне. Это по-первых минимально достаточно, и во-вторых обеспечит необходимую скорость. Почему всё живет в Math? Потому что это не ООП, это не объект, а не более чем scope, своего рода «исторический namespace». Именно так это и надо понимать.

                                                                                Функцию max, работающую с примитивными встроенными типами просто некуда было приписать в Java, которая опирается на устройство java-машины, в оригинале, java-процессора, выполненного в железе. Вне классов ничто существовать не может. Да и как это вообще, могло выглядеть в те годы, в середине 90х?

                                                                                Статические методы можно рассматривать как преимущество, как возможность использования чисто процедурной парадигмы программирования. Зачем она может понадобиться внутри ООП-парадигмы?
                                                                                При портировании программ кода с семейства языков C, C++.
                                                                                При организации эффективных вычислений.
                                                                                Для run-time API, при связи Java-программ с внешним программным обеспечением, таким как run-time библиотеки, функции ОС, для связи с внешней эффективной реализацией самих Java-библиотек. Это фундаментальная основа j2me, а также различных embedded java-машин, скажем в автомобилях концерна Volkswagen.

                                                                                Таким образом, приносить в жертву статические методы пока что рановато. Надо просто применять их при достаточной необходимости и по назначению.
                                                                                  0
                                                                                  int x = Math.max(5, 9);
                                                                                  
                                                                                  Это совершенно неправильно и не должно использоваться в настоящем объектно-ориентированном проектировании.

                                                                                  Интересно, а если бы создатели Java сделали поиск максимума арифметической операцией наравне со сложением и умножением, то переходить к new Max() уже не нужно было бы?
                                                                                  Или тогда уже нужно было бы переходить и к new Plus() и new Mult()?

                                                                                    0

                                                                                    А если бы у вас была функция + и функция -, на которую можно было бы ссылаться и передавать как аргумент другим функциям? Ну и в целом если бы Java позволяла оперировать такими понятиями как функция.

                                                                                      +1
                                                                                      А если бы… главный аргумент, Егор хочет перевести все в ООП, но так ли это важно? неужели все является объектом? или операцией?, она тоже существуют как объект? не даром существуют такие паттерны как «Transaction Script». Вот если честно pattern matching в ФП(хотя я не силен в ФП, пока на стадии обучения) напоминает утиную типизацию,… но все переводить на процесс вычисления, как-то странно, как и наоборот… И дело далеко не в парадигме, а скорее в понимании кода, не понимаю и вряд ли пойму этого фанатизма, а так же кода, ведь давно существует принцип KISS
                                                                                        +2
                                                                                        неужели все является объектом? или операцией?

                                                                                        Для этого надо погрузиться в историю и вспомнить откуда пошла фраза everything is an object. А пошла она из публикации Early history of smalltalk. Это был один (только один из 6-ти) принципов по которому проектировался smalltalk 78.


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


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


                                                                                        То есть суть не в том что бы все было объектом, а в том что бы от данных избавиться. Дальше вся система выстраивается по принципам actor model. То есть правильнее называть "тру ооп" не парадигмой а моделью вычисления. Причем внутри объекта вы можете использовать что ФП (Erlang как пример) что классические процедурные подходы к которым все привыкли.


                                                                                        По источникам если вам интересно, можно начать вот с этого, продолжить на c2.wiki, посмотреть общение автора термина ООП и автора Erlang и о том как они восхваляют prolog и lisp и в крадце рассказывают историю ООП и отношение к нему (скажем последние лет 20 Алан больше в Meta языки ударился).


                                                                                        Вот если честно pattern matching в ФП(хотя я не силен в ФП, пока на стадии обучения) напоминает утиную типизацию

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


                                                                                        а скорее в понимании кода

                                                                                        в том то и суть, объекты Алана — это немного выше уровнем нежели код. Представьте это как модуль, подсистему, сервис или, что там модно нынче, микросервис.


                                                                                        ведь давно существует принцип KISS

                                                                                        Существует распространенное мнение что KISS о том что бы делать то, что первое пришло в голову. Однако KISS это больше о проектировании систем так, что бы эту систему было просто эксплуатировать. В силу простоты принципа наверное, что я нахожу ироничным.


                                                                                        если мы возьмем предложенный Егором и пересмотрим синтаксис то что мы можем найти. Возьмем его пример и просто переложим синтаксис на другой язык — например Kotlin. У нас исчезает оператор new и код становится уже чуть менее "странным", такой код мы могли бы увидеть во многих языках (например javascript).


                                                                                        Что мне кажется странным так это то, сколько Егор тратит усилий, пишет статьи, общается с людьми. Да блин, у него в блоге как-то раз сам Алан Кей отписывался в комментах на темы "что он имел ввиду". Однако всякий раз он опять все путает и делает обработку данных на объектах, хотя идея объектов выше. А его проблемы в целом решит любой функциональный язык.

                                                                                      0
                                                                                      Будете смеяться, но есть SumOf.
                                                                                        0

                                                                                        Чего тут смеяться, Егор последователен. Сказал только объекты и вот — только объекты. Смешно было бы если бы SumOf не было ))

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

                                                                                    Самое читаемое