1

Тема: Принцип підстановки Лісков

Розмірковуючи про принцип підстановки Лісков, я прийшов до висновку, що відкрите наслідування базового класу, в якого є віртуальні методи (не чисто віртуальні), вже порушує цей принцип. До того ж неважливо, абстрактний клас чи ні. Ну і, звичайно ж, віртуального деструктора це не стосується.
Тобто проектуємо клас з поведінкою по-замовчуванню і маємо:

class Base {
public:
  // метод, що порушує принцип підстановки Лісков
  virtual void show() { std::cout << "default base class behaviour" << std::endl; }
  virtual ~Base() {}
};


class Derived : public Base {
  class someException{};
public:
  void show();
};

void Derived::show() {
  std::srand( time( 0 ) );
  // приблизно в половині випадків може "щось піти не так"
  if ( rand() % 2 == 0 ) {
    std::cout << " -- here might be an exception" << std::endl;
//     throw (new someException);
  }
  std::cout << "derived class behaviour" << std::endl; 
}

int main( int argc, char **argv ) {
  Base b;
  Derived d;
  b.show();
  d.show();
  return 0;
}

Також порушується принцип інверсії залежностей.
Тепер саме питання - я все правильно розумію? :)

Принцип підстановки Лісков
Принцип інверсії залежностей

2

Re: Принцип підстановки Лісков

Не зрозумів, де порушення принципу підстановки. Програма, яка може викинути exception, не обов'язково некоректна, і навпаки - це не зв'язані речі. Якщо з точки зору логіки замість Base можна поставити Derived, то все нормально. Як, наприклад, у твоєму прикладі.

3 Востаннє редагувалося koala (16.09.2014 15:40:21)

Re: Принцип підстановки Лісков

Ви переплутали. Принцип підставновки Лісков каже, що для нормальної роботи програми треба, щоб властивості не порушувалися нащадками. А ваша програма не порушує цей принцип - вона просто не відповідає йому (якщо властивість "не створювати винятків" є визначною для Base::show). Ну і тим більше його не порушує наслідування саме по собі - це те саме, що сказати, що молотки є небезпечними, оскільки ними можна вбити людину.

Так, і srand() треба викликати тільки один раз в коді, а не кожного разу перед викликом rand() - інакше ви з великою ймовірністю отримуватимете однакові числа.

Подякували: Arete1

4 Востаннє редагувалося Arete (16.09.2014 16:00:26)

Re: Принцип підстановки Лісков

Дякую за відповіді.

Можливо я зробив невірний приклад з exception, але я маю на увазі саме те що перевизначений віртуальний метод може змінити логіку та поведінку класу і при цьому порушити принцип Лісков. Нехай навіть порушення буде не програмне, а семантиче. Наприклад, в класі-потомку метод show() може некоректно працювати, якшо перед ним не виконати якийсь метод "А", який визначений в цьому ж класі. Звичайно ж базовий клас ні про який метод "А" нічого не знає.

В посиланні знизу саме приклад з віртуальними функціями і прозглядається як порушення принципу Лісков.
http://habrahabr.ru/post/83269/


srand() викликається один раз, але написано коряво, згоден.

5

Re: Принцип підстановки Лісков

Та звісно, що може! А молотком можна себе по пальцях вдарити. Просто не робіть такого, і все буде добре.

6

Re: Принцип підстановки Лісков

Arete написав:

Дякую за відповіді.

Можливо я зробив невірний приклад з exception, але я маю на увазі саме те що перевизначений віртуальний метод може змінити логіку та поведінку класу і при цьому порушити принцип Лісков.

Тепер правильно - саме неправильне перевизначення методу в нащадку порушує принцип підстановки, але не наявність реалізації у базовому класі.

Подякували: koala1

7 Востаннє редагувалося Arete (16.09.2014 17:26:38)

Re: Принцип підстановки Лісков

koala написав:

А молотком можна себе по пальцях вдарити. Просто не робіть такого, і все буде добре.

quez написав:

саме неправильне перевизначення методу в нащадку порушує принцип підстановки, але не наявність реалізації у базовому класі.

Ми з вами говоримо на різних рівнях програмування. Я говорю не про кодування методів і не про "правильні" чи "не правильні" методи. Я говорю саме про проектування інтерфейсу класу. А тут виходить що якщо є можливість "налажати" в наслідуємому класі, підкреслюю - достатьно тільки можливості, то це вже є порушенням принципу Лісков, бо властивість об’єкту x класу Base може бути змінена в класі-потомку.

З вашої логіки, якщо я використовую Pimpl з динамічним навішуванням реалізаціі на інтерфейс класу в ході виконання програми, то це значить в залежності від конкретної реалізації, наслідування є то з порушенням принципу Лісков, то ні, то невідомо яке... Так?

Я взагалі вважаю що принцип Лісков це рівень проектування системи, а не кодування методів. Не повинно проектування інтерфейсу класу (порушення чи непорушення принципу) залежати від реалізації та кодування методів цього класу.  По-доброму взагалі це повинні робити різні люди :)

Подивіться посилання на Хабр з мого попереднього повідомлення. Там вказується про те що зміна методу базового класу зі звичайного на віртуальний вже є порушенням принципу Лісков. Без всяких прикладів того як треба правильно чи неправильно кодувати методи в класі-нащадку.

8 Востаннє редагувалося Arete (16.09.2014 18:29:12)

Re: Принцип підстановки Лісков

Цитата з вікіпедії про принцип підстановки Лісков, посилання в першому повідомленні теми.

Сформулювати його можна у вигляді простого правила: тип S буде підтипом Т тоді і тільки тоді, коли кожному об'єктові o1 типу S відповідає певний об'єкт o2 типу T таким чином, що для всіх програм P, реалізованих в термінах T, поведінка P не зміниться, якщо o1 замінити на o2.

Віртуальна функція і є варіативність поведінки, отже в випадку використання віртуальних функцій принцип підставновки Лісков порушується.

Дякую за відповіді, мабуть я всеж-таки все вірно зрозумів  :D

9

Re: Принцип підстановки Лісков

Досить цікава логіка: самого класу-потомка може й не бути, але принцип уже порушений.

Подякували: koala, Arete2

10

Re: Принцип підстановки Лісков

Ні, неправильно. Це не абстрактний математичний принцип, а інженерний, просто сформульований так, що це вас заплутує. Тут питання не в теоретичній відповідності всіх можливих розширень програми, а в тому, чи відповідає йому конкретна повністю написана програма.

11 Востаннє редагувалося Arete (16.09.2014 22:43:58)

Re: Принцип підстановки Лісков

quez написав:

Досить цікава логіка: самого класу-потомка може й не бути, але принцип уже порушений.

Ліричний відступ для аналогії.
Наприклад, я написав метод, який зчитує деякі дані ззовні, нехай з консолі. Але я не зробив перевірку вхідних даних і програма кожен раз падає коли користувач вводить невірні дані. Цей метод є баговим? Чи це залежить від того які дані ввів користувач? Я вважаю що цей метод баговий, тому що надіятись на те що користувач буде вводити завжди тільки вірні дані, м’яко кажучи, безглуздо. Цей метод баговий і точка. Тобто потенційно баговий = баговий, незалежно від того наскільки мала ймовірніть того що програма впаде.

Те ж саме і в проектуванні. Я - проектувальник, розробляю підсистему, інтерфейси класів, та взаємодію цих класів між собою. А кодувальник, який буде реалізовувати медоти класів - користувач проекту підсистеми. І надіятись що він зробить все  саме так як я собі запланував - теж безглуздо. Я не знаю наскільки адекватною буде людина що буде реалізовувати ці методи, хто саме буде це робити і коли теж не знаю.
Тому з моєї точки зору, якщо принцип потенціально може бути порушений, то він порушений взагалі :)

В даному випадку я згоден - якщо немає класу-потомка, нема й наслідування, нема й принципу, а значить і порушувати нічого. Але якщо тільки унаслідуватись від цього класу то принцип буде відразу ж порушений.

Наголошую ще раз, процеси проектування і кодування методів я розглядаю окремо один від одного.

12

Re: Принцип підстановки Лісков

Ні, не так. Якщо ви розробляєте для користувача, то маєте самі перевіряти всі його дії, бо можете їх передбачити. А якщо ви розробляєте для інших розробників того самого коду, то перевірити всі дії майбутніх розробників ви не можете за визначенням, бо вони можуть і внести правки у початковий код. Можна хіба що погодитися, що треба якось вираховувати поведінку віртуальних функцій і видавати попередження, якщо вона змінюється, але це теж не така легка справа - якщо ваш приклад з виключними ситуаціями можна було легко попередити, додавши noexcept до першого визначення, то приклад на хабрахабрі не відловлюється взагалі, бо потребує знання про зовнішню сутність.

13

Re: Принцип підстановки Лісков

Я вважаю що оскільки визначення відносин між класами, в тому числі і відносин наслідування, відбуваються на етапі проектування, тому і аналіз додержання чи не додержання цього принципу повинен теж відбуватись на етапі проектування.

Прихований текст

Для спрощення давайте вважати що проектування процесс не ітеративний, або нехай всього з однією ітерацією.

koala написав:

Тут питання не в теоретичній відповідності всіх можливих розширень програми, а в тому, чи відповідає йому конкретна повністю написана програма.

Якщо вам не важко, то чи не могли б ви подивитись статтю, на хабрі і сказати чи порушує вказаний там приклад принцип підстановки Лісків і чому? Там простенька стаття та простенький приклад.
http://habrahabr.ru/post/83269/

14

Re: Принцип підстановки Лісков

koala написав:

то перевірити всі дії майбутніх розробників ви не можете за визначенням, бо вони можуть і внести правки у початковий код.

Можу, бо:
  1) коли я пишу базовий клас з невіртуальними методами я розраховую на те що ці методи не будуть переозначені в класі-потомку. Поведінка цих методів в класі-потомку не буде змінена і принцип буде додержаний.
  2) коли я пишу базовий клас з віртуальними функціями то я розраховую що ці функції будуть, чи можуть бути переозначені в класі-потомку. Поведінка цим методів в класі-потомку може бути змінена, тобто принцип не додержується.
  3) коли кодувальник вносить правки в початковий код, наприклад змінює реалізацію методів базового класу з п.1, то поведінка методів залишиться однаковою відносно базового класу та його потомка, а отже принцип всеодно буде додержаний.

Якщо ж кодувальник "переозначує" непереозначувані методи в класі-потомку то він ССЗБ. За таке можна і лінійкою по рукам отримати :)

Якщо кодувальник змінює інтерфейси методів, то це вже зовсім інше питання, тут може бути все що завгодно.

15

Re: Принцип підстановки Лісков

Я не розумію, чого ви хочете - в статті все описано. Там останній розробник змінив гетер і сетер на віртуальні, до речі. Але проблема не в цьому, а в тому, що він змінив неочевидний, але задіяний принцип роботи класу - в старому класі змінна ініціалізувалася до (і окремо від) ініціалізації обладнання, в новому - береться з обладнання.
Ну і невіртуальні методи порушують парадигму ООП, але то вже інше питання.
А забороняти майбутнім розробникам будь-що змінювати "бо можуть поламати" - безглуздо.

Подякували: Arete1

16

Re: Принцип підстановки Лісков

koala, quez, щиро дякую вам за роз’яснення.