2016-07-23 3 views
1

Я пробовал искать подходящее решение, но было трудно найти его.Альтернативная реализация INSTANCEOF при наличии «многих» подклассов

Это довольно простой вопрос: существует ли эффективная альтернатива использованию INSTACEOF, когда мы хотим определить тип класса, когда есть много того instancess этого класса?

Например:

Мы есть родительский Product класс

public abstract class Product { 

    public abstract String preview(); 

} 

который имеет 3 подклассы (Винил, бронирование и видео)

public class Vinyl extends Product { 

@Override 
public String preview() { 
    return "Playing a vinyl"; 
} 

}

public class Book extends Product { 

@Override 
public String preview() { 
    return "Reading a book"; 
} 

}

@Override 
public String preview() { 
    return "Playing a video"; 
} 

Теперь я хочу, чтобы проверить в соответствии для каждого продукта типа это предварительный метод возвращал значение - Когда мы подклассы, это вполне понятно и аккуратным при написании:

public class TestProduct { 

    List<Product> productList = new ArrayList<>(); 

    public void preview(Product product) { 
     for (Product currentProduct : productList) { 
      String preview = currentProduct.preview(); 

      if (product instanceof Book) { 
       Assert.assertEquals(preview, "Reading a book"); 

      } else if (product instanceof Vinyl) { 
       Assert.assertEquals(preview, "Playing vinyl"); 

      } else { 
       Assert.assertEquals(preview, "Playing video"); 
      } 
     } 
    } 
} 

Но что, если у нас нет 3 продукта, , но 100 или более? Это не делает меня в каком-то смысле письменной 100 раз это INSTACEOF заявления .. Есть ли какой-либо правильный, масштабируемый способ?

+0

или расширить все классы от 'interface' продукта и сделать' общий список всего, что расширяет 'продуктов и повторите его, используя цикл 'for' для каждого продукта, который у вас есть. – emotionlessbananas

+0

Но при взаимодействии с генетическим списком. как именно я должен определить текущий тип элемента? – Nimrod

+0

Вам разрешено использовать 'interface' вместо класса' abstract'? –

ответ

2

Почему бы не сделать работу, в которой вы выполняете часть самого подкласса?

public abstract class Product { 
    abstract void doSomething(); 
} 

Тогда код, выполняющий цикл, не должен знать, какой тип продукта он имеет. Он может позволить продукту решить, что делать.

for (Product product : products) { 
    product.doSomething(); 
} 

Метод doSomething() может иметь аргументы и тип возвращаемого значения, если вам нужно.

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

+0

Это хороший подход, когда мне нужно активировать текущий метод подкласса, но (я отредактировал мой вопрос), что вы будете делать, когда вам нужно будет подтвердить каждое возвращаемое значение каждого подкласса? – Nimrod

+0

@Nimrod Если один класс утверждает что-то о поведении другого, тогда ваш класс не работает. – Sam

+0

@ Нимрод - это единичный тест? В этом случае каждый подкласс «Продукт» должен тестироваться индивидуально. – Sam

1

Это не всегда практично или возможно использовать полиморфизм или шаблон посетителя, например. когда вы не можете изменить подкласс или когда существует множество различных ситуаций, в которых вы хотите использовать doSomething() на основе определенного подтипа, например. когда у вас много разных декораторов —, вам не нужна эта логика в самих подклассах.В таких ситуациях вы можете использовать внешний обработчик, например:

class Handler<T> { 
    private Map<Class<? extends T>, Consumer<? super T>> handlers = new HashMap<>(); 

    Handler<T> register(Class<? extends T> subtype, Consumer<? super T> action) { 
     handlers.put(subtype, action); 
     return this; 
    } 

    void handle(T instance) { 
     Optional.ofNullable(instance) 
       .map(Object::getClass) 
       .map(this.handlers::get) 
       .orElseThrow(() -> new IllegalStateException("Unknown subtype!")) 
       .accept(instance); 
    } 
} 

Использование:

Handler<Product> productHandler = new Handler<Product>() 
     .register(Vinil.class, vinil -> gramophone.play(vinil.sideB())) 
     .register(Book.class, book -> library.put(book.isbn(), book)) 
     .register(Video.class, video -> { /* no-op */ }); 

products.stream() 
     .forEach(productHandler::handle); 
+2

Хотя что-то в этих строках будет работать, это извращение полиморфизма. Я не могу представить, что это облегчает жизнь в долгосрочной перспективе. – Sam

3

Это возможная реализация с использованием interface:

public interface class Product { 
    public void doSomething(); 
} 

Тогда каждый подкласс должен будет реализовать его собственное doSomething()

public class Vinyl implements Product { 
    public void doSomething(){ 
     System.out.println("Vinyl product"); 
    } 
} 

public class Book implements Product { 
    public void doSomething(){ 
     System.out.println("Book product"); 
    } 
} 

public class Video implements Product { 
    public void doSomething(){ 
     System.out.println("Video product"); 
    } 
} 

Тогда

Product product = new Vinyl(); 
product.doSomething(); 
// prints on the console --> "Vinyl product" 

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

+1

Очень важное примечание стороны. – Keppil

1

один из возможных решений, чтобы наслаждаться полиморфизм

public static void check(Object o) { 
     List<? extends Products> list = Arrays.asList(new A(), new B(), new C()); 

      if (o instanceof Products) { 
       Products p = (Products) o; 
       p.doSomething(); 
      } 
     } 

и интерфейс

public interface Products { 
    public void doSomething(); 
} 
Смежные вопросы