Сериализация абстрактного класса - PullRequest
13 голосов
/ 25 августа 2009

Я пытаюсь сериализоваться и столкнулся с проблемой с абстрактным классом.

Я гуглил ответ и нашел этот блог . Я пробовал это и эту работу.

Хорошо, очень мило. Но посмотрите комментарий к товару:

Эта методология, кажется, скрывается настоящая проблема, и это неточная реализация ОО дизайна выкройки, а именно заводские выкройки.

Необходимость изменить базовый класс на ссылка на любой новый заводской класс пагубный.

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

Я предлагаю продолжить исследование заводские образцы, которые, кажется, что вы пытаетесь реализовать здесь.

О чем говорит комментатор? Он немного смутный. Может кто-нибудь объяснить это поподробнее (с примером)? Или он просто говорит глупости?

Обновление (после прочтения первого ответа)

Почему комментатор говорит о

заводская модель

и

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

Хочет ли он сделать такой интерфейс?

public interface IWorkaround
{
    void Method();
}

public class SomeBase : IWorkaround
{
    public void Method()
    {
        // some logic here
    }
}

public class SomeConcrete : SomeBase, IWorkaround
{
    public new void Method()
    {
        base.Method();
    }
}

Ответы [ 2 ]

40 голосов
/ 26 августа 2009

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

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

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

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

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

Самый простой способ сделать это - украсить базовый класс подклассами, о которых мы знаем. Сериализатор может затем проверить каждый из них (и любые дополнительные специфичные для xml атрибуты), чтобы выяснить, что, когда он видит элемент <someConcreteType>, он сопоставляется с экземпляром 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) и настроить конструктор вручную. Это не очень сложно, но достаточно сложно, чтобы это того не стоило, если вам действительно не нужно 1026 *.

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;}
}

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

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

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

0 голосов
/ 04 ноября 2018
**Example of Enum Abstract Serializer

Simple example of an abstract enum ...(Java)(Spring-Boot) 
----------------------------------------------------------------------------------**


@JsonSerialize(using = CatAbstractSerializer.class)
public enum CatTest implements Tes{

    TYPE1(1, "Type 1"), TYPE2(2, "Type 2");

    private int id;
    private String nome;

    private CatTest(int id, String nome) {
        // TODO Auto-generated constructor stub

        this.id = id;
        this.nome = nome;


    }
    @JsonValue
    public int getId() {
        return id;
    }
    @JsonSetter
    public void setId(int id) {
        this.id = id;
    }
    @JsonValue
    public String getNome() {
        return nome;
    }
    @JsonSetter
    public void setNome(String nome) {
        this.nome = nome;
    }
    @Override
     @JsonValue
     public String toString() {
            return nome;
     }
     @JsonCreator
        public static CatTest fromValueString(String nome) {
            if(nome == null) {
                throw new IllegalArgumentException();
            }
            for(CatTest nomeSalvo : values()) {
                if(nome.equals(nomeSalvo.getNome())) {
                    return nomeSalvo;
                }
            }
            throw new IllegalArgumentException();
        }


}


public interface Tes {

    @JsonValue
    int getId();

    @JsonValue
    String getNome();

    @JsonSetter
    void setId(int id);

    @JsonSetter
    void setNome(String nome);


}

public class CatAbstractSerializer<T extends Tes> extends JsonSerializer<T> {

    @Override
    public void serialize(T value, JsonGenerator gen, SerializerProvider serializers)
            throws IOException, JsonProcessingException {
        // TODO Auto-generated method stub

        gen.writeStartObject();
        gen.writeFieldName("id");
        gen.writeNumber(value.getId());
        gen.writeFieldName("name");
        gen.writeString(value.getNome());
        gen.writeEndObject();

    }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...