LINUX.ORG.RU

Как правильно создавать сложные объекты?

 ,


0

2

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

Как правильно написать конструктор такого объекта? Вроде как банально new myItem(f1, f2, f3, ..., f32) некрасиво. В голову приходит только что-то вроде

i = new myItem();
i.f1 = ...;
i.f2 = ...;
...
i.f32 = ...;
i.init(); // здесь проверяется корректность данных

но это как-то совсем громоздко. Как-то красивее можно?

👍👍👍

Последнее исправление: monk (всего исправлений: 1)

Поместить все данные в отдельный класс, и передавать его в конструктор myItem. В конструкторе проверить корректность данных.

four_str_sam
()

Если у тебя класс с 30 вручную инициализируемыми полями, то «красиво» и не громоздко не сделать. Просто потому что тебе все равно придётся где-то их инициализировать

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

Midael
()
Последнее исправление: Midael (всего исправлений: 1)

Либо паттерн «Builder»:

MyItemBuilder myItemBuilder = new MyItemBuilder();
myItemBuilder.setF1(...);
myItemBuilder.setF32(...);
MyItem myItem = myItemBuilder.build();

либо паттерн «Parameter Object»:

MyItemParameters myItemParameters = new MyItemParameters();
myItemParameters.setF1(...);
myItemParameters.setF32(...);
MyItem myItem = new MyItem(myItemParameters);

Обычно лучше использовать первый паттерн, он более гибок.

Legioner
()
Последнее исправление: Legioner (всего исправлений: 1)

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

Ведь главное в продакшене это читабельность. «Как правильно» оставь для anonimousa.

unt1tled
()
Последнее исправление: unt1tled (всего исправлений: 1)
Ответ на: комментарий от Legioner

Две анимированных аватары этому регистранту.

Manhunt ☕☕
()
Ответ на: комментарий от Gvidon
i.f1 = ...;
i.f2 = ...;
...
i.f32 = ...;
...
i.f3000

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

unt1tled
()
Ответ на: комментарий от Legioner

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

winlook38
()

Вроде как банально new myItem(f1, f2, f3, ..., f32) некрасиво.

Почему некрасиво? Этим ты гарантируешь инициализацию всех своих f1, f2, ... f32.

bbk123
()

конструктор

Создавать объект напрямую через вызов конструктора - всё равно, что хардкодить константы. Поэтому вместо

i = new MyItem();
нужно писать
i = MagicWand.make('my-item');

Для инициализции лучше предусмотреть стандартный интерфейс вида

i.init('name', value);
i.initArray( key_value_array );

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

no-such-file 👍👍👍👍👍
()
Ответ на: комментарий от Oxdeadbeef

В Objective C есть именованные параметры, они немного изменяют стиль программирования.

Legioner
()

Как-то красивее можно?

В Io:

O := Object clone do(
  a := 1
  b := Object clone lexicalDo(
    c := a
  )
)
при таком подходе конструкторы вообще не нужны особо.

anonymous
()

i.init(); // здесь проверяется корректность данных

Кстати, явный вызов init — говно. и он не для проверки данных, а как раз для инициализации, для проверки ты мог бы запилить отдельный check. Init как раз для того, чтоб не писать кучу стандартного говна, которое ты показал

MyItem := Object clone do(
  init := method(self f1 := ...; self f2 := ...; f32 := ...)
)
i1 := MyItem clone
i2 := MyItem cloneWithowtInit 
Никакого явного вызова Init не требуется, иначе глупо было бы.

anonymous
()

В жс можно использовать механизм, похожий на миксины o = SomeObject.create({foo: bar}) создаем новый анонимный объект, и копируем его поля в целевой. Также можно использовать with для этих целей,

with(childObject = new ParentObject){a = 1; b = 2}
Наиболее естественный способ, но сейчас with не в почете, из-за плохой оптимизации и опасности.

anonymous
()
Ответ на: комментарий от Legioner

Обычно лучше использовать первый паттерн, он более гибок.

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

anonymous
()
Ответ на: комментарий от Legioner
MyItemBuilder myItemBuilder = new MyItemBuilder();
myItemBuilder.setF1(...);
myItemBuilder.setF32(...);
MyItem myItem = myItemBuilder.build();

Для сравнения

SubMyItem = new MyItem
SubMyItem.setF1(...).setF32(...)
myItem = new SubMyItem

anonymous
()

Чем определяется зависимость твоих полей внутри объекта?
Если только чьими-то хотелками, то инициализируй всё подряд и продолжай иметь кучу хлама.
Если же объект снабжен соответствующими методами, тогда и работай через них.

blexey
()
Ответ на: комментарий от winlook38

Анонiмус нас покинул и обещал не возвращаться :(( Из-за тебя в том числе, зараза! А я только Хьюита всего пересмотрел и перечитал, докуда дотянулся ...

anonymous
()
Ответ на: комментарий от anonymous

Анонiмус нас покинул и обещал не возвращаться

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

Из-за тебя в том числе, зараза!

У меня нет прав на модерирование анонiмусов и я ни разу не жаловался на него модераторам.

А я только Хьюита всего пересмотрел и перечитал, докуда дотянулся ...

Хорошо начал, меня затроллел. Продолжай.

winlook38
()
Ответ на: комментарий от winlook38

У меня нет прав на модерирование анонiмусов и я ни разу не жаловался на него модераторам.

Его не забанили за правила, он сам ушел, выложив пароль. Что скажешь? Как ты теперь с этим жить будешь? Нет тебе прощенья!

anonymous
()
Ответ на: комментарий от JN

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

Deleted
()
Ответ на: комментарий от blexey

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

Снабжён. Только пусть для простоты это 20-угольник. В методе .setpoint(int num, double x, double y) есть проверка, что никакие две вершины не совпадают и рёбра не перекрещиваются. Проблема в том, что после m = new TwentyAngle; m.setpoint(0, 50, 100); проверка определённо напишет ошибку.

monk 👍👍👍
() автор топика
Ответ на: комментарий от bbk123

Почему некрасиво?

Когда в чужом (или старом своём) коде встречается что-то вроде

np.set_printoptions(3, 'inf', 75, 'nan', 8, False, 1000, None, 1, False< True, True, False, 10)

то без документации и счёта запятых понять эту строчку практически невозможно.

monk 👍👍👍
() автор топика
Ответ на: комментарий от Legioner

Обычно лучше использовать первый паттерн, он более гибок.

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

monk 👍👍👍
() автор топика
f.set_fs(fs)

def set_fs(self, *fs):
    _verify_fs(fs)
    for i, x in enumerate(fs):
        setattr(self, 'f' + str(i), f)

Каков вопрос, таков и ответ.

t184256 ☕☕☕☕☕
()
Ответ на: комментарий от monk

В Идее (Java, Python, куча других языков) можно навестись на нужный параметр (75, например), и нажать Ctrl + P. Тогда появится попап, где перечислены все имена переменных, а выделенный аргумент будет отмечен жирным. Причем подсветка будет меняться, когда курсор бегает по аргументам. Отдельной документации, в общем-то, не нужно, все берется прямо из кода.

https://www.jetbrains.com/idea/help/viewing-method-parameter-information.html

anonymous
()

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

1) Действительно ли ваш объект нуждается в N-ном количестве представлений получаемых из единой точки конструирования?

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

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

class House {
   protected Door d;
}
У дома может быть деревянная дверь, может быть железная дверь, а может быть дверь из сладкой ваты. Суть в том что при строительстве дома я вызываю строителя, и только он и место вызова знают какую дверь нужно поставить для конкретного дома. При получении объекта, я не заморачиваюсь за это - я просто говорю - у этого дома будет деревянная дверь, зачастую (в частности, в играх) я вообще могу делегировать этот вопрос SomeRandomHouseBuilder-ру, к примеру.

Вот это сложный объект - хотя у него всего 1 параметр - дверь (как бы глупо для дома это не звучало, не суть, это просто пример). Почему? Потому что объект House в конечном итоге может иметь N-ное количество представлений. Причем сложность не определяется типом или количеством содержащих в себе свойств. Один раз я слышал от программиста такой бред (точно цитату не могу привести, но суть примерно такая) - «Сложный объект - это всегда объект, содержащий в себе другие объекты». Ну чушь ведь полнейшая. Сложность объекта определяется количеством его представлений (в обсуждаемом, конкретном контексте, конечно же). У меня может быть объект имеющий 100 свойств, но если в конечном продукте он будет иметь единственное представление, я ни в жизнь не стану применять Builder. Буду инициализировать так, как вы написали в своем первом посте, пусть там будет хоть 100500 этих свойств.

С другой стороны у меня может быть объект Человек с одним только свойством возраста, и это будет простое целочисленное значение, но я могу сделать его сложным - всего лишь сказав себе - «В этом ПО Человек будет иметь несколько представлений, в зависимости от диапазона его возраста». Все. Вот объект и стал сложным. Поэтому я пишу Builder который будет создавать представления класса Человек отталкиваясь только от этого возраста. Например представление Старик, Младенец, Мужчина и т.д. Я не буду создавать подклассы для решения такой простой задачи. А выберу именно строителя, который при запросе Младенец, всего то и будет делать что проверять возраст (не больше года, допустим). Это простой пример, если что.

Поэтому для начала определитесь со сложностью. Если вам реально необходимо N-ное количество представлений для объекта - тогда да - используйте Builder. А вот если разные представления будут иметь разное поведение, тогда вам уже стоит задуматься о наследовании.

Сори за такую длинную тиаду, не удержался=) Я всегда думал что основная проблема всех проблем ООП (толстые контроллеры/не пришей к одному месту рукав одиночки/прототипы, там где нужны абстрактные фабрики, и наоборот/etc) - это просто неверное понимание идеи паттерна. Зачастую так и получается - человек узнает о ООП, потом человек узнает о паттернах проектирования - затем он начинает вставлять эти самые паттерны там где надо и там где не надо. Будьте осторожны с применением паттернов и старайтесь всегда декомпозировать вашу задачу, чтобы было ясно - что и где нужно применять.

znenyegvkby
()

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

peregrine ☕☕
()
Последнее исправление: peregrine (всего исправлений: 1)
Ответ на: комментарий от znenyegvkby

Я не буду создавать подклассы для решения такой простой задачи. А выберу именно строителя, который при запросе Младенец, всего то и будет делать что проверять возраст (не больше года, допустим)

Угу, а потом проходит 5 лет, а твои младенцы кричат из прошлого, «нам все еще по году, нам рано в детский сад». Это ясно конечно, что в твоем недоязычке ничего сложней хелловорда не напишешь, но на все ООП, где принято писать программы, а не пародии, это обобщать не надо. Все объекты должны быть реактивно взаимосвязаны. И это достигается не передрачиванием на каждый пук миллиардов экземпляров, а метаобъектами, от которых вы анально отгораживаетесь.

anonymous
()
Ответ на: комментарий от anonymous

Буквоедство. Я написал

Это простой пример, если что.

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

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

Опять же, все зависит от задачи. Если мне не нужно

реактивно взаимосвязаны

значит и не

должны быть

А можно спросить вас

в твоем недоязычке

Это в каком?

znenyegvkby
()
Ответ на: комментарий от znenyegvkby

в каком?

Подставь сюда любое ынтырпрайзное статик-говно, какое больше нравится.

anonymous
()
Ответ на: комментарий от monk

пусть для простоты это 20-угольник. В методе .setpoint(int num, double x, double y) есть проверка, что никакие две вершины не совпадают и рёбра не перекрещиваются. Проблема в том, что после m = new TwentyAngle; m.setpoint(0, 50, 100); проверка определённо напишет ошибку.

Поскольку при первом вызове .setpoint(int num, double x, double y) у него есть достаточно информации, чтобы определить, что никаких двух вершин для этой проверки пока не существует, логично было бы выполнять её не безусловно.

blexey
()
Ответ на: комментарий от znenyegvkby

Если мне не нужно

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

anonymous
()
Ответ на: комментарий от anonymous

оо! анонiмус. тыж свалил и обещал не возвращаться, опять пришел свой Io двигать? Давай выкатывай на нем серьезный проект, а не уровня хомпаги васи-гопника.

anonymous
()
Ответ на: комментарий от anonymous

Тебе сегодня не нужно, а завтра понадобится.

Вот для этого задачи и проектируют на N-ное время вперед.

В результате такого дезигна

Не учел изменение заранее - неправильно спроектировал, и это не вопрос просто - непросто.

И только не надо сейчас говорить о

  • все предусмотреть невозможно (крупные изменения, которые по вашим словам предполагают для решения все заново разгребать и переписывать должны предусматриваться заранее, иначе грош цена такому проектированию)
  • пройдет 5 лет, технологии изменятся, надо будет переписывать проект (вот для этого у каждого проекта есть предполагаемое время жизни)
  • и т.д. и т.п. Не имеет смысла об этом говорить, потому что это:
    • уже близко к off-topic
    • можно тереть и спорить об этом до второго пришествия
znenyegvkby
()
Ответ на: комментарий от znenyegvkby

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

anonymous
()
Ответ на: комментарий от anonymous

Господи, ну ведь вы сами говорите об этом

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

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

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

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

кастирует гибкость и расширяемость

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

Может быть все-таки закроем эту тему? Тем более что я ни в коей мере не оспариваю у вас праведности сабклассинга, потому как и для него есть очень много достойных применений (но все же они ограничены). Согласен, возможно мой пример с вариантами представлений класса Человек слишком утрирован, но я специально оговорился, что это просто пример. Каюсь, все мы грешны=)

znenyegvkby
()
Ответ на: комментарий от winlook38

анонiмус же. у него все легко, потому что ничего нет.

anonymous
()
Ответ на: комментарий от znenyegvkby

Согласен, возможно мой пример с вариантами представлений класса Человек слишком утрирован

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

winlook38
()
Ответ на: комментарий от winlook38

Да, вполне логично, затупил, каюсь. Спасибо за достойное объяснение, учел на будущее.

znenyegvkby
()
Ответ на: комментарий от znenyegvkby

Вы не сможете привести мне такой пример

не пойму, то ли Вы придуриваетесь, то ли правда без понятия. Да любой, практически случай применения этого патерна — неуклюжее неповоротливое УГ. Берите первый же пример с википедии https://ru.wikipedia.org/wiki/Строитель_(шаблон_проектирования) про пиццу. Представьте себе что мы хотим добавить метод setPrice, например. Все приехали, Вам придется переписывать все билдеры.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.