Способ расширить инстанцируемый класс и добавить компонент значения, сохраняя контракт равных
Эффективная Java от Джошуа Блоха утверждает:
Нет никакого способа расширить инстанцируемый класс и добавить компонент значения, сохраняя
equals
контракт, если вы не готовы отказаться от преимуществ объектно-ориентированной абстракции.
Эффективная Java приводит несколько примеров, которые нарушают принцип симметрии, транзитивности или подстановки Лискова. Я полагаю, вы прочитали статью. Я не думаю, что я должен публиковать весь предмет здесь. Однако я нашел решение, которое подчиняется контракту:
Point
:
public class Point{
private final int x;
private final int y;
public Point(int x, int y){
this.x = x;
this.y = y;
}
protected boolean equalsImpl(Point p){
return p.x == x && p.y == y;
}
@Override
public final boolean equals(Object o) {
if(!(o instanceof Point))
return false;
Point p = (Point)o;
return equalsImpl(p) && p.equalsImpl(this);
}
// Remainder omitted ...
}
ColorPoint
:
public class ColorPoint extends Point{
private java.awt.Color color;
public ColorPoint(int x, int y, java.awt.Color color){
super(x, y);
this.color = color;
}
@Override
protected boolean equalsImpl(Point p){
return (p instanceof ColorPoint) &&
super.equalsImpl(p) &&
((ColorPoint)p).color == color;
}
// Remainder omitted ...
}
Я знаю, что в этом случае следует использовать композицию, но мне интересно , действительно ли мое решение подчиняется equals
контракт. Если да, то, что Effective Java заявляет неправильно?
4 ответа
При такой реализации нет ColorPoint
равно Point
так что это нарушает принцип подстановки Лискова. Это делает честь equals
контракт хотя.
Обновление: LSP сломан или нет, зависит от контрактов, которые Point
класс имеет. Если у вас есть контракт, в котором говорится, что "две точки с одинаковыми координатами X и Y равны" или "вы можете использовать HashSet
"Устранить точки с дублирующими координатами", он нарушается. Если такого контракта не существует, LSP не нарушается.
С другой стороны, если нет ColorPoint
равно Point
Имеет ли смысл, что ColorPoint
это Point
?
Нет проблем с тем, чтобы инстанцируемый класс добавил компонент значения, соблюдая оба equals
класс и LSP, если контракт базового класса позволит производным классам сделать это, и если equals
Метод базового класса кодируется таким образом, чтобы либо
Проверьте точный тип чего-либо, с чем это сравнивается, и сообщите
false
если точные типы не совпадают.Определите метод, который он использует как часть процесса проверки на равенство и который производный класс может переопределять по мере необходимости.
Используя подход № 1, любой производный класс может определить equals
рассматривать новые поля без каких-либо трудностей, поскольку единственными вещами, которым экземпляр производного класса можно было бы считать равным, были бы другие экземпляры того же производного класса.
Использование подхода № 2 немного сложнее, но простым подходом может быть переопределение equals
что-то назвать protected
Метод equals2, как это:
@override boolean equals(Object other)
{
if (!(other instanceof MyType)) return false;
MyType otherAsMyType = (MyType)other;
return this.equals1(OtherAsMyType) && OtherAsMyType.equals1(this) &&
this.equals2(OtherAsMyType) && OtherAsMyType.equals2(this);
}
equals1
метод производного типа вернул бы false
когда прошло что-то, что не было получено из него, даже если этот другой объект ничего не знал о производной части; разделение сравнения на разные части гарантирует, что если тип может быстро определить, что объект другого типа не может быть равен ему, код в первом классе не будет тратить время на проверку частей, которые, по-видимому, могут быть равны.
LSP не требуется equals
контракт.
Возможно, этот вопрос является результатом ненужного включения LSP в тему equals
Реализации. Сгенерированное стандартное затмение equals
реализация уже использует super.equals
(с использованием наследования) и является гораздо более правильным и менее сложным, чем предлагаемое решение. Тем не менее, он не соответствует критериям LSP, что соответствует документации.
Я хочу знать, что такое LSP, поэтому я наткнулся на одну хорошую статью. Как говорится в статье, принцип подстановки Лискова в Java не выполняется
LSP определяет слабое ограничение для набора свойств, но затем утверждает, что для любого свойства можно показать, что поведение программы одинаково. Единственный способ, которым это когда-либо может быть проверено, - это если исходные объекты в системе настолько строго определены их спецификацией, что они не допускают никакой другой реализации. Например, авторы вышеупомянутой статьи утверждают, что, учитывая набор свойств метода, они могут претендовать на 100% поведенческую совместимость с другим методом, имеющим тот же контракт - и это НЕ ИСТИНА. По сути, я могу утверждать, что контракт метода toString() в Java очень слабый - "Возвращает строковое представление объекта".
Почему важен принцип LiskovSubstitutionPrile?
- Потому что если нет, то иерархия классов будет беспорядок. Беспорядок в том, что всякий раз, когда экземпляр подкласса передавался в качестве параметра любому методу, происходило странное поведение.
- Потому что, если нет, модульные тесты для суперкласса никогда не будут успешными для подкласса.
Ссылка по ссылке, может очистить ваши сомнения.