Предположим, что есть класс с именем Sprinter
:
public class Sprinter {
protected int travelMeters;
public void run(int seconds) {
this.travelMeters = 9 * seconds;
}
public int getTravelMeters(){
return travelMeters;
}
}
И тип SprintGenius
, наследующий Sprinter
:
class SprintGenius extends Sprinter {
public void run(int seconds) {
this.travelMeters = 10 * seconds;
}
}
По логике нужно создать 2 класса юнит-тестов, по одному для каждого типа.
В рамках модульного теста Sprinter
я бы получил:
@Before
public void setUp() {
Sprinter sprinter = new Sprinter();
}
public void testSprinterShouldRun90metersWithin10Seconds() {
sprinter.run(10);
assertEquals(sprinter.getTraveledMeters(),90);
}
В рамках модульного теста SprintGenius
я бы получил:
@Before
public void setUp() {
Sprinter sprinter = new SprintGenius();
}
public void testSprinterShouldRun100metersWithin10Seconds() {
sprinter.run(10);
assertEquals(sprinter.getTraveledMeters(),100);
}
В обоих приведенных выше тестах я бы проверил количество пройденных метров за 10 секунд.
Очевидно, что эти оба теста будут зелеными.
А как насчет нарушения принципа замещения Лискова?
Действительно, любой клиентский код должен ожидать, что любой спринтер пробежит ровно 10 метров за 9 секунд.
3 решения (первые два решения сигнализируются ПРАВИЛАМИ разработчикам ВСЕЙ команды и должны быть приняты и сохранены, даже если не все хорошо освоят концепцию Лискова)
1) В классе Sprinter
повторяются все тесты, но на этот раз на основе Sprinter sprinter = new SuperGenius()
и ожидаются 90 метров. => что должно потерпеть неудачу, и это именно то, что нам нужно! => предотвратить нарушение принципа Лискова.
2) В классе SprintGenius
всегда добавляйте аналогичный «клон» каждого теста на основе базового класса, исходя из точно таких же ожиданий. Итак, если у вас есть 2 разных теста, мы получим 4 теста. 2 объявляет Sprinter как Sprinter
и 2 объявляет Sprinter
как SprintGenius
.
3) Никогда не наследуйте от класса-конкретного (предполагаю, что это была ваша первая реакция, когда вы читали этот пост:)), предпочитайте композицию, если она подходит! Чтобы этой проблемы не было.
Как лучше всего предотвратить нарушение принципа замещения Лискова, основываясь на том факте, что многие разработчики игнорируют принцип Лискова и часто испытывают соблазн наследовать от конкретных классов вместо использования другого лучшего способа, такого как композиция или другая иерархия наследования?
Я не хочу, чтобы у меня возникали проблемы из-за того, что разработчик наследует от моего написанного класса (не сообщая мне..), вводя его в общий огромный список разнородных списков Sprinter
и обращаясь ко мне с «привет, странное поведение!» и часы отладки...
Я, конечно, не хочу объявлять ВСЕ мои конкретные классы "окончательными" :)
Sprinter
s, заставляя подкласс влиять на ожидаемое поведение базового класса. Я согласен, в посте нет кода, так как его довольно легко вывести. - person Mik378   schedule 02.11.2012