在构造函数中调用虚方法 - 更好的设计

3

我在Java中声明了一个ISerializable接口。

基本上有两个方法:serialize()deserialize(byte[] buffer)

public interface ISerializable{
    byte[] serialize();
    deserialize(byte[] buffer);
}

以下是实现此接口的类的示例:

public class MySerializableClass implements ISerializable{   
    byte[] serialize(){bla bla}
    deserialize(byte[] buffer){bla bla};
}

理想情况下,我希望调用 deserailize 的过程是隐式的。也就是说,在调用构造函数 MySerializableClass(byte[] buffer) 时,它会自动调用正确的 deserialize 函数,并将缓冲区传递给它。如下所示:
public abstract class AbstractSerializable {
    public abstract byte[] serialize();
    public abstract void deserialize(byte[] buffer);
    public AbstractSerializable (){}
    public AbstractSerializable (byte[] buffer){
        deserialize();
    }
}

public class MySerializableClass extends AbstractSerializable {
    byte[] serialize(){bla bla}
    deserialize(byte[] buffer){bla bla};
}

据我所知,在构造函数中调用虚函数是有问题的,这可能导致未定义的行为。 因此,目前我正在执行以下操作:
MySerializableClass myClass = new MySerializableClass();
myClass.deserialize(buffer);

或者使用为每个扩展我的接口的类定义的专用静态方法(基本上只需执行上述2行代码):

MySerializableClass myClass = MySerializableClass.CreateMySerializableClass(buffer); 

我的问题是:是否有一种优雅的方法可以做到这一点,而无需为实现ISerializable接口的每个类定义一个专用的静态方法? 是否有任何设计模式可以解决这个问题?
注意:我的序列化是独特的,所以我需要自己编写它,并且出于技术原因,我只能使用Java的基本功能(没有注释、模板元数据等),因此我需要一个非常基本的面向对象解决方案。

你有没有看过Java本身是如何使用自己的Serializable接口来处理这些东西的?你知道你可以操纵java序列化如何处理你的类,对吧? - Fildor
4个回答

2
我认为你的解决方案足够优雅,你正在做的是一个工厂,这是解决问题的一种优雅方式。你可以保持你的构造函数私有,并且总是通过工厂来检索对象。
public class MySerializableClass extends AbstractSerializable {

    private MySerializableClass(){

    }

    public static MySerializableClass CreateMySerializableClass(final byte[] buffer){
        MySerializableClass result = new MySerializableClass();
        result.deserialize(buffer)
        return result;
    }

    byte[] serialize(){bla bla}

    deserialize(byte[] buffer){bla bla};
}

1
好的方法 - 用工厂方法替换构造函数 (http://sourcemaking.com/refactoring/replace-constructor-with-factory-method) - Sambuca
我不会将构造函数设为私有,因为有时我确实想要创建一个新的类而不进行反序列化。 - user844541
@Sambuca 有趣的文章 :) - Guillermo Merino
1
@user844541是为了保持一致性,你可以创建另一个不需要序列化的工厂,实际上结果是相同的,但由于你可以使用想要的名称调用工厂,因此你可以更好地了解它们的作用。因此,我的建议是将其保持私有并创建另一个工厂。 - Guillermo Merino
@GuillermoMerino,那正是我会做的事情。 - Sambuca

0
另一个解决方案是删除无参构造函数,这样你的具体类必须使用带参数的构造函数进行初始化。

那将如何解决问题?有时我确实想创建一个不是从字节数组创建的新实例。 - user844541

0

在我看来,在任何可序列化的类中,您不需要实现ISerializable接口及其方法:serialize和deserialize。我认为最好的做法是

interface ISerializer
{
    byte[] serialize(ISerializable serializable);
    ISerializable deserialize(byte[] buffer);
}

并且有实现ISerializer接口的Serializer类

ISerializable_要序列化或反序列化的内容。

ISerializable将拥有方法,序列化和反序列化所需的方法。 如果序列化和反序列化不需要任何东西,则可以使用Object代替ISerializable。

如果您不想在使用反序列化方法后进行转换,则此反序列化方法可以是通用的:

T deserialize<T>( byte[] buffer );


0
在进行序列化时,我不会传递字节数组 - 相反,我会使用 java.io.DataOutputjava.io.DataInput。然后,您可以声明接口 ISerializable,例如:
public interface ISerializable{   
    void serialize(DataOutput out) throws IOException;
    void deserialize(DataInput in) throws IOException;
}

然后,您可以提供静态实用程序方法,当提供一些DataOutput/DataInput时,能够序列化和反序列化ISerializable的实例。静态反序列化方法还可以调用可能接受DataInput作为其唯一参数的构造函数。

以下是此方法的完整示例代码,还包括用于测试序列化的主要方法:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;


public class SerializableTest {

    public interface ISerializable{   
        void serialize(DataOutput out) throws IOException;
        void deserialize(DataInput in) throws IOException;
    }

    /**
     * Writes the given ISerializable to the given DataOutput.
     */
    public static void writeSerializable(ISerializable s, DataOutput out) throws IOException{
        writeClass(out, s.getClass());
        s.serialize(out);
    }

    /**
     * Reads an ISerializable from the given DataInput.
     */
    public static ISerializable readSerializable(DataInput in, ClassLoader cl) throws IOException{
        ISerializable element = null;
        Class<?> c;
        try {
            c = readClass(in, cl);
        } catch (ClassNotFoundException e) {
            throw new IOException(e);
        }
        try {
            try {
                // see if the class has a constructor that accepts a DataInput
                Constructor<?> constructor= c.getDeclaredConstructor(DataInput.class);
                constructor.setAccessible(true);
                return (ISerializable)constructor.newInstance(in);
            } catch (NoSuchMethodException e) {
                //ignore
            }
            element = (ISerializable) newInstance(c);
            element.deserialize(in);
        } catch (Exception e) {
            throw new IOException("Could not deserialize the class" + c.getName());
        }
        return element;

    }

    private static <T> T newInstance(Class<T> c) throws IOException {
        T element = null;
        Constructor<T> constructor;
        try {
            constructor = c.getDeclaredConstructor();
            if (!constructor.isAccessible()) {
                constructor.setAccessible(true);
            }
            element = constructor.newInstance();
        } catch (NoSuchMethodException | InstantiationException |
                IllegalAccessException | IllegalArgumentException | 
                InvocationTargetException e) {
                throw new IOException(e);
        }
        return element;
    }

    private static void writeClass(DataOutput out, Class<?> c) throws IOException {
        out.writeUTF(c.getName());
    }

    private static Class<?> readClass(DataInput in, ClassLoader cl) throws IOException, ClassNotFoundException {
        String name = in.readUTF();
        return cl.loadClass(name);
    }


    // some test classes for testing serialization in the main method

    public static class TestClass implements ISerializable{
        private String data;

        protected TestClass() {
            // ISerializable no argument constructor
            super();
        }

        public TestClass(String data) {
            super();
            this.data = data;
        }

        @Override
        public void serialize(DataOutput out) throws IOException {
            out.writeUTF(data);
        }

        @Override
        public void deserialize(DataInput in) throws IOException {
            this.data = in.readUTF();
        }
    }

    public static class TestClass2 implements ISerializable{
        private final String data;

        protected TestClass2(DataInput in) throws IOException {
            // ISerializable DataInput constructor
            super();
            this.data = in.readUTF();
        }

        public TestClass2(String data) {
            super();
            this.data = data;
        }

        @Override
        public void serialize(DataOutput out) throws IOException {
            out.writeUTF(data);
        }

        @Override
        public void deserialize(DataInput in) throws IOException {
            throw new UnsupportedOperationException();
        }
    }

    // tests serialization and deserialization of two test classes
    public static void main(String[] args) {
        TestClass t1 = new TestClass("TestClass 1");
        TestClass2 t2 = new TestClass2("TestClass 2");

        File file = new File("testfile");
        if (file.exists()) {
            file.delete();
        }
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }

        DataOutputStream out = null;
        try {
            out  = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
            writeSerializable(t1, out);
            writeSerializable(t2, out);
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }finally{
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {}
            }
        }

        DataInputStream in = null;
        try {
            in  = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
            ClassLoader cl = SerializableTest.class.getClassLoader();
            TestClass loadedClass1 = (TestClass) readSerializable(in, cl);
            TestClass2 loadedClass2 = (TestClass2) readSerializable(in, cl);

            System.out.println("loadedClass1.data: " + loadedClass1.data);
            System.out.println("loadedClass2.data: " + loadedClass2.data);

        } catch (IOException e) {
            e.printStackTrace();
            return;
        } finally{
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {}
            }
        }
    }
}

当使用静态方法时,您当然会有存储类名和数据的内存开销。但是,如果这是一个问题,您仍然可以手动调用serializedeserialize方法。

事实上,我甚至没有访问IO的权限,更不用说反射或泛型了.. :( 就像我所说的,我正在一个非常原始的环境中工作... - user844541
你不需要访问IO来使用这种方法:DataInputDataOutput是简单的接口 - 你可以提供自己的实现,用于处理后台中的byte[]读写。 - Balder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接