Динамическая диспетчеризация (runtime-polymorphism) с перегруженными методами без использования instanceof

Я хочу сохранить объекты Arc а также Line в одном ArrayList, затем получите пересечение обоих. Вопрос в том, как я могу сыграть i а также j в своем первоначальном классе. я знаю это instanceof работает, но это был бы самый грязный метод.

public class Intersection {
    public static boolean intersect(ArrayList<Curve> list1, ArrayList<Curve> list2) {
        for (Curve i : list1) {
            for (Curve j : list2) {
                if (i.intersection(j).length > 0) 
                    return true;
            }
        }
        return false;
    }
}

public abstract class Curve {
    public Point[] intersection(Curve c) {
        return new Point[] {};
    }
}

public class Line extends Curve {
    public Point[] intersection(Line l) {
        // returns intersection Point of this and l
    }

    public Point[] intersection(Arc a) {
        // returns intersection Point(s)
    }
}

public class Arc extends Curve {
    public Point[] intersection(Line l) {
        // return intersection Point(s) of this and l
    }

    public Point[] intersection(Arc a) {
        // returns intersection Point(s)
    }
}

Спасибо за вашу помощь!

7 ответов

Решение

Поскольку каждый подкласс уже должен знать о других подклассах (например, Arc должен знать о Line класс для того, чтобы реализовать Arc а также Line пересечение), нет ничего плохого в использовании instanceof,

В каждом подклассе вы можете переопределить базовый класс public Point[] intersection(Curve c) метод и отправьте реализацию одному из перегруженных методов.

Например:

public class Arc extends Curve {    
    @Override
    public Point[] intersection(Curve c) {
        if (c instanceof Line)
            return instersection ((Line) c);
        else if (c instanceof Arc)
            return intersection ((Arc) c);
        else
            return an empty array or null, or throw some exception
    }

    public Point[] intersection(Line l) {
        // return intersection Point(s) of this and l
    }

    public Point[] intersection(Arc a) {
        // returns intersection Point(s)
    }
}

Таким образом, вам не нужно ничего менять в своем public static boolean intersect(ArrayList<Curve> list1, ArrayList<Curve> list2) метод.

Есть два подхода к решению такого варианта использования:


1. Реализация многократной отправки:

Начните с создания Curve интерфейс и добавить две перегруженные версии intersect к этому интерфейсу, что делает их частью договора. Далее, есть intersection(Curve c) Метод в каждом из подклассов делегирует вызов соответствующей перегруженной форме. (иначе шаблон посетителя)

interface class Curve {
    public Point[] intersection(Curve c);

    public Point[] intersection(Line l);

    public Point[] intersection(Arc c);

}

class Line extends Curve {

    public Point[] intersection(Curve c) {
        return c.intersection(this);
    }

    @Override
    public Point[] intersection(Line l) {
        System.out.println("line interesection with line");
        return new Point[0];
    }

    @Override
    public Point[] intersection(Arc c) {
        System.out.println("line intersection with arc");
        return new Point[0];
    }

}

class Arc extends Curve {

    public Point[] intersection(Curve c) {
        return c.intersection(this);
    }
    @Override
    public Point[] intersection(Line l) {
        System.out.println("arc interesection with line");
        return new Point[0];
    }

    @Override
    public Point[] intersection(Arc c) {
        System.out.println("arc interesection with arc");
        return new Point[0];
    }
}

Вы можете позвонить intersection метод в Intersection Класс без необходимости явного приведения:

public class Intersection {
    public static boolean intersect(ArrayList<Curve> list1,
            ArrayList<Curve> list2) {
        for (Curve i : list1) {
            for (Curve j : list2) {
                if (i.intersection(j).length > 0)
                    return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Curve line1 = new Line();
        Curve arc1 = new Arc();
        Curve line2 = new Line();
        Curve arc2 = new Arc();

        ArrayList<Curve> list1 = new ArrayList<>();
        ArrayList<Curve> list2 = new ArrayList<>();
        list1.add(line1);
        list1.add(arc1);
        list2.add(line2);
        list2.add(arc2);

        Intersection.intersect(list1, list2);

    }
}

Дополнительно: взгляните на этот альтернативный подход к реализации шаблона посетителя.


2. Сделайте так, чтобы линия и кривая придерживались того же интерфейса (контракта):

Если Line а также Arc придерживаться интерфейса Curveваш код больше не будет нуждаться в перегруженных версиях intersect метод. Если мы говорим, что Line это Curve и Arc также Curveоба эти класса должны иметь одинаковый интерфейс Curve (под интерфейсом я имею в виду список операций, которые они поддерживают). Если эти классы не имеют такой же интерфейс, как Curve, это где проблемная область лежит. Методы, представленные в Curve должны быть единственными методами, которые должны требовать Line а также Arc классы.

Существует несколько стратегий, позволяющих исключить необходимость использования в подклассах методов, отсутствующих в суперклассе:

  • Если подкласс требует дополнительных входных данных по сравнению с суперклассом, предоставьте эти входные данные через конструктор, а не создавайте отдельные методы, которые работают с этими входными данными.
  • Если для подкласса требуется дополнительное поведение, не поддерживаемое суперклассом, поддерживайте это поведение с помощью композиции (см. Шаблон стратегии), а не добавляйте методы для поддержки дополнительного поведения.

Как только вы избавляетесь от необходимости иметь специализированные методы в подклассе, отсутствующие в суперклассе, ваш код автоматически устраняет необходимость instanceof или тип проверки. Это соответствует принципу подстановки Лискова.


Альтернативой было бы использовать isAssignableFrom метод класса Class, Ниже приведен пример:

Exception e = new Exception();
RuntimeException rte = new RuntimeException();
System.out.println(e.getClass().isAssignableFrom(RuntimeException.class));
System.out.println(rte.getClass().isAssignableFrom(Exception.class));
System.out.println(rte.getClass().isAssignableFrom(RuntimeException.class));

Вот этот isAssignableFrom метод, и это то, что он говорит:

Определяет, является ли класс или интерфейс, представленный этим объектом Class, тем же или является суперклассом или суперинтерфейсом класса или интерфейса, представленного указанным параметром Class. Возвращает истину, если так; в противном случае возвращается false. Если этот объект Class представляет примитивный тип, этот метод возвращает true, если указанный параметр Class является именно этим объектом Class; в противном случае возвращается false.

Первое соображение: нужно ли вам конвертировать (upcast) i а также j от Curve в Arc или же Line?

Пожалуйста, посмотрите, например, здесь:

Что нужно использовать Upcasting в Java?

Если вы решили, что действительно нуждаетесь в повышении, к сожалению, нет волшебного яйца - вы не можете избежать использования instanceof чтобы решить класс для upcast.

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

Сожалею!

Если вы не хотите использовать instanceof тогда альтернативой является использование композиции для получения типа. Следующий подход не будет использовать instanceof и будет использовать только предпочтительный Class.cast операция:

public static class Intersection {

        public static boolean intersect(ArrayList<Curve> list1, ArrayList<Curve> list2) {            
            for (Curve i : list1) {                
                Optional<Line> il = i.get(Line.class);
                Optional<Arc> ia = i.get(Arc.class);

                for (Curve j : list2) {
                    Optional<Line> jl = j.get(Line.class);
                    Optional<Arc> ja = j.get(Arc.class);

                    Point[] intersection = null;

                    if ( il.isPresent() ){
                        if ( jl.isPresent() ) intersection = il.get().intersection( jl.get() );
                        else if ( ja.isPresent() ) intersection = il.get().intersection( ja.get() );
                    }else if ( ia.isPresent() ){
                        if ( jl.isPresent() ) intersection = ia.get().intersection( jl.get() );
                        else if ( ja.isPresent() ) intersection = ia.get().intersection( ja.get() );
                    }    

                    if ( intersection != null && intersection.length > 0 ) return true;
                }
            }
            return false;
        }
    }

    public static abstract class Curve {

        public abstract <T extends Curve> Optional<T> get(Class<T> clazz);

    }

    public static class Line extends Curve {

        public <T extends Curve> Optional<T> get(Class<T> clazz){
            return clazz.equals(Line.class) ? Optional.of( clazz.cast(this) ) : Optional.empty();
        }

        public Point[] intersection(Line l) {
            return new Point[] {};
        }

        public Point[] intersection(Arc a) {
            return new Point[] {};
        }
    }

    public static class Arc extends Curve {

        public <T extends Curve> Optional<T> get(Class<T> clazz){
            return clazz.equals(Arc.class) ? Optional.of( clazz.cast(this) ) : Optional.empty();
        }

        public Point[] intersection(Line l) {
            return new Point[] {};
        }

        public Point[] intersection(Arc a) {
            return new Point[] {};
        }
    } 

Итак, одно решение, которое я обнаружил, было бы использовать абстрактный метод в Curve и if-else цепочка в подклассах. Однако я не очень доволен этим решением.

public abstract class Curve {
    public abstract Point[] intersection(Curve c);
}

public class Line extends Curve {
    public Point[] intersection(Curve c) {
        if (c instanceof Line) {
            return this.intersection((Line) c);
        } else if (c instanceof Arc) {
            return this.intersection((Arc) c);
        }
    }

    private Point[] intersection(Line l) {
        // returns intersection Point of this and l
    }

    private Point[] intersection(Arc a) {
        // returns intersection Point(s)
    }
}

+ Изменить Curve к интерфейсу. Держать ArrayList<Curve> то же самое и вместо этого, извлеките intersection метод к отдельному классу, и заставить его работать Curves,

Вам нужно будет использовать instanceof проверьте там, но ваш дизайн будет немного чище из-за использования наследования.

public interface Curve {
...
}

public class Line extends Curve {
...
}

public class Arc extends Curve {
...
}

public class IntersectionUtility {
    public static boolean intersects(ArrayList<Curve> list1, ArrayList<Curve> list2) {
        for (Curve i : list1) {
            for (Curve j : list2) {
                if (i.intersection(j).length > 0) 
                    return true;
            }
        }
        return false;
    }

   public Point[] intersection(Curve a, Curve b) {
      if (a.instanceof(Line.class)) {
        if (b.instanceof(Line.class)) {
           return findIntersection((Line) a, (Line) b); // two Lines
        } else {
           return findIntersection((Line) a, (Arc) b); // a Line and an Arc
        }
      } else {
        if (b.instanceof(Line.class)) {
           return findIntersection((Line) b, (Arc) a); // a Line and an Arc
        } else {
           return findIntersection((Arc) a, (Arc) b); // two Arcs
        }
      }
   }

    public Point[] findIntersection(Line a, Line b) {
        // returns intersection Point of two Lines
    }

    public Point[] findIntersection(Arc a, Arc b) {
        // returns intersection Point(s) of two Arcs
    }

    public Point[] findIntersection(Line a, Arc b) {
        // returns intersection Point(s) of an Line and an Arc
    }
}
Другие вопросы по тегам