【问题标题】:interfaces and object deserialization接口和对象反序列化
【发布时间】:2017-03-18 04:36:39
【问题描述】:

我有一个接口,它定义了一个可序列化为字节数组的类。

public interface IByteSerializable
{
    byte[] GetBytes();
}

这方面的自然伙伴是反序列化方法,我想返回一个实现IByteSerializable的对象。

我在纠结如何设计这样的界面。

这似乎没有意义:

public interface IByteSerializable
{
    byte[] GetBytes();
    IByteSerializable GetObject(byte[] bytes);
}

因为GetObject() 的实现不能是static 并且使用虚拟IByteSerializable 对象只是为了调用GetObject() 方法来反序列化实际 我要的对象。

这样做似乎也没有意义:

public interface IByteSerializableFactory
{
    IByteSerializable GetObject(byte[] bytes);
}

工厂类可以解决问题,但这感觉会导致类爆炸。此外,给定IByteSerializable 子类如何序列化然后反序列化的细节是相互依赖的,因此将它们放在同一个位置而不是在两个不同的类中是有意义的。显然,反序列化给定IByteSerializable 对象所需的确切过程完全取决于该对象的GetBytes() 方法是如何编写的。

我可以使用一个通用的设计或模式来解决这个问题吗?

【问题讨论】:

    标签: c# oop interface


    【解决方案1】:

    当涉及到您的问题时,对于接口、类和模式有很多不同的看法。我的个人偏好是实现一个带有 byte[] 属性的接口和一个带有虚拟方法的抽象类(甚至完全失去接口,这可能不是你的选择,确实不适合 DI 和单元测试):

    public interface IByteSerializable
    {
        byte[] SerializableByteObject { get; }
    }
    
    public abstract class ByteSerializable : IByteSerializable
    {
        public byte[] SerializableByteObject { get; }
        protected virtual byte[] GetBytes() {
            return SerializableByteObject;
        }
        public abstract IByteSerializable GetObject();
        //{    // You can make this method virtual and use Impl method:
               // GetObjectImpl(SerializableByteObject);
        //}
        protected internal IByteSerializable GetObjectImpl(byte[] bytes) {
            // If you need a default implementation (GetObject() should be protected virtual then)
            // return IByteSerializable...;
        }
    }
    

    我想强调的是,接口 VS 抽象类是一个无休止的讨论。如果您可以在不实现接口的情况下做一些事情并且只使用抽象类 - 我强烈建议您这样做。

    2017 年 3 月 18 日更新:回复评论(定义行为是界面的目的)并解释我的看法,并在下面添加解释。

    在这种情况下,我们定义的“行为”是“一个对象应该可以转换为字节数组。转换结果应该可以转换回同一个对象。”所以我们实际上是在为一个对象和一个字节数组定义行为(因为在一个对象被反序列化之后——它不再是同一个对象,它只是一个字节数组)。

    在我看来,这是纯粹的工厂模式场景。

    // Let's define an interface for our serializable type of objects factory
    public interface IByteSerializableFactory<T>
    {
         T CreateFromBytes(byte[] objectDataToUse);
         byte[] CovertToBytes(T objectToConvert);
    }
    
    // Interface for any class that needs a serialization factory
    // This is not even necessary, but I like it to enforce people to implement simple methods that reference the factory.
    
    public interface IByteSerializable<T>
    {
        IByteSerializableFactory<T> GetFactory();
    }
    
    // Now a moment comes for us to have this kind of class. We need to build a factory first (because our interface requires a GetFactory() implementation. We can lose the IByteSerializable interface altogether, but then we lose a way to let people know which factory should be used.
    
    public class SomeBaseClassSerializationFactory : IByteSerializableFactory<SomeBaseClass>
    {
        public SomeBaseClass CreateFromBytes(byte[] objectDataToUse) { //...
            return new SomeClass();
        }
        public byte[] CovertToBytes(SomeBaseClass objectToConvert) { //...
            return new byte[1];
        }
    }
    
    // We have a factory, let's implement a class.
    
    public abstract class SomeBaseClass : IByteSerializable<SomeBaseClass>
    {
        public virtual IByteSerializableFactory<SomeBaseClass> GetFactory() { 
            return new SomeBaseClassSerializationFactory();                                             
        }
    }
    
    public class SomeClass : SomeBaseClass {
        // Now we're independent. Our derived classes do not need to implement anything.
        // If the way the derived class is serialized is different - we simply override the method
    }
    

    更新 2 3/18/17:在不同的答案下回复评论(通过简单使用接口的通用实现)。

    不幸的是,没有干净的方法可以做到这一点。有一种肮脏的方式(我个人认为:“BAD BAD BAD!”)使用一些作弊,定义定义序列化方法的类并使用反射返回正确的类型。下面的示例将需要在序列化方法中使用大量自定义逻辑来使用不同类型的正确字段:

    // You define an enum with action and a dictionary with a collection of serialization methods.
    public enum SerializationAction {
        ToBytes,
        ToObject    
    }
    
    // It can also be an enum, but it's easier to test with a collection of strings.
    public static readonly string[] SerializationKindList = new string[] {
        "FirstKind",
        "SecondKind"
    };
    
    // This generic class can have an implementation of all the handlers. Additional switching can be done by type, or reflection can be used to find properties for different classes and construct different classes.
    public class SerializationMethod {
        public object ProcessByKind (string kindToUse, SerializationAction action, object objectToProcess) {
            if (kindToUse == "FirstKind") {
                if (action == SerializationAction.ToBytes) {
                    return new byte[1]; 
                }
    
                return new SomeClass(); // These would need to be your hard implementations. Not clean.
            } else {
                throw new NotImplementedException();    
            }
        }
    }
    
    // This struct type defines the serialization method and is required for the interface implementation
    public struct ByteSerialization
    {
        public string SerializationTypeName { get; private set; }
        public ByteSerialization(string kindToUse) {
            if (!SerializationKindList.Contains(kindToUse)) {
                throw new ArgumentException();
            }
    
            SerializationTypeName = kindToUse;
        }
        public byte[] Deserialize(object objectToProcess) {
            var serializationMethod = new SerializationMethod();
            return (byte[])serializationMethod.ProcessByKind(this.SerializationTypeName, SerializationAction.ToBytes, objectToProcess);
        }
        public object Serialize(byte[] byteArrayToProcess) {
            var serializationMethod = new SerializationMethod();
            return serializationMethod.ProcessByKind(this.SerializationTypeName, SerializationAction.ToObject, byteArrayToProcess);
        }
    }
    
    
    // Interface for any class that needs to use generic serialization
    public interface IByteSerializable
    {
        ByteSerialization serializationType { get; }
    }
    
    // Creating extension methods for the interface to make the life easier
    public static class IByteSerializableExtensions {
        public static byte[] DeserializeObjectIntoBytes(this IByteSerializable objectToProcess) {
            return objectToProcess.serializationType.Deserialize(objectToProcess);
        }
        public static void SerializeObjectFromBytes(this IByteSerializable objectToProcess, byte[] fromBytes) {
            var someObjectData = objectToProcess.serializationType.Serialize(fromBytes);
        }
    }
    
    
    // Abstract base class implementation with static readonly field.
    // Only downside - there is no way to enforce the config of this field in the constructor from the interface.
    // There also no way to make sure this field always gets set for other implementations of IByteSerializable
    public abstract class SomeBaseClass : IByteSerializable
    {
        private static readonly ByteSerialization _serializationType = new ByteSerialization("FirstKind");
    
        public ByteSerialization serializationType { get { return _serializationType; } }
    }
    
    public class SomeClass : SomeBaseClass {
    
    }
    
    
    // And here's how one would use it. You will need to create a new object of the class before serializing from bytes.
    var someClass = new SomeClass();
    var bytes = someClass.DeserializeObjectIntoBytes();
    var someClass2 = new SomeClass();
    var byteArray = new byte[1];
    someClass2.SerializeObjectFromBytes(byteArray);
    

    【讨论】:

    • 谢谢菲尔。我知道您在这里陈述您的观点,但为了我自己的学习 - 为什么您建议尽可能避免使用接口?
    • @khargoosh 仅仅是因为接口在今天被过度使用。人们在使用它们时并没有真正了解它们的用途。接口是一个契约,就像一个承诺某些事情将被实现的请求——如果一个人不期望不同的开发者实现多个实现,就没有必要(我现在不是在谈论依赖注入)来实现一个契约——接口会使事情变得过于复杂。抽象类虽然使人们能够做几乎相同的事情并定义默认实现(虚拟方法)。
    • 我明白你在说什么,菲尔,非常感谢你的帮助。看看这个具体案例,你不认为interfaceabstract 类更有意义吗?我们在这里实际上是在定义行为,而不是定义对象是什么。许多不同类型的类可以找到实现IByteSerializable 接口的有效用途——但它们也可能被更好地定义为差异很大的基类的子类型。这不正是界面的目的吗?
    • @khargoosh,你是对的,行为定义是界面的目的。让我们用普通语言简单地说:“我希望一个对象可以序列化为一个字节数组。序列化之后,我希望这个字节数组可以序列化回原始对象。”该行为是为不同的对象定义的:为您的类和字节数组。所以你的类的唯一行为是:它可以转换为字节数组。如果您有许多需要从字节数组构建的类:应该使用工厂模式。
    • 感谢您的更新 - 我知道您的目标是什么,Factory 类负责序列化和反序列化对象。不幸的是,这意味着我需要一个单独的 Factory 类,它为每个实现 IByteSerializable 的类实现 IByteSerializableFactory 但也许这是最好的选择。
    【解决方案2】:

    使用泛型接口,每个实现都可以关闭泛型并返回关闭的类型。由实现决定返回什么类型。

    public interface ICustomSerializable<T> where T : class
    {
        byte[] GetBytes();
        T Deserialize(byte[]);
    }
    
    public class Foo : ICustomSerializable<Foo>
    {
        public byte[] GetBytes() {} 
        public Foo Deserialize(byte[]){} 
    } 
    
    public class Bar : ICustomSerializable<Bar>
    {
        public byte[] GetBytes() {} 
        public Bar Deserialize(byte[]){} 
    } 
    

    如果您有以通用方式进行序列化的类,那么:

    public abstract class Something
    {
        public byte[] GetBytes() { //common code } 
    } 
    
    public class SomethingConcrete : Something, ICustomSerializable<SomethingConcrete>
    {
        public SomethingConcrete Deserialize(byte[]){} 
    } 
    

    【讨论】:

    • 感谢 CodingYoshi - 我喜欢使用泛型在 Deserialize() 方法上定义返回类型的想法。您对如何在实际类中以一种有意义的方式最好地实现该接口方法有任何想法吗?我最初的问题仍然存在:工厂类会导致类爆炸;无法实现静态接口方法;从Something 派生只是为了实现这种行为 或功能似乎不是一个好的设计实践!在上面的最后一个示例中,我需要一个 SomethingConcrete 的实例来反序列化不同的对象。
    猜你喜欢
    • 2010-11-11
    • 2013-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-14
    相关资源
    最近更新 更多