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

24

У меня есть большой объект:

class BigObject{
    public int Id {get;set;}
    public string FieldA {get;set;}
    // ...
    public string FieldZ {get;set;}
}

и специализированный, DTO-подобный объект:

class SmallObject{
    public int Id {get;set;}
    public EnumType Type {get;set;}
    public string FieldC {get;set;}
    public string FieldN {get;set;}
}

Лично я нахожу концепцию явного приведения BigObject в SmallObject - зная, что это односторонняя операция с потерей данных - очень интуитивно понятный и читаемый:

var small = (SmallObject) bigOne;
passSmallObjectToSomeone(small);

Это реализовано с использованием явного оператора:

public static explicit operator SmallObject(BigObject big){
    return new SmallObject{
        Id = big.Id,
        FieldC = big.FieldC,
        FieldN = big.FieldN,
        EnumType = MyEnum.BigObjectSpecific
    };
}

Теперь я мог бы создать SmallObjectFactoryкласс с FromBigObject(BigObject big)методом, который делал бы то же самое, добавлял его в инъекцию зависимостей и вызывал его при необходимости ... но мне он кажется еще более сложным и ненужным.

PS Я не уверен, если это актуально, но будет то, OtherBigObjectчто тоже можно будет конвертировать в SmallObjectразличные настройки EnumType.

Gerino
источник
4
Почему не конструктор?
edc65
2
Или статический фабричный метод?
Брайан Гордон
Зачем вам нужен фабричный класс или внедрение зависимостей? Вы сделали ложную дихотомию там.
user253751
1
@immibis - потому что я почему-то не задумывался над тем, что предложил @Telastyn: .ToSmallObject()метод (или GetSmallObject()). Мгновенное отклонение разума - я знал, что что-то не так с моим мышлением, поэтому я спросил вас, ребята :)
Gerino
3
Это звучит как идеальный вариант использования интерфейса ISmallObject, который реализуется только BigObject как средство обеспечения доступа к ограниченному набору его обширных данных / поведения. Особенно в сочетании с идеей @ Теластина о ToSmallObjectметоде.
Марьян Венема

Ответы:

0

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

Эсбен Сков Педерсен
источник
Это потрясающая идея. У меня уже есть Automapper на месте, так что это будет бриз. Единственная проблема, с которой я столкнулся: не должно ли быть следа, что BigObject и SmallObject так или иначе связаны?
Джерино
1
Нет, я не вижу никаких преимуществ, связывая BigObject и SmallObject дальше друг с другом, кроме службы картографирования.
Эсбен Сков Педерсен
7
В самом деле? Автомаппер - это ваше решение для задач дизайна?
Теластин
1
BigObject сопоставим с SmallObject, они на самом деле не связаны друг с другом в классическом смысле ООП, и код отражает это (оба объекта существуют в домене, возможность сопоставления устанавливается в конфигурации сопоставления наряду со многими другими). Он удаляет сомнительный код (мой неудачный переопределение операторов), оставляет модели чистыми (в них нет методов), так что да, похоже, это решение.
Джерино
2
@EsbenSkovPedersen Это решение похоже на использование бульдозера для рытья ямы для установки вашего почтового ящика. К счастью, ОП все равно хотел выкопать двор, так что в этом случае работает бульдозер. Однако я бы не рекомендовал это решение в целом .
Нейл
81

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

Я бы порекомендовал .ToSmallObject()метод вместо. Это более понятно о том, что на самом деле происходит, и о том, как многословно.

Telastyn
источник
18
Дох ... ToSmallObject () кажется наиболее очевидным выбором. Иногда самым очевидным является самый неуловимый;)
Герино
6
mildly distastefulэто занижение. К сожалению, язык позволяет подобным вещам выглядеть как типичные. Никто не мог бы предположить, что это была реальная трансформация объекта, если бы они не написали это сами. В команде из одного человека, хорошо. Если вы сотрудничаете с кем-либо, в лучшем случае это пустая трата времени, потому что вам нужно остановиться и выяснить, действительно ли это актерский состав или одно из тех безумных преобразований.
Кент А.
3
@Telastyn Согласился, что это не самый вопиющий запах кода. Но скрытое создание нового объекта из операции, которую большинство программистов понимают как инструкцию для компилятора относиться к тому же объекту как к другому типу, недобросовестно для всех, кому приходится работать с вашим кодом после вас. :)
Кент А.
4
+1 за .ToSmallObject(). Вряд ли когда-нибудь вы должны переопределить операторов.
Итоледано
6
@dorus - по крайней мере, в .NET, Getподразумевает возврат существующей вещи. Если вы не изменили операции над маленьким объектом, два Getвызова вернут неравные объекты, что приведет к путанице / ошибкам / wtfs.
Теластин
11

Хотя я понимаю, почему вам нужно иметь SmallObject, я бы по-другому подошел к проблеме. Мой подход к этому типу проблем заключается в использовании Фасада . Его единственная цель - заключать в капсулу BigObjectи делать доступными только определенные элементы. Таким образом, это новый интерфейс в том же экземпляре, а не копия. Конечно, вы также можете захотеть выполнить копию, но я бы порекомендовал вам сделать это с помощью метода, созданного для этой цели в сочетании с фасадом (например return new SmallObject(instance.Clone())).

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

Обратите внимание, это означает, что BigObjectэто не зависит, SmallObjectа скорее наоборот (как и должно быть по моему скромному мнению).

Нил
источник
Единственное преимущество, которое вы упомянули, это то, что фасад имеет преимущество перед копированием полей в новый класс, это избегание копирования (что, вероятно, не является проблемой, если у объектов нет абсурдного количества полей). С другой стороны, он имеет тот недостаток, что вы должны изменять исходный класс каждый раз, когда вам нужно преобразовать в новый класс, в отличие от метода статического преобразования.
Довал
@ Довал, наверное, в этом суть. Вы бы не конвертировали его в новый класс. Вы бы создали другой фасад, если это то, что вам нужно. Изменения, сделанные в BigObject, должны применяться только к классу Facade, а не везде, где он используется.
Нейл
Одно интересное различие между этим подходом и ответом Теластина заключается в том, лежит ли ответственность за генерацию SmallObjectна SmallObjectили BigObject. По умолчанию этот подход заставляет SmallObjectизбегать зависимостей от частных / защищенных членов BigObject. Мы можем пойти еще дальше и избежать зависимости от частных / защищенных членов SmallObject, используя ToSmallObjectметод расширения.
Брайан
@ Брайан Вы рискуете BigObjectтаким образом загромождать . Если вы хотите сделать что-то подобное, вы бы гарантировали создание ToAnotherObjectметода расширения внутри BigObject? Это не должно вызывать беспокойства, BigObjectпоскольку, по-видимому, он уже достаточно большой, как есть. Это также позволяет вам отделиться BigObjectот создания его зависимостей, то есть вы можете использовать фабрики и тому подобное. Другой подход сильно пары BigObjectи SmallObject. Это может быть хорошо в данном конкретном случае, но это не лучшая практика, по моему скромному мнению.
Нейл
1
@Neil На самом деле, Брайан объяснил это неправильно, но он является право - методы расширения действительно избавиться от муфты. Это больше не BigObjectсвязано с этим SmallObject, это просто статический метод где-то, который принимает аргумент BigObjectи возвращает SmallObject. Методы расширения на самом деле просто синтаксический сахар для более приятного вызова статических методов. Метод расширения не является частью из BigObject, это совершенно отдельный статический метод. На самом деле это довольно хорошее использование методов расширения и очень удобно, в частности, для DTO-преобразований.
Луаан
6

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

В качестве требования я хотел бы предложить, чтобы, учитывая, что x=(SomeType)foo;за этим последует некоторое время спустя y=(SomeType)foo;, когда оба приведения применяются к одному и тому же объекту, они x.Equals(y)всегда и всегда будут истинными, даже если рассматриваемый объект был изменен между двумя приведениями. Такая ситуация может применяться, если, например, у одного есть пара объектов разных типов, каждый из которых содержит неизменную ссылку на другой, и приведение любого объекта к другому типу вернет его парный экземпляр. Это также может применяться к типам, которые служат оболочками для изменяемых объектов, при условии, что идентификаторы обертываемых объектов являются неизменяемыми, и два упаковщика одного и того же типа будут сообщать о себе как о равных, если они будут упаковывать одну и ту же коллекцию.

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

Supercat
источник
1

Это может быть хорошо.

Проблема с вашим примером заключается в том, что вы используете такие имена из примера. Рассмотреть возможность:

SomeMethod(long longNum)
{
  int num = (int)longNum;
  /* ... */

Теперь, когда у вас есть хорошая идея, что означает long и int , то и неявное приведение intк, longи явное приведение longк to intвполне понятны. Также понятно, как это 3делается 3и это просто еще один способ работы 3. Понятно, как это не получится int.MaxValue + 1в проверенном контексте. Даже то, как он будет работать int.MaxValue + 1в неконтролируемом контексте, приведет к тому, что int.MinValueэто не самая сложная вещь.

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

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

Джон Ханна
источник
На самом деле они не намного больше, чем то, что предусмотрено в вопросе - но, например, BigObjectможет описать Employee {Name, Vacation Days, Bank details, Access to different building floors etc.}, и SmallObjectможет быть MoneyTransferRecepient {Name, Bank details}. Существует простой перевод из Employeeв MoneyTransferRecepient, и нет никаких оснований для отправки в банковское приложение больше данных, чем необходимо.
Джерино