Динамическая диспетчеризация (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
}
}