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

时间:2014-03-25 07:21:02

标签: java oop design-patterns

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

AFAIK在构造函数中调用虚方法是有问题的,这可能最终导致未定义的行为。 所以目前,我正在做以下事情:

MySerializableClass myClass = new MySerializableClass();
myClass.deserialize(buffer);

或者使用为扩展我的界面的每个类定义的专用静态方法(基本上只做上面两行代码):

MySerializableClass myClass = MySerializableClass.CreateMySerializableClass(buffer); 

我的问题是:有没有优雅的方法可以做到这一点,而无需为每个类实现ISerializable定义专用的静态方法?是否有任何设计模式可以解决这个问题?

注意:我的序列化是独一无二的,所以我需要自己编写,而且由于技术原因,我只能使用Java的非常基本的功能。 (没有注释,模板化元数据等)所以我需要一个非常基本的OOP解决方案。

4 个答案:

答案 0 :(得分:2)

我发现你的解决方案足够优雅,你正在做的是Factory,这是解决问题的一种优雅方式。您可以将构造函数保持为私有,并始终通过工厂检索对象

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 :(得分:0)

另一个解决方案是删除no-argument constructor,因此具体类必须必须使用参数构造函数进行初始化。

答案 2 :(得分: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的实例。然后,静态deserialize方法也可以调用一个可能的构造函数,该构造函数接受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方法。

答案 3 :(得分:0)

在我看来,您不需要使用方法实现ISerializable接口:serialize和deserialize。在任何可序列化的类中。

我认为最好
interface ISerializer
{
    byte[] serialize(ISerializable serializable);
    ISerializable deserialize(byte[] buffer);
}

并具有实现此ISerializer接口的Serializer类

ISerializable _序列化或反序列化的内容。

ISerializable将具有方法,序列化和反序列化需要什么。 如果序列化和反序列化不需要任何东西,它可以是Object而不是ISerializable。

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

  

反序列化&lt; T&gt;(byte []缓冲区);