Чи розумне використання оператора явного кастингу розумним чи поганим злом?


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.


4
Чому б не конструктор?
edc65

2
Або статичний заводський метод?
Брайан Гордон

Навіщо тобі потрібен заводський клас чи ін'єкція залежності? Ви там зробили помилкову дихотомію.
користувач253751

1
@immibis - тому що я якось не замислювався над тим, що запропонував @Telastyn: .ToSmallObject()метод (або GetSmallObject()). Миттєвий проміжок причини - я знав, що щось не так з моїм мисленням, тому я попросив вас, хлопці :)
Gerino

3
Це звучить як ідеальний випадок використання інтерфейсу ISmallObject, який реалізується лише BigObject як засіб забезпечення доступу до обмеженого набору його широких даних / поведінки. Особливо в поєднанні з ідеєю @ Telastyn про ToSmallObjectметод.
Мар'ян Венема

Відповіді:


0

На мою скромну думку жодна з інших відповідей не має права. У цьому питанні stackoverflow відповідь, що найбільше голосує, стверджує, що код картографування повинен залишатися поза доменом. Щоб відповісти на ваше запитання, ні - ваше використання оператора литих не є великим. Я б радив зробити службу картографування, яка знаходиться між вашим DTO та вашим доменним об'єктом, або ви можете використовувати для цього автоматичний код.


Це приголомшлива ідея. У мене вже є Automapper, тому буде вітер. Єдине питання, яке я маю з цим: чи не повинно бути жодного сліду, що BigObject і SmallObject якимось чином пов'язані?
Герино

1
Ні, я не бачу жодної переваги, з'єднуючи BigObject і SmallObject далі разом, крім служби картографування.
Есбен Сков Педерсен

7
Дійсно? Автоматпер - це ваше рішення для дизайнерських проблем?
Теластин

1
BigObject можна порівняти з SmallObject, вони насправді не пов'язані один з одним у класичному сенсі OOP, і код відображає це (обидва об'єкти існують у домені, здатність відображення встановлюється у конфігурації відображення разом з багатьма іншими). Він видаляє сумнівний код (мій нещасний оператор переосмислює), він залишає моделі чистими (методів у них немає), так що так, здається, це рішення.
Герино

2
@EsbenSkovPedersen Це рішення схоже на використання бульдозера, щоб викопати яму для встановлення вашої поштової скриньки. На щастя, ОП хотіли все-таки розкопати подвір’я, тому бульдозер працює в цій справі. Однак я б не рекомендував це рішення взагалі .
Ніл

81

Це ... Не здорово. Я працював з кодом, який зробив цей розумний трюк, і це призвело до плутанини. В кінці кінців, можна було б очікувати , щоб мати можливість просто привласнити BigObjectв SmallObjectзмінну , якщо об'єкти пов'язані досить , щоб кинути їх. Однак це не працює - ви отримуєте помилки компілятора, якщо ви намагаєтесь, оскільки щодо системи типів вони не пов'язані. Також оператору кастингу м'яко неприємно робити нові предмети.

Я б рекомендував .ToSmallObject()замість цього метод. Ясніше про те, що насправді відбувається і про як багатослівне.


18
Doh ... ToSmallObject () здається найбільш очевидним вибором. Іноді найбільш очевидним є самий невловимий;)
Геріно

6
mildly distastefulце заниження. Прикро, що мова дозволяє подібній речі виглядати як машинопис. Ніхто не здогадається, що це фактична трансформація об'єкта, якщо вони самі їх не написали. В одноосібному колективі штрафу. Якщо ви співпрацюєте з ким-небудь, в кращому випадку це марна трата часу, тому що вам доведеться зупинитися і з'ясувати, чи справді це акторський склад, чи це одна з цих шалених перетворень.
Кент А.

3
@Telastyn Погодився, що це не самий кричущий запах коду. Але приховане створення нового об'єкта під час роботи, яке більшість програмістів розуміє як інструкція компілятору, щоб той самий об'єкт трактував як інший тип, недобросовісний для тих, хто повинен працювати з вашим кодом після вас. :)
Кент А.

4
+1 для .ToSmallObject(). Навряд чи вам доведеться перекривати операторів.
ytoledano

6
@dorus - принаймні в .NET, Getпередбачає повернення наявної речі. Якщо ви не скасуєте операції над малим об’єктом, два Getвиклики повернуть нерівні об'єкти, викликаючи плутанину / помилки / wtfs.
Теластин

11

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

Фасад має ряд інших переваг, а саме: забезпечення того, що певні розділи вашої програми можуть використовувати лише членів, доступних через ваш фасад, фактично гарантуючи, що він не може використовувати те, про що не повинен знати. На додаток до цього, він також має величезну перевагу в тому, що ви маєте більше гнучкості в зміні BigObjectмайбутнього технічного обслуговування, не надто турбуючись про те, як воно використовується у вашій програмі. Поки ви можете наслідувати стару поведінку в тій чи іншій формі, ви можете зробити SmallObjectроботу такою ж, як і раніше, без необхідності змінювати свою програму скрізь, коли BigObjectб це було використано.

Зауважте, це BigObjectне залежить від іншого, SmallObjectа навпаки (як на мою скромну думку).


Єдина перевага, яку ви згадали, що фасад має над копіюванням полів до нового класу, - це уникати копіювання (що, мабуть, не є проблемою, якщо об'єкти не мають абсурдної кількості полів). З іншого боку, недоліком є ​​те, що ви повинні змінювати початковий клас кожного разу, коли вам потрібно перетворити його в новий клас, на відміну від статичного методу перетворення.
Doval

@Doval Я гадаю, в цьому справа. Ви б не перетворили його в новий клас. Ви б створили інший фасад, якщо для цього потрібно. Зміни, внесені до BigObject, повинні застосовуватися лише до класу Facade, а не всюди, де він використовується.
Ніл

Одне цікаве відмінність між цим підходом і відповіддю Telastyn в відповідальність за генеруючи SmallObjectбрехня з SmallObjectабо BigObject. За замовчуванням такий підхід змушує SmallObjectуникати залежностей від приватних та захищених членів BigObject. Ми можемо піти на крок далі і уникнути залежностей від приватних / захищених членів SmallObject, використовуючи ToSmallObjectметод розширення.
Брайан

@Brian Ти ризикуєш зациклюватися 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)повинна завжди і назавжди бути правдивою, навіть якщо відповідний об'єкт був змінений між двома кастами. Така ситуація може бути застосована, якби, наприклад, один мав пару об'єктів різного типу, кожен з яких мав непорушну посилання на інший, і вкидання будь-якого об'єкта до іншого типу повертало б його парний екземпляр. Він також може застосовуватися для типів, які служать обгортками для об'єктів, що змінюються, за умови, що ідентичність обернених предметів є незмінною, і два обгортки одного типу будуть повідомляти про себе як рівні, якби вони упакували одну колекцію.

Ваш конкретний приклад використовує змінні класи, але не зберігає жодної форми ідентичності; як такий, я б припустив, що це не належне використання оператора кастингу.


1

Це може бути гаразд.

Проблема у вашому прикладі полягає в тому, що ви використовуєте такі назви приклад-ish. Поміркуйте:

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

Тепер, коли ви гарно розумієте, що означає long та int , тоді як неявна передача intдо, так longі явна передача з longto 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, і немає підстав надсилати в банківську заявку більше даних, ніж потрібно.
Герино
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.