Клонирование подклассов в Java - PullRequest
6 голосов
/ 02 декабря 2010

Мне нужно клонировать подкласс в Java, но в той точке кода, где это происходит, я не буду знать тип подкласса, только суперкласс.Какой лучший шаблон дизайна для этого?

Пример:

class Foo {
    String myFoo;
    public Foo(){}
    public Foo(Foo old) {
        this.myFoo = old.myFoo;
    }
}

class Bar extends Foo {
    String myBar;
    public Bar(){}
    public Bar(Bar old) {
        super(old); // copies myFoo
        this.myBar = old.myBar;
    }
}

class Copier {
    Foo foo;

    public Foo makeCopy(Foo oldFoo) {

        // this doesn't work if oldFoo is actually an
        // instance of Bar, because myBar is not copied
        Foo newFoo = new Foo(oldFoo);
        return newFoo;

        // unfortunately, I can't predict what oldFoo's the actual class
        // is, so I can't go:
        // if (oldFoo instanceof Bar) { // copy Bar here }
    }
}

Ответы [ 7 ]

6 голосов
/ 02 декабря 2010

Если у вас есть контроль над классами, которые вы пытаетесь скопировать, то виртуальный метод - это путь вперед:

class Foo {
    ...
    public Foo copy() {
        return new Foo(this);
    }
}
class Bar extends Foo {
    ...
    @Override public Bar copy() {
        return new Bar(this);
    }
}

(В идеале классы должны быть абстрактными или окончательными).

3 голосов
/ 02 декабря 2010

Самым стандартным способом в Java было бы заставить каждый класс, который может быть клонирован, реализовать Cloneable и переопределить Object.clone() с помощью общедоступной версии, которая выполняет клонирование подходящим образом для этого класса (по умолчанию Object.clone() делает мелкую копию объекта).

Обратите внимание, что многие думают, что Cloneable / Object.clone() - это плохой дизайн.

2 голосов
/ 02 декабря 2010

Если вы реализуете клонирование / клонирование правильным способом , у вас не возникнет проблем суперкласса. Это связано с тем, что Object.clone () является очень специальным методом - в очень кратком описании: Object.clone () всегда будет возвращать объект того же типа, который был вызван.

@ см. http://download.oracle.com/javase/6/docs/api/java/lang/Object.html#clone%28%29

Таким образом, правильная реализация clone и clonable будет выглядеть так:

class Foo implements Clonable{
    String myFoo;
    ...
    Foo clone() {
      try {
        Foo clone = (Foo) super.clone();
        clone.myFoo = this.myFoo;
        return clone;
      } catch (CloneNotSupportedException e) {
        throw new RuntimeException("this could never happen", e);
      }
    }
}

class Bar extends Foo {
    String myBar;
    ...
    Bar clone() {
      try {
        Bar clone = (Bar) super.clone();
        clone.myBar = this.myBar();
        return clone;
      } catch (CloneNotSupportedException e) {
        throw new RuntimeException("this could never happen", e);
      }
    }
}

Тогда реализация копира проста:

class Copier {
  Foo foo;

  /**
   * @return return a Foo if oldFoo is of class Foo, return Bar if oldClass is of class Bar.
   */
  public Foo makeCopy(Foo oldFoo) {
    return oldFoo.clone();
  }
}
0 голосов
/ 08 апреля 2017

Этот шаблон / решение использует комбинацию конструкторов копирования и переопределенных типизированных виртуальных функций и передает экземпляр каждому суперклассу.

public class A {
    private String myA;

    public A(A a) {
        myA = a.myA;
    }

    public A copy() {
        return new A(this);
    }
}

public class B extends A {
    private String myB;

    public B(B b) {
        super(b);
        myB = b.myB;
    }

    public B copy() {
        return new B(this);
    }
}

public class C extends B {
    private String myC;

    public C(C c) {
        super(c);
        this.myC = c.myC;
    }

    public C copy() {
        return new C(this);
    }
}
0 голосов
/ 02 декабря 2010

Я не уверен, что это точно соответствует вашим требованиям, но я бы посоветовал взглянуть на шаблон Factory .

0 голосов
/ 02 декабря 2010

Если вам нужно глубоко клонировать объект, лучше всего использовать сериализацию Java. Для этого требуется, чтобы объект реализовал Serializable, но он создаст совершенно новый клонированный объект без общих ссылок на оригинал.

Возможно, ваш основной класс реализует Serializable, поэтому каждый подкласс будет автоматически поддерживать его.

Наконец, это, вероятно, можно обновить, чтобы использовать Piped Streams, а не ByteArrayOutputStream, чтобы использовать меньше памяти и быть быстрее, хотя это будет не заметно, если у вас есть небольшие объекты.

public static<T> T clone(T object) {
    try {
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(bOut);
        out.writeObject(object);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bOut.toByteArray()));
        T copy = (T)in.readObject();
        in.close();

        return copy;
    }
    catch(Exception e) {
        throw new RuntimeException(e);
    }
}
0 голосов
/ 02 декабря 2010

Способ сделать это - создать метод newInstance() в Foo и Bar.Обе реализации могут создавать объект правильного типа и возвращать его, код копирования должен знать только то, что он должен использовать oldFoo.newInstance() для получения копии объекта.

...