Допустим, у меня есть интерфейс, FooInterface
который имеет следующую подпись:
interface FooInterface {
public function doSomething(SomethingInterface something);
}
И конкретный класс, ConcreteFoo
который реализует этот интерфейс:
class ConcreteFoo implements FooInterface {
public function doSomething(SomethingInterface something) {
}
}
Я хотел бы ConcreteFoo::doSomething()
сделать что-то уникальное, если ему передан специальный тип SomethingInterface
объекта (скажем, он называется SpecialSomething
).
Это определенно нарушение LSP, если я усиливаю предварительные условия метода или выбрасываю новое исключение, но будет ли это все еще нарушением LSP, если я создаю специальные SpecialSomething
объекты, предоставляя запасной вариант для универсальных SomethingInterface
объектов? Что-то вроде:
class ConcreteFoo implements FooInterface {
public function doSomething(SomethingInterface something) {
if (something instanceof SpecialSomething) {
// Do SpecialSomething magic
}
else {
// Do generic SomethingInterface magic
}
}
}
doSomething()
метода было бы преобразование типа вSpecialSomething
: если он получит,SpecialSomething
он просто вернет объект без изменений, тогда как если он получит универсальныйSomethingInterface
объект, он запустит алгоритм для преобразования его вSpecialSomething
объект. Поскольку предварительные условия и постусловия остаются прежними, я не верю, что контракт был нарушен.y = doSomething(x)
, тоx.setFoo(3)
, а затем обнаружить , чтоy.getFoo()
возвращается 3?SpecialSomething
взамен возврата копии объекта. Хотя ради чистоты я также мог бы отказаться от оптимизации в особом случае, когда передается объект,SpecialSomething
и просто запустить его с помощью более крупного алгоритма преобразования, поскольку он все еще должен работать, поскольку он также являетсяSomethingInterface
объектом.Это не нарушение LSP. Однако это все еще является нарушением правила «не делать проверки типов». Было бы лучше спроектировать код таким образом, чтобы спецэффект возник естественным образом; возможно,
SomethingInterface
нужен другой член, который мог бы выполнить это, или, может быть, вам нужно внедрить абстрактную фабрику куда-нибудь.Однако это не сложное и быстрое правило, поэтому вам нужно решить, стоит ли компромисс. Прямо сейчас у вас есть запах кода и возможное препятствие для будущих улучшений. Избавление от этого может означать значительно более запутанную архитектуру. Без дополнительной информации я не могу сказать, какая из них лучше.
источник
Нет, использование того факта, что данный аргумент не только предоставляет интерфейс A, но и A2, не нарушает LSP.
Просто убедитесь, что у специального пути нет никаких более сильных предварительных условий (кроме тех, которые были проверены при принятии решения о его выборе), а также никаких более слабых постусловий.
Шаблоны C ++ часто делают это, чтобы обеспечить лучшую производительность, например, требуя
InputIterator
s, но давая дополнительные гарантии при вызове сRandomAccessIterator
s.Если вместо этого вам нужно решить во время выполнения (например, с помощью динамического приведения), остерегайтесь решения, какой путь выбрать, используя все ваши потенциальные выгоды или даже больше.
Использование особого случая часто идет против СУХОГО (не повторяйте себя), поскольку вам, возможно, придется дублировать код, и против ПОЦЕЛУЯ (Keep it Simple), поскольку это более сложно.
источник
Существует компромисс между принципом «не делать проверки типов» и «разделять ваши интерфейсы». Если многие классы предоставляют работоспособное, но, возможно, неэффективное средство для выполнения некоторых задач, и некоторые из них также могут предложить более эффективные средства, и существует потребность в коде, который может принимать любую из более широкой категории элементов, которые могут выполнить задачу (возможно, неэффективно), но затем выполнить задачу настолько эффективно, насколько это возможно, необходимо, чтобы все объекты реализовали интерфейс, включающий член, чтобы сказать, поддерживается ли более эффективный метод, и другой, чтобы использовать его, если он есть, или иметь код, который получает объект, проверить, поддерживает ли он расширенный интерфейс, и привести его, если так.
Лично я предпочитаю первый подход, хотя хотел бы, чтобы объектно-ориентированные структуры, такие как .NET, позволяли интерфейсам указывать методы по умолчанию (делая большие интерфейсы менее болезненными для работы). Если общий интерфейс включает необязательные методы, то один класс-оболочка может обрабатывать объекты с множеством различных комбинаций способностей, в то же время обещая потребителям только те способности, которые присутствуют в исходном упакованном объекте. Если многие функции разделены на разные интерфейсы, то для каждой комбинации комбинаций интерфейсов, которые могут понадобиться для обернутых объектов, потребуется разный объект-оболочка.
источник
Принцип подстановки Лискова касается подтипов, действующих в соответствии с контрактом их супертипа. Итак, как писал Ixrec, недостаточно информации, чтобы ответить, является ли это нарушением LSP.
То, что здесь нарушено, - это принцип открытого закрытого типа. Если у вас есть новое требование - магия SpecialSomething - и вам нужно изменить существующий код, то вы определенно нарушаете OCP .
источник