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
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.