1. Определение
По соглашению, сначала мы рассмотрим определение принципа подстановки Лискова.
Все ссылки на базовый (родительский) класс должны прозрачно использовать объекты его подклассов. С точки зрения непрофессионала,Подклассы могут расширять родительские классыфункция, ноНевозможно изменить исходный родительский классФункции.
Основная идеянаследовать. Благодаря наследованию объекты подклассов могут использоваться везде, где есть ссылка на базовый класс. Например:
Parent parent = new Child();
Дело в том, как использовать его прозрачно?
Давайте подумаем над вопросом, может ли подкласс изменить исходную функцию родительского класса?
public class Parent {
public int add(int a, int b){
return a+b;
}
}
public class Child extends Parent{
@Override
public int add(int a, int b) {
return a-b;
}
}
Это хорошо?
Это точно нехорошо, изначально это было сложение, но оно было преобразовано в вычитание, что явно не соответствует познанию.
Это нарушает принцип подстановки Лискова.После того, как подкласс изменил исходную функцию родительского класса, когда мы используем его подкласс, где есть ссылка на родительский класс, нет возможности прозрачно использовать метод добавления.
Все методы, которые были реализованы в родительском классе, на самом деле устанавливают ряд спецификаций и контрактов.Хотя это не требует, чтобы все подклассы соответствовали этим спецификациям, если подкласс произвольно изменит эти неабстрактные методы, он будет разрушить всю систему наследования.
Следовательно, ключ к прозрачному использованию заключается в том, что подкласс не может изменить исходную функцию родительского класса.
2. Значение
1. Подкласс может реализовывать абстрактные методы родительского класса, но не может переопределять неабстрактные методы родительского класса.
Мы только что сказали, что подкласс не может изменить исходную функцию родительского класса, поэтому подкласс не может переопределить неабстрактный метод родительского класса.
Подкласс может реализовать абстрактный метод родительского класса, должен быть, абстрактный метод изначально реализован подклассом.
package com.fanqiekt.principle.liskov.rapper;
/**
* Rapper抽象类
*
* @Author: 懒人
*/
public abstract class BaseRapper {
/**
* freeStyle
*/
protected abstract void freeStyle();
/**
* 播放伴奏
*/
protected void playBeat(){
System.out.println("从乐库中随机播放一首伴奏:动次打次...");
}
/**
* 表演
* 播放伴奏,并进行freeStyle
*/
public void perform(){
playBeat();
freeStyle();
}
}
BaseRapper — это абстрактный класс, представляющий базовый класс Rapper.
Обычное выступление рэпера состоит в том, чтобы случайным образом сыграть аккомпанемент, а затем исполнить произвольный стиль.
freeStyle отличается, поэтому он написан как абстрактный метод, позволяющий подклассам свободно играть.
Процесс playBeat в основном такой же, воспроизведение аккомпанемента из музыкальной библиотеки по желанию, поэтому он написан как неабстрактный метод.
Процесс исполнения в основном одинаков, ставим аккомпанемент, а потом фристайл, и тоже пишем как неабстрактный метод.
package com.fanqiekt.principle.liskov.rapper;
/**
* Rapper
*
* @author 懒人
*/
public class Rapper extends BaseRapper {
/**
* 播放伴奏
*
* 子类覆盖父类非抽象方法
*/
@Override
protected void playBeat() {
System.out.println("关闭麦克风");
}
/**
* 表演
*
* 子类覆盖父类非抽象方法
*/
@Override
public void perform() {
System.out.println("跳鬼步");
}
/**
* 子类可以覆盖父类抽象方法
*/
@Override
protected void freeStyle() {
System.out.println("药药切克闹,煎饼果子来一套!");
}
}
Rapper является подклассом BaseRapper и переопределяет абстрактный метод freeStyle родительского класса.
Переопределение неабстрактного метода playBeat родительского класса и изменение логики включения микрофона явно нарушает принцип подстановки Лискова. Очевидно, это очень неправильный способ написания, потому что поведение родительского класса несовместимо с поведением дочернего класса, а родительский класс нельзя использовать прозрачно. Сыграй аккомпанемент и включи мне микрофон, ты точно не шутишь?
Я пытался модифицировать playBeat.
/**
* 子类覆盖父类非抽象方法
* 子类方法中调用super方法
*/
@Override
protected void playBeat() {
super.playBeat();
System.out.println("关闭麦克风");
}
Можно ли вызывать суперметод в методе подкласса?
Нет, причина в том, что между включением микрофона и воспроизведением аккомпанемента нет логической связи.
При прозрачном использовании подкласса, хотя аккомпанемент будет играть нормально, микрофон отключается без ведома звонящего, а отключение микрофона явно не имеет никакого отношения к воспроизведению аккомпанемента. Это не совсем прозрачно для вызывающего абонента.
Он также переопределяет неабстрактный метод Perform родительского класса и изменяет логику на dance, что нарушает принцип подстановки Лискова. Выступление, в котором только танцы без рэпа, все еще называется «Рэппер»?
Я попытался изменить представление ниже.
/**
* 表演
* freestyle + 跳舞
* 子类覆盖父类非抽象方法
*/
@Override
public void perform() {
super.perform();
System.out.println("跳鬼步");
}
Могу ли я изменить метод выполнения следующим образом?
Это нормально, почему подкласс вызывает суперметод, почему нельзя играть в Beat, просто исполнять?
Perform — это перфоманс, а dance — это дополнение к перфомансу, которое относится к категории перфоманса, и вызывающая сторона может прозрачно вызывать метод Perform.
Спокойный фристайл или танцевальный фристайл, для звонящего, оба относятся к фристайлу.
2. Подклассы могут добавлять свои уникальные методы.
Очень важная особенность наследования: подклассы могут добавлять новые методы после наследования от родительского класса.
/**
* 跳舞
* 子类中增加特有的方法
*/
public void dance(){
System.out.println("跳鬼步!");
}
Танцевальный метод можно добавить в Rapper.
3. Когда подкласс перегружает метод родительского класса, предварительные условия метода (то есть формальные параметры метода) более смягчены, чем входные параметры метода родительского класса.
Обратите внимание, что это подклассперегрузкаРодительский класс, а не дочерний класс переопределяет родительский класс.
Перегрузка эквивалентна совершенно новому методу, который не конфликтует с одноименным методом в родительском классе. Оба существуют одновременно, и метод выбирается автоматически на основе поступающих параметров.
Абстрактные методы могут быть перегружены так же, как и неабстрактные методы.
Почему формальные параметры метода более мягкие, чем у родительского класса?
Во-первых, формальные параметры не должны совпадать, если они совпали, то он переписывается и возвращается первое значение.
Во-вторых, что произошло бы, если бы мы были более строгими?
Мы можем рассмотреть следующий пример.
package com.fanqiekt.principle.liskov.rapper;
import java.util.List;
/**
* 父类
*
* @author 懒人
*/
public abstract class Parent {
public void setList(List<String> list){
System.out.println("执行父类setList方法");
}
}
Это родительский класс, и метод setList имеет формальный параметр типа List. >
package com.fanqiekt.principle.liskov.rapper;
import java.util.ArrayList;
/**
* 子类
*
* @author 懒人
*/
public class Children extends Parent {
public void setList(ArrayList<String> list) {
System.out.println("执行子类setList方法");
}
}
Это подкласс, и тип входящего параметра — ArrayList, который является более строгим, чем родительский класс.
Children children = new Children();
children.setList(new ArrayList<>());
Давайте запустим эту строку кода и посмотрим на результат.
执行子类setList方法
Есть ли проблема с этим результатом?
Есть проблема, setList(new ArrayList()) должен прозрачно выполнять метод setList(List list) родительского класса по принципу подстановки Лискова.
Это не очень хорошо понятно.Для вызывающей стороны я хочу вызвать метод setList(List list) Parent, но результатом будет выполнение метода setList(ArrayList list) Children.
Это как если бы подкласс переопределял метод setList суперкласса вместо переопределения метода setList подкласса.
То есть после того, как формальные параметры метода являются строгими, в некоторых случаях он становится переопределенным.
И переписывание явно не соответствует принципу подстановки Лискова.
Тогда давайте посмотрим на расслабленную версию.
/**
* 子类
*
* @author 懒人
*/
public class Children extends Parent {
public void setList(Collection<String> list) {
System.out.println("执行子类setList方法");
}
}
Подкласс, тип входящего параметра — Коллекция, который более расслаблен, чем родительский класс.
Children children = new Children();
children.setList(new ArrayList<>());
Снова запустим эту строку кода и посмотрим на результат.
执行父类setList方法
Children children = new Children();
children.setList(new HashSet<>());
Снова запустим эту строку кода и посмотрим на результат.
执行子类setList方法
Тип входящего параметра более мягкий, и подкласс может перегрузить суперкласс.
4. Когда метод подкласса реализует абстрактный метод родительского класса, постусловие метода (то есть возвращаемое значение метода) является более строгим, чем у родительского класса.
Обратите внимание, что этоПереопределить абстрактные методыНеабстрактные методы не могут быть переписаны.
Почему возвращаемое значение является более строгим, когда подкласс реализует абстрактный метод суперкласса?
package com.fanqiekt.principle.liskov.rapper;
import java.util.List;
/**
* 父类
*
* @author 懒人
*/
public abstract class Parent {
public abstract List<String> getList();
}
Родительский класс имеет абстрактный метод getList, а возвращаемое значение — List.
package com.fanqiekt.principle.liskov.rapper;
import java.util.List;
/**
* 子类
*
* @author 懒人
*/
public class Children extends Parent {
@Override
public Collection<String> getList() {
return new ArrayList<>();
}
}
Подкласс, getList возвращает как тип коллекции, который является более мягким.
Будет красная строка: ... попытка использовать несовместимый возвращаемый тип.
Поскольку возвращаемое значение родительского класса — это список, а возвращаемое значение дочернего класса — коллекция родительского класса из списка, при прозрачном использовании родительского класса необходимо преобразовать коллекцию в список. Повышение класса безопасно, но понижение не обязательно безопасно.
package com.fanqiekt.principle.liskov.rapper;
import java.util.List;
/**
* 子类
*
* @author 懒人
*/
public class Children extends Parent {
@Override
public ArrayList<String> getList() {
return new ArrayList<>();
}
}
Подкласс, getList возвращает как тип ArrayList, тип более строгий.
Преобразование ArrayList в список, безопасное преобразование.
2. Сцена
Шеф-повара восьми кухонь
Ресторан Tomato вырос из небольшого ресторана в большой ресторан после тщательного управления.
Шеф: Босс, у нас сейчас большой бизнес и большой трафик.Хотя я полон энергии, я не могу вынести опустошение такого количества людей.
Босс: Разрушение? Уверен?
Шеф: Как ты можешь, ты не ослышался, он заботится, он не выдерживает заботы о таком количестве людей.
Босс: Индейка, да, желание выжить очень сильное. Итак, каковы ваши мысли?
Шеф-повар: Я думаю, что мы можем представить шеф-поваров из восьми основных кухонь.Как только блюда будут переданы шеф-поварам разных кухонь, вкус и качество будут выше, чтобы их можно было сравнить с рестораном такого высокого уровня, как наш.
Босс: Ну, это логично, продолжайте.
Шеф-повар: Во-вторых, поскольку людей больше, мы можем увеличить скорость подачи блюд.В-третьих,...
Босс: Это имеет смысл. Мы нанимаем шеф-повара прямо сейчас, Маленькая Индейка. Поздравляю, вы получили повышение. Вы будущий шеф-повар. Потому что ваше желание выжить действительно сильно.
Шеф-повар: Спасибо, босс. (Внутреннее: У меня есть сильное желание выжить? Где оно сильное? Не уходи после школы, я дам тебе попробовать мою силу и приготовлю тебе стол из хороших блюд)
Желание выжить действительно сильное.
3. Осознайте
Никакой ерунды, просто код.
package com.fanqiekt.principle.liskov;
/**
* 抽象厨师类
*
* @author 懒人
*/
public abstract class Chef {
/**
* 做饭
* @param dishName 餐名
*/
public void cook(String dishName){
System.out.println("开始烹饪:"+dishName);
cooking(dishName);
System.out.println(dishName + "出锅");
}
/**
* 开始做饭
*/
protected abstract void cooking(String dishName);
}
Абстрактный класс шеф-повара с общедоступным методом cook отвечает за часть той же логики, что и шеф-повара, например, за подготовку к началу приготовления и приготовление пищи.
Детали конкретной кулинарии обеспечивают абстрактный метод приготовления пищи (кулинария), который должен быть переписан поварами конкретной кухни.
package com.fanqiekt.principle.liskov;
/**
* 山东厨师
*
* @author 懒人
*/
public class ShanDongChef extends Chef{
@Override
protected void cooking(String dishName) {
switch (dishName){
case "西红柿炒鸡蛋":
cookingTomato();
break;
default:
throw new IllegalArgumentException("未知餐品");
}
}
/**
* 炒西红柿鸡蛋
*/
private void cookingTomato() {
System.out.println("先炒鸡蛋");
System.out.println("再炒西红柿");
System.out.println("...");
}
}
Шеф-повар шаньдунской кухни ShanDongChef наследует абстрактный класс шеф-повара Chef и реализует абстрактный метод Cooking.
package com.fanqiekt.principle.liskov;
/**
* 四川厨师
*
* @author 懒人
*/
public class SiChuanChef extends Chef{
@Override
protected void cooking(String dishName) {
switch (dishName){
case "酸辣土豆丝":
cookingPotato();
break;
default:
throw new IllegalArgumentException("未知餐品");
}
}
/**
* 炒酸辣土豆丝
*/
private void cookingPotato() {
System.out.println("先放葱姜蒜");
System.out.println("再放土豆丝");
System.out.println("...");
}
}
Сычуаньский шеф-повар SiChuanChef наследует абстрактный класс шеф-повара Chef и реализует абстрактный метод приготовления пищи.
package com.fanqiekt.principle.liskov;
/**
* 服务员
*
* @author 懒人
*/
public class Waiter {
/**
* 点餐
* @param dishName 餐名
*/
public void order(String dishName){
System.out.println("客人点餐:" + dishName);
Chef chef = new SiChuanChef();
switch(dishName) {
case "西红柿炒鸡蛋":
chef = new ShanDongChef();
break;
case "酸辣土豆丝": //取款
chef = new SiChuanChef();
break;
}
chef.cook(dishName);
System.out.println(dishName + "上桌啦,请您品尝!");
}
}
Класс официанта Waiter имеет метод заказа, который информирует повара соответствующей кухни о необходимости готовить в соответствии с разными названиями блюд.
Здесь используется принцип подстановки Лискова, и родительский класс Chef может прозрачно использовать подклассы ShanDongChef или SiChuanChef.
package com.fanqiekt.principle.liskov;
/**
* 客人
*
* @author 懒人
*/
public class Client {
public static void main(String args[]){
Waiter waiter = new Waiter();
waiter.order("西红柿炒鸡蛋");
System.out.println("---------------");
waiter.order("酸辣土豆丝");
}
}
Давайте запустим его.
客人点餐:西红柿炒鸡蛋
开始烹饪:西红柿炒鸡蛋
先炒鸡蛋
再炒西红柿
...
西红柿炒鸡蛋出锅
西红柿炒鸡蛋上桌啦,请您品尝!
---------------
客人点餐:酸辣土豆丝
开始烹饪:酸辣土豆丝
先放葱姜蒜
再放土豆丝
...
酸辣土豆丝出锅
酸辣土豆丝上桌啦,请您品尝!
4. Преимущества
После кода мы обнаружили несколько преимуществ принципа замены.
Основная идея принципа подстановки Лискова — наследование, поэтому преимущество — это преимущество наследования.
повторное использование кодаНаследуя родительский класс, мы можем повторно использовать много кода, например, приготовление поваром перед приготовлением пищи и приготовление пищи.
Чтобы уменьшить стоимость создания класса, каждый подкласс имеет свойства и методы родительского класса.
Простота обслуживания и расширенияБлагодаря наследованию подклассы могут легче расширять функциональность.
Его также легче поддерживать, общедоступные методы находятся в родительском классе, а конкретные методы — в определенных подклассах.
5. Недостатки
Как видно выше, его недостатком является недостаток наследования.
Уничтожить пакетНаследование является навязчивым, поэтому оно создает тесную связь между подклассами и суперклассами.
Подкласс не может изменить родительский классЭто может привести к избыточному коду и снижению гибкости подклассов, поскольку подклассы имеют все методы и свойства родительского класса.
6. Хип-хоп говорит
Далее, пожалуйста, наслаждайтесьОригинальные песни принципа замещения Лискова.
嘻哈说:里氏替换原则
作曲:懒人
作词:懒人
Rapper:懒人
隔壁的说唱歌手可以在乐库播放的beat freestyle歌曲
他们表演默契得体还充满乐趣
非抽象重写不是合理
抽象的重写不需客气
这是属于他们哲理
继承是里氏替换的核心想法
引用父类的地方透明使用子类会让代码更加强大
子类可以有自己特有方法
重载父类时形参更加的广大
不然可能覆盖父类方法
重写抽象方法时返回值类型要往下
因为类向上转换可以把心放下
八大菜系每个厨师都有自己拿手的
那些共有基本功也都掌握透彻
优点是易扩展易维护自动继承父类拥有的
Прослушивание, пожалуйста, нажмите здесь
Когда мне нечего делать и слушать музыку, знания наполняют мой разум;
Не следует недооценивать изучение новых способов ношения наушников.