Измените несколько файлов по расширению на UTF-8 без спецификации:
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False)
foreach($i in ls -recurse -filter "*.java") {
$MyFile = Get-Content $i.fullname
[System.IO.File]::WriteAllLines($i.fullname, $MyFile, $Utf8NoBomEncoding)
}
Принцип замены Лисков (LSP, lsp) является понятием в Объектно-ориентированном программировании, которое указывает:
Функции, которые используют указатели или ссылки на базовые классы, должны быть в состоянии использовать объекты производных классов, не зная это.
В его сердечном LSP об интерфейсах и контрактах, а также как решить, когда расширить класс по сравнению с использованием другая стратегия, такая как состав для достижения цели.
самый эффективный способ, которым я видел, чтобы проиллюстрировать этот тезис, был в [1 122] Главный Первый OOA& D. Они представляют сценарий, где Вы - разработчик на проекте создать платформу для стратегических игр.
Они представляют класс, который представляет плату, которая похожа на это:
Все методы берут координаты X и Y в качестве параметров для определения положения мозаики в двухмерной антенной решетке Tiles
. Это позволит разработчику игр управлять единицами в плате в ходе игры.
книга продолжает изменять требования, чтобы сказать, что игровая основа должна также поддерживать 3D игровые доски для размещения игр, которые имеют полет. Так ThreeDBoard
класс представлен, который расширяется Board
.
На первый взгляд это походит на хорошее решение. Board
обеспечивает и Height
и Width
, свойства и ThreeDBoard
обеспечивают ось Z.
то, Где это ломается, - когда Вы смотрите на всех других участников, наследованных от Board
. Методы для AddUnit
, GetTile
, GetUnits
и так далее, все берут оба параметра X и Y в Board
класс, но ThreeDBoard
потребности параметр Z также.
, Таким образом, необходимо реализовать те методы снова с параметром Z. Параметр Z не имеет никакого контекста к Board
класс и унаследованные методы от Board
, класс теряет их значение. Единица кода, пытающегося использовать ThreeDBoard
класс как его базовый класс Board
, была бы очень неудачливой.
, Возможно, мы должны найти другой подход. Вместо того, чтобы расшириться Board
, ThreeDBoard
должен состоять из [1 119] объекты. Один Board
объект на единицу оси Z.
Это позволяет нам использовать хорошие объектно-ориентированные принципы как инкапсуляция и повторное использование, и doesn’t нарушают LSP.
Был бы, реализовывая ThreeDBoard с точки зрения массива Совета это быть полезным?
, Возможно, можно хотеть рассматривать части ThreeDBoard в различных плоскостях как Совет. В этом случае можно хотеть абстрагировать интерфейс (или абстрактный класс) для Совета для обеспечения нескольких реализаций.
С точки зрения внешнего интерфейса, Вы могли бы хотеть факторизовать Интерфейс платы и для TwoDBoard и для ThreeDBoard (хотя ни одно из вышеупомянутого соответствия методов).
LSP касается инвариантов.
классический пример дан следующим объявлением псевдокода (опущенные реализации):
class Rectangle {
int getHeight()
void setHeight(int value)
int getWidth()
void setWidth(int value)
}
class Square : Rectangle { }
Теперь у нас есть проблема, хотя интерфейс соответствует. Причина состоит в том, что мы нарушили инварианты, происходящие от математического определения квадратов и прямоугольников. Путем методы get и работа методов set, Rectangle
должны удовлетворить следующий инвариант:
void invariant(Rectangle r) {
r.setHeight(200)
r.setWidth(100)
assert(r.getHeight() == 200 and r.getWidth() == 100)
}
Однако этот инвариант должен быть нарушенным корректной реализацией Square
, поэтому это не допустимая замена Rectangle
.
У Robert Martin есть превосходное статья о принципе замены Лисков . Это обсуждает тонкие и not-so-subtle пути, которыми может быть нарушен принцип.
Некоторые соответствующие части статьи (отмечают, что второй пример в большой степени сжат):
А Простой Пример Нарушения LSP
Одно из самых явных нарушений этого принципа является использованием Информации о типах во время выполнения (RTTI) C++ для выбора функции, основанной на типе объекта. т.е.:
void DrawShape(const Shape& s) { if (typeid(s) == typeid(Square)) DrawSquare(static_cast<Square&>(s)); else if (typeid(s) == typeid(Circle)) DrawCircle(static_cast<Circle&>(s)); }
Очевидно эти
DrawShape
функция плохо формируется. Это должно знать о каждой возможной производнойShape
класс, и это должно быть изменено каждый раз, когда новые производныеShape
создаются. Действительно, многие просматривают структуру этой функции как анафема на Объектно-ориентированное проектирование.Квадрат и Прямоугольник, Более тонкое Нарушение.
Однако существуют другие, намного более тонкие, способы нарушить LSP. Рассмотрите заявление, которое использует
Rectangle
класс, как описано ниже:class Rectangle { public: void SetWidth(double w) {itsWidth=w;} void SetHeight(double h) {itsHeight=w;} double GetHeight() const {return itsHeight;} double GetWidth() const {return itsWidth;} private: double itsWidth; double itsHeight; };
[...] Предполагают, что однажды пользователи требуют способности управлять квадратами в дополнение к прямоугольникам. [...]
Очевидно, квадрат является прямоугольником для всех нормальных намерений и целей. Так как отношения ISA содержат, логично смоделировать
Square
класс, как получаемый отRectangle
. [...]
Square
наследуетсяSetWidth
иSetHeight
функции. Эти функции являются совершенно несоответствующими дляSquare
, так как ширина и высота квадрата идентичны. Это должно быть значительной подсказкой, что существует проблема с дизайном. Однако существует способ обойти проблему. Мы могли переопределитьSetWidth
иSetHeight
[...], Но рассмотреть следующую функцию:
void f(Rectangle& r) { r.SetWidth(32); // calls Rectangle::SetWidth }
, Если мы передаем ссылку на
Square
объект в эту функцию, этиSquare
, объект будет поврежден потому что высота won’t быть измененным. Это - четкое нарушение LSP. Функция не работает на производные своих аргументов.[...]
LSP является правилом о контракте классов: если базовый класс удовлетворяет контракт, то LSP производные классы должны также удовлетворить тот контракт.
В Псевдо-Python
class Base:
def Foo(self, arg):
# *... do stuff*
class Derived(Base):
def Foo(self, arg):
# *... do stuff*
удовлетворяет LSP, если каждый раз Вы называете Нечто на Производном объекте, это дает точно те же результаты как вызов Нечто на Базовом объекте, пока аргумент является тем же.
Функции, которые используют указатели или ссылки на базовые классы, должны быть в состоянии использовать объекты производных классов, не зная это.
, Когда я сначала читал о LSP, я предположил, что это было предназначено в очень строгом смысле, по существу приравнивая его для взаимодействия через интерфейс с реализацией и безопасным с точки зрения типов броском. Который означал бы, что LSP или обеспечен или не самим языком. Например, в этом строгом смысле, ThreeDBoard, конечно, substitutable для Совета, насколько компилятор затронут.
После чтения больше на понятии, хотя я нашел, что LSP обычно интерпретируется более широко, чем это.
Короче говоря, то, что это означает для клиентского кода "знать", что объект позади указателя имеет производный тип, а не тип указателя, не ограничивается безопасностью типов. Соблюдение LSP является также тестируемым посредством зондирования объектов фактическое поведение. Таким образом, исследуя влияние состояния объекта и аргументов метода на результатах вызовов метода или типах исключений, выданных от объекта.
Возвращение к примеру снова, в теории методы Совета могут быть сделаны работать просто великолепно на ThreeDBoard. На практике однако будет очень трудно предотвратить различия в поведении, которое клиент не может обработать правильно, не создавая помехи функциональности, которую ThreeDBoard предназначается для добавления.
С этим знанием в руке, оценивая соблюдение LSP может быть большой инструмент в определении, когда состав является более соответствующим механизмом для расширения существующей функциональности, а не наследования.
LSP иллюстрирования яркого примера (данный Дядей Входят в подкаст, который я недавно услышал) был то, как иногда что-то, что звучит правильным на естественном языке, не вполне работает в коде.
В математике, Square
Rectangle
. Действительно это - специализация прямоугольника. "", Заставляет Вас хотеть смоделировать это с наследованием. Однако, если в коде Вы сделали Square
, происходят от Rectangle
, тогда Square
должно быть применимым где угодно, Вы ожидаете Rectangle
. Это делает для некоторого странного поведения.
Предполагают, что Вы имели SetWidth
и SetHeight
методы на Вашем Rectangle
базовый класс; это кажется совершенно логичным. Однако, если Ваш Rectangle
ссылка указала Square
, то SetWidth
и SetHeight
не имеет смысла, потому что установка той изменила бы другой для соответствия ему. В этом случае Square
проваливает Тест Замены Liskov с [1 114], и абстракция наличия Square
наследовались от [1 116], плохой.
Y'all должен проверить другое бесценное ТВЕРДЫЕ Принципы Мотивационные Плакаты .
Эта формулировка LSP слишком сильна:
Если для каждого объекта o1 типа S существует объект o2 типа T, таким образом, что для всех программ P defined с точки зрения T, поведение P неизменно, когда o1 заменяют o2, то S является подтипом T.
Который в основном означает, что S - другой, полностью инкапсулировавшая реализация той же самой вещи как T. И я мог быть полужирным и решить, что производительность является частью поведения P...
Так, в основном любое использование позднего связывания нарушает LSP. Это - смысл OO к получить другое поведение, когда мы заменяем объектом одного вида для одного из другого вида!
Формулировка, процитированная Википедией, лучше, так как свойство зависит от контекста и не обязательно включает целое поведение программы.
принцип замены Лисков