Сериализация абстрактного класса

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

(defun sort-atoms-first-recursive (x &optional y)
  (cond
    ((null x) y)
    ((consp (car x))
     (sort-atoms-first-recursive (cdr x) (cons (car x) y)))
    (t (cons (car x) (sort-atoms-first-recursive (cdr x) y)))))

(defun sort-atoms-first-loop (x)
  (do ((a x (cdr a))
       (b) (c) (d) (e))
      (nil)
    (if (consp (car a))
      (if b (setf (cdr b) a b (cdr b)) (setf b a d a))
      (if c (setf (cdr c) a c (cdr c)) (setf c a e a)))
    (when (null (cdr a))
      (cond
        ((null d) (return e))
        ((null c) (return d))
        (t (setf (cdr b) nil (cdr c) d) (return e))))))


(sort-atoms-first-recursive '(5 -1 (2 6 1) (8 7 -3) (0 (9 4)) -6))

(sort-atoms-first-loop '(5 -1 (2 6 1) (8 7 -3) (0 (9 4)) -6))

Второй - разрушительный (но не создает никаких новых conses).

13
задан Natrium 26 August 2009 в 12:55
поделиться

1 ответ

Он прав и неправ одновременно.

С такими вещами, как BinaryFormatter , это не проблема; сериализованный поток содержит метаданные полного типа, поэтому, если у вас есть:

[Serializable] abstract class SomeBase {}
[Serializable] class SomeConcrete : SomeBase {}
...
SomeBase obj = new SomeConcrete();

и сериализован obj , то он включает в поток «Я SomeConcrete ». Это делает жизнь простой, но многословной, особенно при повторении. Это также непросто, так как требует такой же реализации при десериализации; плохо для различных реализаций клиент / сервер или для долгосрочного хранения.

С XmlSerializer (о котором, я думаю, говорится в блоге), метаданных нет - но имена элементов (или xsi: type атрибуты) используются, чтобы помочь определить, что используется. Чтобы это сработало, сериализатору необходимо знать заранее , какие имена соответствуют каким типам.

Самый простой способ сделать это - украсить базовый класс подклассами, о которых мы знаем. Затем сериализатор может проверить каждый из них (и любые дополнительные атрибуты, специфичные для xml), чтобы выяснить, что, когда он видит элемент , он отображается на экземпляр SomeConcrete (примечание что имена не должны совпадать, поэтому он не может просто искать его по имени).

[XmlInclude(typeof(SomeConcrete))]
public abstract class SomeBase {}
public class SomeConcrete : SomeBase {}
...
SomeBase obj = new SomeConcrete();
XmlSerializer ser = new XmlSerializer(typeof(SomeBase));
ser.Serialize(Console.Out, obj);

Однако, если он пурист (или данные недоступны), тогда есть альтернатива; вы можете указать все эти данные отдельно через перегруженный конструктор XmlSerializer . Например, вы можете найти набор известных подтипов из конфигурации (или, возможно, контейнера IoC), и настраиваем конструктор вручную. Это не очень сложно, но достаточно сложно, чтобы оно того не стоило, если только вам это действительно не нужно .

public abstract class SomeBase { } // no [XmlInclude]
public class SomeConcrete : SomeBase { }
...
SomeBase obj = new SomeConcrete();
Type[] extras = {typeof(SomeConcrete)}; // from config
XmlSerializer ser = new XmlSerializer(typeof(SomeBase), extras);
ser.Serialize(Console.Out, obj);

Кроме того, с XmlSerializer , если вы переходите по пользовательскому маршруту ctor, важно кэшировать и повторно использовать экземпляр XmlSerializer ; в противном случае новая динамическая сборка загружается за одно использование - очень дорого (их нельзя выгрузить). Если вы используете простой конструктор, он кэширует и повторно использует модель, поэтому используется только одна модель.

YAGNI диктует, что мы должны выбрать самый простой вариант; использование [XmlInclude] устраняет необходимость в сложном конструкторе и устраняет необходимость беспокоиться о кешировании сериализатора. Есть и другой вариант, который, тем не менее, полностью поддерживается.


Ответьте на следующие вопросы:

По "заводскому шаблону", он говорит о случае, когда ваш код не знает о SomeConcrete , возможно, из-за IoC / DI или подобных фреймворков; так что у вас может быть:

SomeBase obj = MyFactory.Create(typeof(SomeBase), someArgsMaybe);

Который вычисляет соответствующую конкретную реализацию SomeBase , создает ее экземпляр и возвращает обратно. Очевидно, что если наш код не знает о конкретных типах (потому что они указаны только в файле конфигурации), мы не можем использовать XmlInclude ; но мы можем проанализировать данные конфигурации и использовать подход ctor (как указано выше). На самом деле, в большинстве случаев XmlSerializer используется с объектами POCO / DTO, так что это искусственная проблема.

И интерфейсы; то же самое, но более гибкое (интерфейс не требует иерархии типов). Но XmlSerializer не поддерживает эту модель. Честно говоря, жестко; это не его работа. Его задача - позволить вам хранить и передавать данные. Не реализация. Любые объекты, созданные с помощью xml-схемы, не будут иметь методов. Данные конкретны, а не абстрактны. Пока вы думаете «DTO», обсуждение интерфейса не является проблемой. Люди, которых раздражает невозможность использовать интерфейсы на своих границах, не приняли разделения ответственности, то есть они пытаются сделать:

Client runtime entities <---transport---> Server runtime entities

вместо менее ограничительного

Client runtime entities <---> Client DTO <--- transport--->
           Server DTO <---> Server runtime entities

Теперь во многих (большинстве?) Случаев DTO и объекты могут совпадать; но если вы пытаетесь сделать что-то, что не нравится транспорту, введите DTO; не борись с сериализатором. Та же логика применяется, когда люди изо всех сил пытаются записать свой объект:

class Person {
    public string AddressLine1 {get;set;}
    public string AddressLine2 {get;set;}
}

как xml формы:

<person>
    <address line1="..." line2="..."/>
</person>

Если вы этого хотите, создайте DTO, который соответствует транспорту, и сопоставление между вашей сущностью и DTO:

// (in a different namespace for the DTO stuff)
[XmlType("person"), XmlRoot("person")]
public class Person {
    [XmlElement("address")]
    public Address Address {get;set;}
}
public class Address {
    [XmlAttribute("line1")] public string Line1 {get;set;}
    [XmlAttribute("line2")] public string Line2 {get;set;}
}

Это также относится ко всем другим мелочам, например:

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

У вас не всегда возникают эти проблемы; но если вы это сделаете - введите DTO (или несколько), и ваши проблемы исчезнут. Возвращаясь к вопросу об интерфейсах; типы DTO могут не основываться на интерфейсах, но ваши типы времени выполнения / бизнеса могут быть.

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

У вас не всегда возникают эти проблемы ; но если вы это сделаете - введите DTO (или несколько), и ваши проблемы исчезнут. Возвращаясь к вопросу об интерфейсах; типы DTO могут не основываться на интерфейсах, но ваши типы времени выполнения / бизнеса могут быть.

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

У вас не всегда возникают эти проблемы ; но если вы это сделаете - введите DTO (или несколько), и ваши проблемы исчезнут. Возвращаясь к вопросу об интерфейсах; типы DTO могут не основываться на интерфейсах, но ваши типы времени выполнения / бизнеса могут быть.

t Я использую неизменяемый тип?
  • почему мой тип должен быть общедоступным?
  • как мне управлять сложным управлением версиями?
  • как мне обрабатывать разных клиентов с разными макетами данных?
  • почему я не могу использовать интерфейсы?
  • и т. д.
  • У вас не всегда возникают эти проблемы; но если вы это сделаете - введите DTO (или несколько), и ваши проблемы исчезнут. Возвращаясь к вопросу об интерфейсах; типы DTO могут не основываться на интерфейсах, но ваши типы времени выполнения / бизнеса могут быть.

    t Я использую неизменяемый тип?
  • почему мой тип должен быть общедоступным?
  • как мне управлять сложным управлением версиями?
  • как мне обрабатывать разных клиентов с разными макетами данных?
  • почему я не могу использовать интерфейсы?
  • и т. д.
  • У вас не всегда возникают эти проблемы; но если вы это сделаете - введите DTO (или несколько), и ваши проблемы исчезнут. Возвращаясь к вопросу об интерфейсах; типы DTO могут не основываться на интерфейсах, но ваши типы времени выполнения / бизнеса могут быть.

    41
    ответ дан 1 December 2019 в 18:49
    поделиться
    Другие вопросы по тегам:

    Похожие вопросы: