Pull to refresh
64
0.4
Михаил@michael_v89

Программист

Send message

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

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

и складывания функций в мапы

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

Я предлагал избавиться от классов

Но сделали их полный аналог в виде пары нетипизированный объект + тип typescript (аналог new SomeClass), методами объекта через точку как в ООП, замыканиями с локальными переменными (аналог полей класса), и функциями makeXXX (аналог конструктора). О чем я вам уже писал.
Классы под капотом именно так и работают, слово "class" это просто синтаксический сахар для того, что вы сделали в своем коде.

и обладает множеством недостатков

Раз это полный аналог, значит и ваш код обладает теми же недостатками.

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

Никто не говорил про нереализуемый код, вы спорите со своими фантазиями. Все прекрасно знают, что код с классами на C++ компилируется в набор машинных команд без классов.
Я вам об этом писал тут еще полгода назад.

включая ваш, но так и не привели ни одного

Вы в статье говорили, что не надо использовать объекты с методами, но в примере с кодеками не смогли избавиться от объектов с методами. Значит привели. На этом всё.

Удобно не реализовывать полностью интерфейс

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

что еще удалили?

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

Про процедурный подход это вообще ваша выдумка - в статье ясно говорится

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

я рад что вы в итоге пришли к ФП

У вас неадекватное восприятие реальности. Я не "приходил" ни к настоящему ФП, ни к тому, что вы называете ФП.
В русском языке слова "можно вызвать" в том контексте обозначают просто констатацию факта.

Вы видимо таки не поняли о чем речь, попробую объяснить еще проще:

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

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

Вот и результат makeSoftwareFallbackEncoder это объект, где есть все методы, которые там реализованы.
Вот и методы этого объекта нельзя переиспользовать для данных, у которых нет всего, что есть в виде локальных переменных замыкания (таких как isFallback). Вы же в курсе, что в движке JS замыкание с захваченными локальными переменными это объект с полями? Похоже что нет.

и самое смешное что вы далее сами это делаете

Похоже вы что-то не так понимаете. Возможно путаете объявление и вызов. Я нигде не заменял вызов swEncoder.requestKeyFrame() на вызов процедуры/функции.

Я согласился переписать ваш код на ФП, что без проблем сделал.

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

Я ее заменил на вызов функции

Это ложь, в вашем коде написано swEncoder.encode(frame) и swEncoder.requestKeyFrame?.(). Это вызовы методов объектов, а не вызовы функций.
Поскольку вы скатились до открытого вранья, я не вижу смысла далее вам отвечать.

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

Я использовал более лаконичный и простой подход без лишних символов.
и в целом по символам вышло меньше

Объясняю еще раз то же самое, что было написано в моем предыдущем комментарии. Исходный пример написан на C++, а вы использовали JavaScript. Все различия связаны только с разным синтаксисом языков, а не с тем, что вы не использовали классы. Я привел diff с доказательством.

Вот тот же код на TypeScript с классами и объектами в стиле ООП. Он занимает ровно те же 78 строк. Значит различие в количестве строк не связано с использованием ООП. Более того, в нем на 10 символов меньше, чем в вашем.

TypeScript
interface Encoder {
  Encode(frame: VideoFrame): Bitstream;
  RequestKeyFrame(): void;
};

class Vp9SWEncoder implements Encoder {
  Encode(frame) {
    // кодируем через libvpx
  }
};

class Vp9HWEncoder implements Encoder {
  Encode(frame) {
    // Вызываем функции ОС чтобы драйвер видеокарты что-то закодировал
  }
};

class SoftwareFallbackEncoder implements Encoder {
  private isFallback = false;

  constructor(private swEncoder: Encoder, private hwEncoder: Encoder) {}

  Encode(frame) {
    if (!this.isFallback) {
      const encoded = this.hwEncoder.Encode(frame);
      if (!encoded.isError) return encoded;
      this.isFallback = true;
    }
    return this.swEncoder.Encode(frame);
  }
  RequestKeyFrame() {
    if (this.isFallback) this.swEncoder.RequestKeyFrame();
    else this.hwEncoder.RequestKeyFrame();
  }
};

function makeEncoder(codec: Codec): Encoder {
  switch (codec) {
    case 'vp9f': return new SoftwareFallbackEncoder(
      new Vp9SWEncoder(), new Vp9HWEncoder()
    );
    case 'vp8f': return new SoftwareFallbackEncoder(
      new Vp8SWEncoder(), new Vp8HWEncoder()
    );
    case 'vp9s': return new Vp9SWEncoder();
    case 'vp9h': return new Vp9HWEncoder();
    case 'vp8h': return new Vp8HWEncoder();
    case 'vp8': return new Vp8SWEncoder();
    case 'vp9': return new Vp9SWEncoder();
    case 'h264': return new H264HWEncoder();
  }
};

const main = () => {
  // ...
  const codecs = GetCodecs();
  const encoders = codecs.map(makeEncoder)

  while (true) {
    const event = getEvent();
    if (!event) break;

    switch (event.Type()) {
      case 'gotFrame': {
        for (const encoder of encoders) {
          encoder.encode(event.getFrame());
        }
        break;
      }
      case 'requestedKeyFrame': {
        for (const encoder of encoders) {
          encoder.requestKeyFrame();
        }
        break;
      }
    }
  }
};

написанное калькой ваше же решение более лаконичное

Вы это для самоуспокоения что ли повторяете?) Ваш пример не является более лаконичным, я уже несколько раз это объяснил с доказательствами.

Более того у стрелочных функций что я использую в принципе нет this.

А, то есть если надо будет вызвать encode из requestKeyFrame, надо будет городить третью функцию до return и вызывать ее из обоих? А говорите, лаконично.
Получается, ваш код содержит меньше возможностей, чем был с ООП.

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

Вы согласились написать полный пример для демонстрации вашего процедурного подхода? Согласились.
В вашем полном примере они вынесены? Нет.

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

Ну попробуйте заменить swEncoder.requestKeyFrame() на вызов процедуры. У вас это не получится. В этом был весь смысл примера с кодеками.

У вас даже вынести requestKeyFrame из makeSoftwareFallbackEncoder "без проблем" не получится.

Подробнее

Она использует состояние isFallback, которая у вас локальная переменная, поэтому просто так вынести ее нельзя. Вам придется передавать ее аргументом, как и оба энкодера. Где уж тут лаконичность. И строк кода у вас уже будет не 78.

const msfe_requestKeyFrame(swEncoder, hwEncoder, isFallback) => {
  if (isFallback) swEncoder.requestKeyFrame();
  else hwEncoder.requestKeyFrame();
}

makeSoftwareFallbackEncoder(swEncoder, hwEncoder) {
  let isFallback = false;
  
  requestKeyFrame: () => msfe_requestKeyFrame(swEncoder, hwEncoder, isFallback)
}

Обратите внимание, внутри makeSoftwareFallbackEncoder стрелочная функция осталась, то есть одна функция вызывает другую. Глобальные функции можно вызывать и с ООП. Если вы это не считаете проблемой в своем коде, тогда и в ООП это не является проблемой.

Опять ошибка - где они это требуют? У них даже ссылки нет друг на друга))

Там же, где их по вашим словам требует метод User.getDisplayName() из вашего примера в статье. У него тоже нет ссылок на другие методы, но вы сказали, что он их требует. Вы уж определитесь, либо в обоих случаях требуют, либо в обоих не требуют, так как в обоих случаях это объект с методами.

encoder?.encode()

Это можно сделать и с ООП, но вы в статье все равно это указали как проблему ООП. Значит и вашем коде она есть. Вы уж определитесь, такой вызов это проблема или нет.

78 строк

Все различия связаны с синтаксисом языков JavaScript/CPP и не связаны с вашим подходом. Также вы пропустили одну строку.
То есть лаконичности, про которую вы говорили, с использованием процедур вместо классов нет.

Diff
- public:
- public:
- public:

// SoftwareFallbackEncoder
- private:
- Encoder * sw_encoder_;
- Encoder * hw_encoder_;
- 
- bool is_fallback_ = false;
-
- public:
- SoftwareFallbackEncoder(Encoder sw, Encoder hw): sw_encoder_(sw), hw_encoder_(hw) {}
+ let isFallback = false;

- case vp8s: return new Vp8SWEncoder();
+ У вас эта строка пропущена

- vector<Encoder> encoders;
- for (auto codec : codecs) {
-   encoders.push_back(CreateEncoder(codec))
- }
+ const encoders = codecs.map(makeEncoder)

Также в вашем коде можно отметить большую вложенность элементов. Это тоже не выглядит более лаконично.

// js
return {
  encode: (frame) => {
  }
}

// cpp
Encode(frame: Frame) {
}

Теперь значит про проблемы ООП.
У вас функции encode и requestKeyFrame из глобальных процедур, за которые вы так выступали, стали методами объекта. Поэтому для них стали актуальны проблемы, которые вы указали в статье.

"метод намертво приколочен к типу своего неявного аргумента — this"
Методы encode и requestKeyFrame в вашей реализации тоже приколочены к this. Например если вы захотите вызывать один метод из другого, вы будете использовать this.

"Он зависит не от интерфейса, а от конкретного класса."
У вас эти методы зависят даже не от класса, а вообще от конкретного объекта.

"метод требует для работы не только данные и методы, что ему нужны, но и те что не нужны, но есть в классе User - то есть абсолютно все поля и методы этого класса"
У вас то же самое, методы encode и requestKeyFrame "требуют для работы" все методы, которые есть в объекте где они объявлены.

"нельзя использовать метод без создания экземпляра этого класса или его потомка"
У вас аналогично, нельзя использовать метод без создания объекта где он содержится.

"Невозможность обработки ситуации, когда user null или undefined в самом методе"
Сможете обработать в ваших методах ситуацию, когда encoder null или undefined? Я в этом сомневаюсь.

let encoder: Encoder | null
encoder.requestKeyFrame() // Ошибка: Uncaught TypeError: can't access property "requestKeyFrame", encoder is undefined

"нельзя отнаследовать определенные поля или методы — только целиком весь класс"
Нельзя отнаследовать или переиспользовать другим способом один метод encode. Чтобы его вызывать, нужно получить целиком объект из функции makeVp9SWEncoder, в котором будут оба метода.

"Используем сырые данные, без лишних преобразований."
"getArea(shape)"
Если у вас будут сырые данные, представляющие стейт кодеков (который вы ранее обозначили как XxxState/YyyState), вы не сможете их использовать без лишних преобразований. Вам надо будет для каждого стейта вызвать одну из функций makeVp8SWEncoder / makeVp9SWEncoder /makeSoftwareFallbackEncoder в виде "makeVp9SWEncoder(rawCodecState)", то есть сделать фабрику для создания объектов кодеков из сырых данных.

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

Ладно, вот полный код, 89 строк.

Код
class Encoder {
  public:
  virtual Bitstream Encode(VideoFrame frame) = 0;
  virtual void RequestKeyFrame() = 0;
};

class Vp9SWEncoder : public Encoder {
  public:
  Bitstream Encode(VideoFrame frame) override {
    // кодируем через libvpx
  }
};

class Vp9HWEncoder : public Encoder {
  public:
  Bitstream Encode(VideoFrame frame) override {
    // Вызываем функции ОС чтобы драйвер видеокарты что-то закодировал
  }
};

class SoftwareFallbackEncoder : public Encoder {
  public:
  Bitstream Encode(VideoFrame frame) override {
    if (!is_fallback) {
      Bitstream encoded = hw_encoder_->Encode(frame);
      if (!encoded.IsError()) return encoded;
      is_fallback_ = true;
    }
    return sw_encoder_->Encode();
  }
  
  void RequestKeyFrame() {
    if (is_fallback_) sw_encoder_->RequestKeyFrame();
    else hw_encoder_->RequestKeyFrame();
  }
  
  private:
  bool is_fallback_ = false;

  Encoder * sw_encoder_;
  Encoder * hw_encoder_;
};

Encoder* CreateEncoder(CodecType codec) {
  switch (codec) {
    case vp9f: return new SoftwareFallbackEncoder(
      new Vp9SWEncoder(), new VP9HWEncoder()
    );
    case vp8f: return new SoftwareFallbackEncoder(
      new Vp8SWEncoder(), new VP8HWEncoder()
    );
    case vp9s: return new Vp9SWEncoder();
    case vp8s: return new Vp8SWEncoder();
    case vp9h: return new Vp9HWEncoder();
    case vp8h: return new Vp8HWEncoder();
    
    case vp8: return new VP8SWEncoder();
    case vp9: return new VP9SWEncoder();
    case h264: return new H264HWEncoder();
  }
}

int main() {
 ...
  codecs = GetCodecs(); 
  // может быть любой набор вроде {vp8, vp8, h264} или {vp9}, 
  // или даже {h264, h264, h264, vp8, vp8, vp8, vp8}
  for (auto codec : codecs) {
    encoders.push_back(CreateEncoder(codec))
  }

  while (auto event = GetEvent()) {
    switch (event.Type()) {
        case kGotFrame: {
          for (auto encoder : encoders) {
            encoder->Encode(event.GetFrame());
          }
        }
        break;
        
        case kRequestedKeyFrame: {
          for (auto encoder : encoders) {
            encoder->RequestKeyFrame();
          }
        }
        break;
    }
  }
}

И где ваш полный пример этого кода?

Ваш собеседник вам уже его привел, зачем я должен его еще раз приводить?
https://habr.com/ru/articles/885980/comments/#comment_27974912
https://habr.com/ru/articles/885980/comments/#comment_27978858

Какой кусок кода нужно еще привести, чтобы вы смогли увидеть проблемы

Объясняю еще раз. Приводить надо не "кусок кода", а полный пример кода. Полный. Полностью. Весь код. Который реализует всю функциональность, которая есть в исходном примере. Не в виде "encodeXxx", а с теми же названиями, которые были в исходном примере.

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

Я это уже писал 28 февраля в этом комменте, на который вы сегодня ответили. Сколько раз вам надо повторять одно и то же, чтобы вы поняли?

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

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

Ключевые слова "я бы".

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

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

И вы еще этим гордитесь?)) Это хорошо показывает, что нет смысла ожидать от вас нормальной дискуссии.

В вашем последнем куске кода тоже нет.

В каком куске кода? Похоже вы перепутали собеседников.
У другого вашего собеседника, насколько я вижу, примеры кода не независимые, а являются часть одной логики и дополняют друг друга. Он написал "К моему коду выше добавляем".

makeSoftwareFallbackEncoder
20 строк кода

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

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

Я все проблемы ООП, перечисленные в статье, не реализовывал в своих примерах - и их там нет.

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

но в одном из вариантов сильно меньше проблем чем во втором - значит он лучше

Правильно. А если больше, то хуже. В вашем коде проблем больше.

тут не куда упрощать, так как уже идет искажение смысла

Я не предлагал упрощать само объяснение, я предлагал объяснять то же самое простыми словами. Это не одно и то же.
Например, можно заменить слово "нелинейный" на его определение.

нелинейный
"отклоняющийся от прямо пропорциональной зависимости"

Тогда получится:
"Нейросети находят способ, отклоняющийся от прямо пропорциональной зависимости, вывести из любых входных данных любые выходные"

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

Тремя черточками вы линию синуса не опишете.
Смотрите, я обучил нейросеть, где всего 3 нейрона

И что это должно доказывать? У меня вроде прямым текстом написано "тремя черточками", а не "тремя нейронами".
Слова "если описывать очень упрощенно" вы похоже тоже проигнорировали.

3 сигмоидных нейрона
И никаких чёрточек

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

и этот результат как-то не похож на то, что вы описали

Я писал "приближают набором ломаных черточек". Ваш рисунок именно так и выглядит. Что на что не похоже?

которая выглядела бы угловато, но равномерно

Это зависит от того, как вы обучаете.

Даже если взять 10000 нейронов, всё выглядит не так, как ожидается:

Этот график мало отличается от предыдущего. Скорее всего у вас что-то не то с обучающей выборкой или с алгоритмом обучения.

Как ваше описание будет работать, если исходной функции просто нет?

Исходную функцию можно задавать в табличном виде - вход и выход. Это называется "обучающая выборка". По этим точкам можно построить аппроксимирующую их функцию, то есть функцию, которая проходит через них или вблизи них с заданной ошибкой. Это базовые концепции, их изучают в университете на курсе нейронных сетей.

При обучении нейросети нет никакой исходной функции

Теорема Цыбенко
Универсальная теорема аппроксимации — теорема, доказанная Джорджем Цыбенко в 1989 году, которая утверждает, что искусственная нейронная сеть прямой связи с одним скрытым слоем может аппроксимировать любую непрерывную функцию многих переменных с любой точностью.

Я не понимаю, зачем вы спорите, если незнакомы с предметом.

В смысле вы на полном серьезе думаете, что это буквально "само собой" и усиленно с этим боретесь?

"Так и получается не правильное представление об обучении, уже идет искажение смысла, как в вашем упрощенном описании."

Очень часто форма не важнее содержания.

Я не умею читать мысли, я читаю то, что написано.

Аппроксимация это то, как работают нейросети
На вход можно подавать всё, что угодно

Я знаю, как работают нейросети.

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

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

они находят нелинейный

Опять синоним слова "магический". Попробуйте не использовать умные термины и объяснять простыми словами. Если ваше объяснение всё еще будет выглядеть корректным, тогда в нем не будет магии.

Но способ нахождения не определен и не прослеживается

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

начинают проявляться новые эмерджентные свойства модели. Сами собой.
но при этом фраза "сами собой" тут уместна и хорошо передает смысл

Само собой ничего не появляется. Поэтому эта фраза неуместна и ложна.
Я вам уже объяснил, что веса, записанные в файл, никаких свойств не проявляют.

Вы игнорируете аппроксимацию и фокусируетесь только на эмерджентности.

Я фокусируюсь на том, что вы сказали. Для указания на это я привел цитаты.

Эмерджентность LLM проявляется благодаря математической аппроксимации

Выглядит так, что вы не знаете, что означает слово "аппроксимация". Она не дает ничего нового, наоборот, она теряет некоторые детали.

Аппроксимация
Аппроксима́ция (от лат. proxima — ближайшая) или приближе́ние — упрощение, замена сложного более простым, но схожим в результате.
Остаточный член — разность между заданной функцией и функцией её аппроксимирующей.

У 100 чисел нет свойства вести чат с пользователем, а у 4000000000 такое свойство появляется
Это всё еще просто числа

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

Что именно вы хотите отслеживать и описывать?

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

но просто увеличивая количество чисел в какой-то момент начинают проявляться новые свойства

Вот именно про это я и говорю. Это некорректное утверждение. Само по себе ничего не появляется. Всегда можно найти причину почему они появились.

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

Вот именно что выводится. Это сложно сделать силами одного человека, но от этого оно никуда не исчезает.

но каким путём нельзя описать, в этом и смысл нейросетей,

Можно, в этом смысл моего коммента.

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

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

но тоже самое не будет работать в космосе

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

споря с аналогиями вы очень быстро уходите от изначального смысла идеи

Ну так не спорьте, я ее привел для пояснения, а не в качестве доказательства.

- апелляция к авторитету является логической ошибкой.
- Нет, не является.

Логическая ошибка
"апелляция к авторитету"

Мне не известно что такое "категория номер 1".

И? Вы попросили отнести к категории, я отнес к категории.

Так какое из них?

Любое из этих определений является определением. Вы же попросили привести определение, я привел.

Но для сложных понятий, процессов и явлений такой метод не подходит: нужно выделить существенный признак и обобщить по нему.

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

Чего вы в своём определении не сделали

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

ибо индивидуальный язык невозможен

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

С точки зрения удобства в коде нам конечно было бы лучше всего сделать так:
// Инвариант: нельзя получить скидку по неактивной карте
return null;

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

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

Такая возможность существует даже с логикой в сущностях.
Пример есть прямо у вас в статье, только наоборот. Вы в LoyaltyProgram::changeCurrency() пропустили установку newCurrency.

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

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

$this->levels->resetRequiredAmounts(); //внутри foreach по уровням

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

Теперь изменение валюты невозможно без обнуления настроек уровней

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

class LoyaltyProgram
{
  public function changeCurrency(LoyaltyCurrency $newCurrency): void
  {
    ...
  }
  
  public function changeCurrencyNew(LoyaltyCurrency $newCurrency): void
  {
      if ($newCurrency === $this->currency) {
          return;         
      }
      
      $this->currency = $newCurrency;
      
      // не сбрасываем уровни
  }
}

а сохранение всего агрегата происходит атомарно

Ну то есть в проекте появилось больше магии. К тому же транзакция теперь запускается всегда, а не только когда нужно, что влияет на производительность.

и мы при написании кода об этом можем больше не задумываться

Зато должны задумываться о куче других вещей. Например искать где находится foreach по уровням.
А также: писать доменный сервис или не писать, как не загружать историю всех событий, как изменить свойства вложенного объекта LoyaltyCard, и т.д.

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

Это ложь. В середине вызова changeCurrency, после установки newCurrency (которой у вас нет) и до вызова resetRequiredAmounts объект класса LoyaltyProgram содержит неконсистентные данные.
А если оценивать только после завершения метода с логикой, то и с сервисом надо так же делать.

Когда вся логика сосредоточена в агрегатах, сделать это сложнее.

Да нисколько не сложнее. Точно так же берем и пишем новый метод с любой логикой.

При сохранении агрегата в репозитории мы обеспечим атомарное сохранение и того и другого.
$changesObserver = DoctrineEntityChangesObserver::instance();
foreach ($changesObserver->getNewMaterials() as $material)

А чего это у вас репозиторий сохраняет данные, не относящиеся к агрегату? Вы же только что доказывали, что они должны быть частью агрегата. Кроме того, теперь бизнес-логика действия находится не только в сущности, а еще и в неком MaterialAddedSubscriber.
Глобальные переменные, синглтоны. И на это вы предлагаете заменять простой тестируемый сервис с зависимостями в конструкторе?

DomainEventPublisher::instance()->publish(new LoyaltyCardRequested())
$event->collection->initCard($card);

Угу, добавим еще больше магии. Убрали event subscriber, и всё перестало работать. Где тут простота поддержки, для которой мы хотели использовать DDD?

и вы возлагаете на слой Application ответственность вытащить карту из репозитория и обернуть в транзакцию сохранение ПЛ и карты

Ну то есть фактически пишете сервис.

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

Ну то есть ответа у вас нет.

Философы и учёные в курсе, что апелляция к авторитету является логической ошибкой.

можете попробовать

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

начав с того, что отнести это понятие к какой-то категории

Ок. Отношу это понятие к категории номер 1.
Как люди используют понятия, не относя их к какой-то категории, и как понятия вообще появились, обсуждать не будем. Раз у вас аргументы уровня "Ну вот тот чувак так сказал, не знаю почему".

можете попробовать дать определение понятию "пространство"

https://slovarozhegova.ru/word.php?wordid=24901
Пространство
1. Одна из форм существования бесконечно развивающейся материи, характеризующаяся протяженностью и объемом.
2. Протяженность, место, не ограниченное видимыми пределами.
3. Промежуток между чем-н., место, где что-н. вмещается.

потому что только так можно дать корректное определение

Определение:
В рамках этой ветки комментариев словом "абырвалг" будем называть последовательность букв ABCD.

Пример использования:
Как называется последовательность ABCD? В соответствии с приведенным выше определением она называется "абырвалг".

Это корректное определение, при этом ни к какой категории я его не относил. Значит ваше утверждение неверно.

Понятия и их определения существуют независимо от того, что говорил Аристотель. Раз люди их используют, значит что-то ими обозначают.

Представьте, как будет выглядеть этот if для 10 обязательных полей

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

часто в такие сервисы с логикой тащат инфраструктурные зависимости, такие как доктриновский EntityManager
При таком подходе код становится существенно грязнее

При таком подходе единственная разница будет в том, что вместо $this->transactionalSession будет $this->entityManager.

Код какого из вариантов чище и лаконичнее, судите сами

Угу, в одном из вариантов молча заменили проверки на один вызов $client->profileComplete(), код которого не привели, и предлагаете сравнивать.
С конструкторами тоже, логика в сервисах не запрещает делать конструктор с параметрами.

Последовательная логика из бизнес-требований теперь разбросана по разным местам.
Что за "accountActivated", какой части бизнес-требований он соответствует?
Открываем метод "BonusAccount::activate()", где там сохранение в истории факта активации бонусной программы?
А-а, это оказывается одно и то же. Потратили много времени просто чтобы найти где что. А в сервисе это написано прямым текстом. Особенно если вынести эти шаги в private-методы с понятными названиями.

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

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

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

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

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

Ни то ни другое. Во-первых, новый метод активации скорее всего надо будет добавить в BonusAccountService. Точно так же, как вы будете их добавлять в BonusAccount, из-за чего она постепенно превратится в God-object.
Во-вторых, у вас есть новые бизнес-требования, они могут содержать совершенно другую логику активации. С одним сервисом общие части можно вынести в private-методы, с разными в отдельный общий сервис, и вызывать их в нужном порядке. Вызывать один сервис верхнего уровня из другого неправильно.

Заключение и резюме

А, это все минусы? Ну ок.

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

Ну как это нет. Он называется "Бизнес-логика в сервисах". Только это не паттерн, а архитектурный подход.

Необязательно делать отдельный сервис на каждое действие, тогда можно переиспользовать зависимости и private-методы в реализации разных связанных бизнес-действий.

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

Вы действительно можете человеку показать пальцем

При чем тут показать пальцем? Я сказал "Определение того, что такое молния". Показывание пальцем никоим образом не является определением.

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

Не понял эту логику. То есть для термина "молния" можно считать нормальным, если его без примера и объективно объяснить нельзя, а для другого термина нет?

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

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

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

И в словаре их не будет.

Ок. И?
Я нигде не подразумевал критерий "должно существовать в словаре".

Если слово в каком-то контексте имеет смысл, то оно существует.

Я нигде не говорил, что какого-то слова не существует.

которые буквально так же и говорят, что если слова нет в словаре, то его не существует

Я такого не говорил ни буквально, ни не буквально.

Существует ли в реальном мире "два"?

А это тут при чем? Это оффтоп, мне неинтересно это обсуждать.

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

Я сказал, что то определение, которое ссылается на изначальное, существует только в книге и начинается с "важный элемент цивилизации ардритов с планеты Энтеропия". В реальности нет никаких ардритов и планеты Энтеропия. Когда вы мне сказали это слово, вы не подразумевали цивилизацию ардритов. А что вы подразумевали? Вот это и будет определением этого слова в реальности, или по крайней мере его частью. И оно не рекурсивное. Пример выше на скриншоте.

Information

Rating
2,268-th
Location
Россия
Registered
Activity