Java абстрактный метод с абстрактным параметром и наследованием - PullRequest
3 голосов
/ 30 июня 2010

Я недавно натолкнулся на проблему с API и реализацией, где появился следующий тип кода:

API - это абстрактный класс:

public abstract class A {
 public A sum(A a) {
  System.out.println("A.sum(A) called");
  return null;
 }
}

Реализация простакласс:

public class B extends A {
 public B sum(B b) {
  System.out.println("B.sum(B) called");
  return null;
 }
}

Когда дело доходит до его использования, я пишу:

public class Main {

  public static void main(String args[]) {
    B b = new B();
    A basa = new B();

    b.sum(b);
    basa.sum(b);
    basa.sum(basa);
  }  
}

Что приводит к:

% java Main
B.sum(B) called
A.sum(A) called
A.sum(A) called

Я понимаю, что сумма B не переопределяетСумма A, поскольку ее подпись различна, но я хотел бы обеспечить эффективную реализацию суммы для объектов эффективного типа B. Я думаю, что такой дизайн довольно классический, и я хотел бы сейчас узнать, как мне разработать свой API и реализацию так, чтобы онявляется эффективным.

Конечно, я мог бы предоставить sum (A a) в классе be и проверить, является ли b instanceOf B, прежде чем вызывать sum (B) или super, но я подумал, что instanceOf следует избегатьпо соображениям эффективности (если он неэффективен, он может быть еще менее эффективным, чем моя «абстрактная» реализация.

Ответы [ 3 ]

3 голосов
/ 30 июня 2010

instanceof обычно можно избежать с помощью шаблона посетителя.В зависимости от ваших потребностей, это может быть или не быть излишним.Это гибкий, но довольно многословный.В приведенном ниже примере я удалил abstract из A, чтобы проиллюстрировать, как он работает с разными типами.

Хитрость в том, что когда объект просят посетить посетителя, сам объект выбирает правильный accept метод у посетителя.Проверка «instanceof» разрешается с помощью полиморфизма.(Я сомневаюсь, что он более эффективен, чем instanceof.)

interface Visitor {
    public A accept(A a);
    public B accept(B b);
}

class A {
    public A sum(A a) {
        System.out.println("A.sum(A) called");
        return null;
    }

    public A visit(Visitor sv) {
        return sv.accept(this);
    }
}

class B extends A {
    public B sum(B b) {
        System.out.println("B.sum(B) called");
        return null;
    }

    public B visit(Visitor sv) {
        return sv.accept(this);
    }
}

public class Test {

    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        A basa = new B();

        a.visit(new SumVisitor(b));        // a.sum(b);
        b.visit(new SumVisitor(b));        // b.sum(b);
        basa.visit(new SumVisitor(b));     // basa.sum(b);
        basa.visit(new SumVisitor(basa));  // basa.sum(basa);
    }

    static class SumVisitor implements Visitor {
        A arg;
        SumVisitor(A arg) { this.arg = arg; }
        public A accept(A a) { return a.sum(arg); }
        public B accept(B b) { return b.sum(arg); }
    }
}

Вывод:

A.sum(A) called
B.sum(B) called
B.sum(B) called
B.sum(B) called

Disclamer;Некоторое время назад я написал посетителю, поэтому, пожалуйста, исправьте меня, если у меня есть какие-либо ошибки в этом (почти не проверенном) фрагменте кода.Или лучше, отредактируйте пост самостоятельно и улучшите его:)

3 голосов
/ 30 июня 2010

Поскольку B экземпляры можно суммировать с A экземплярами, используя myA.sum(myB), вы сможете изменить определение B на sum, чтобы оно переопределяло , если только конечно sum является заполнителем и не должен быть коммутативным.

UPDATE:

Если этого недостаточно, вы можете начать фантазировать с использованием дженериков. Вот грубый пропуск в том, что я имею в виду:

public abstract class A {
    public <T extends A> T sum(T a) {
        System.out.println("A.sum(A) called");
        return null;
    }

    public static void main(String args[]) {
        B b = new B();
        b.sum(b);

        A basa = new B();
        basa.sum(b);
        basa.sum(basa);
    }

    public static class B extends A {
        @Override
        public <T extends A> T sum(T b) {
            System.out.println("B.sum(B) called");
            return null;
        }
    }
}

@ aioobe прав, что общепринятым решением является использование шаблона посетителя. Я предлагаю их как менее полные, но менее многословные альтернативы.

0 голосов
/ 30 июня 2010

Итак, что заставляет вас думать, instanceof медленный? Он используется в нескольких местах в JDK, где они хотят предоставить «быстрый путь» для некоторых известных реализаций абстрактного класса или интерфейса. Обычный совет применим здесь: «Тест, не угадай».

...