Дополнение чужого функционала

velkin velkin
Предположим имеется сторонняя библиотека (пусть будет C++), то есть написанная другими людьми, и в ней от 100 до 1000 классов (парадигма ООП). Причём это не столь важно, теоретически может быть 10, а может и 10000. Далее каждый класс содержит некую функциональность, которую хотелось бы расширить. При этом каждый расширенный класс по функционалу не просто содержит, а именно является тем самым классом из сторонней библиотеки.

В этом случае мне на ум приходят два варианта:
1) Создаём производный класс и уже в нём проводим нужные изменения
2) Изменяем классы в сторонней библиотеке

Если подумать, то недостаток первого варианта это само наследование. Речь даже не о скоростных характеристиках, а просто о том, что в сторонней библиотеке могут быть установлены закрытые (private) модификаторы доступа. Опять же происходит полное дублирование классов, в своей программе придётся использовать только производные и тщательно за этим следить.

Изменение же сторонней библиотеки в случае её изменения от версии к версии повлечёт за собой изменения уже изменённых классов, то есть процесс обновления будет не сказать, чтобы очень простым. При этом есть вероятность поломать чужой функционал. И в отличие от первого варианта, чем больше изменений произведено, тем сложнее потом будет обновляться до новых версий, не говоря уже о перекомпиляции чужой библиотеки.

В принципе и всё, интересно было бы услышать идеи на этот счёт, хотя вопрос скорее теоретический об эволюции ПО и отказа от повторного изобретения велосипедов.
LaptevVV
LaptevVV
08.01.2017 06:39
Композицию и агрегацию можно вместо наследования.
velkin
velkin
08.01.2017 09:17
Здравствуйте, LaptevVV, Вы писали:

LVV>Композицию и агрегацию можно вместо наследования.


V>Предположим имеется сторонняя библиотека (пусть будет C++), то есть написанная другими людьми, и в ней от 100 до 1000 классов (парадигма ООП). Причём это не столь важно, теоретически может быть 10, а может и 10000. Далее каждый класс содержит некую функциональность, которую хотелось бы расширить. При этом каждый расширенный класс по функционалу не просто содержит, а именно является тем самым классом из сторонней библиотеки.


Написал чтобы не возникало вопросов про отношение содержит, коим является агрегирование. А вот является это именно наследование. C моей точки зрения агрегирование в данном случае гораздо хуже, чем открытое наследование, так как проблему модификаторов доступа чужих классов не решает, но накладывает ограничение на использование членов класса. Собственно говоря с агрегированием получится ещё более сложная архитектура. Но если есть какие-то мысли, то интересно было бы послушать.
LaptevVV
LaptevVV
08.01.2017 01:42
LVV>>Композицию и агрегацию можно вместо наследования.
V>>Предположим имеется сторонняя библиотека (пусть будет C++), то есть написанная другими людьми, и в ней от 100 до 1000 классов (парадигма ООП). Причём это не столь важно, теоретически может быть 10, а может и 10000. Далее каждый класс содержит некую функциональность, которую хотелось бы расширить. При этом каждый расширенный класс по функционалу не просто содержит, а именно является тем самым классом из сторонней библиотеки.
V>Написал чтобы не возникало вопросов про отношение содержит, коим является агрегирование. А вот является это именно наследование. C моей точки зрения агрегирование в данном случае гораздо хуже, чем открытое наследование, так как проблему модификаторов доступа чужих классов не решает, но накладывает ограничение на использование членов класса. Собственно говоря с агрегированием получится ещё более сложная архитектура. Но если есть какие-то мысли, то интересно было бы послушать.
Собственно, я подумал, что ОБЫЧНО библиотечные классы не предназначены для наследования.
Если они разрабатывались именно с учетом будущего наследования, то вопросов нет — надо наследовать.
А если нет?
Вот жеж STL никак для наследования не предназначена.
А написать обертку при необходимости с использованием композиции — как 2 байта переслать.
Кроме того, вопросы вызывает потребность изменения модификаторов доступа.
Ведь разработчик не от былды расставил модификаторы.
А вы хотите его модель доступа порушить?
Нафига это нужно?
Sharov
Sharov
08.01.2017 11:35
Здравствуйте, LaptevVV, Вы писали:

LVV>Композицию и агрегацию можно вместо наследования.


Паттерн decorator наше всё.
ylem
ylem
08.01.2017 01:44
Наследование тоже много не даст, если классы изначально для этого не были предназначены. Фактически получите не многим больше, чем то, что в C# можно получить extension-методами.
То, чего ими нельзя получить ("расширить состояние" объектов), не уверен, что вообще стоит делать.
Shtole
Shtole
12.07.2021 08:35
Здравствуйте, velkin, Вы писали:

На главной странице написано, что заметке 4 месяца. В тексте заметки — что она 2017 года. Короче, обвинения в некропостинге не принимаются.

V>Предположим имеется сторонняя библиотека (пусть будет C++), то есть написанная другими людьми, и в ней от 100 до 1000 классов (парадигма ООП). Причём это не столь важно, теоретически может быть 10, а может и 10000. Далее каждый класс содержит некую функциональность, которую хотелось бы расширить. При этом каждый расширенный класс по функционалу не просто содержит, а именно является тем самым классом из сторонней библиотеки.


V>В этом случае мне на ум приходят два варианта:

V>1) Создаём производный класс и уже в нём проводим нужные изменения
V>2) Изменяем классы в сторонней библиотеке

V>Если подумать, то недостаток первого варианта это само наследование. Речь даже не о скоростных характеристиках, а просто о том, что в сторонней библиотеке могут быть установлены закрытые (private) модификаторы доступа. Опять же происходит полное дублирование классов, в своей программе придётся использовать только производные и тщательно за этим следить.


Если я правильно понимаю, что значат слова «каждый расширенный класс по функционалу не просто содержит, а именно является тем самым классом из сторонней библиотеки», наследование приведёт к тому, что структура кода вступит в противоречие с описываемой им реальностью. Лично я считаю, что после этого код становится плохо поддерживаемым, в частности, незнакомый с ним человек будет долго тупить, зачем там класс-родитель и класс-потомок, ответ придётся прописывать в комментариях, конфлюэнсе или передавать изустно и т.п.

V>Изменение же сторонней библиотеки в случае её изменения от версии к версии повлечёт за собой изменения уже изменённых классов, то есть процесс обновления будет не сказать, чтобы очень простым. При этом есть вероятность поломать чужой функционал. И в отличие от первого варианта, чем больше изменений произведено, тем сложнее потом будет обновляться до новых версий, не говоря уже о перекомпиляции чужой библиотеки.


Так делать, разумеется, тоже не стоит — любой будущий багфикс (со стороны автора библиотеки) будет иметь непредсказуемую стоимость интеграции. То есть, такое решение приведёт к росту рисков, а они должны уменьшаться.

V>В принципе и всё, интересно было бы услышать идеи на этот счёт, хотя вопрос скорее теоретический об эволюции ПО и отказа от повторного изобретения велосипедов.


Если я правильно понимаю, что значат слова «каждый расширенный класс по функционалу не просто содержит, а именно является тем самым классом из сторонней библиотеки», значит библиотека в текущем виде — сырая. Надо просто подождать новую версию или поискать другую, где уже всё написано.

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

В крайнем случае, пусть будут предусмотрены колбэки, которым предоставлен r/w-доступ ко всем необходимым кишкам.

Если ни того, ни другого не будет (и при этом налицо нехватка функционала, который, как подразумевается, должен быть без наследования) — я сто раз подумаю, прежде чем пользоваться такой библиотекой.

Кстати, это причина, по которой у меня аллергия на слово YAGNI.

>Речь даже не о скоростных характеристиках, а просто о том, что в сторонней библиотеке могут быть установлены закрытые (private) модификаторы доступа.


Вот про это хотелось бы сказать отдельно. Лично я считаю, что разработчик класса должен чётко думать над выбором private/protected исходя из того, хочет ли он заблокировать соответствующую кишку от (предполагаемого) автора класса-потомка. В соответствии с этой логикой, что значит «могут быть установлены»?! Если установлены — не трогай, это же не просто так сделано!

К моему большому удивлению, оказалось, что примерно половина программистов считает как я, а другая — тупо лепит по дефолту private, по мере необходимости меняя его на protected. Разумеется, я считаю, что это недостаточно времени потрачено на проектирование
varenikAA
varenikAA
13.07.2021 01:17
Здравствуйте, velkin, Вы писали:

V>Предположим имеется сторонняя библиотека (пусть будет C++), то есть написанная другими людьми, и в ней от 100 до 1000 классов (парадигма ООП). Причём это не столь важно, теоретически может быть 10, а может и 10000. Далее каждый класс содержит некую функциональность, которую хотелось бы расширить. При этом каждый расширенный класс по функционалу не просто содержит, а именно является тем самым классом из сторонней библиотеки.


Очевидно, если есть возможность договорится с авторами просто сделайте пулреквест с нужными правками.
Если код брошен и есть возможность — форкнуть и написать свою версию.
Далее по убывающей уже исходя из возможностей языка.
qaz77
qaz77
13.07.2021 06:56
Здравствуйте, velkin, Вы писали:
V>В этом случае мне на ум приходят два варианта:
V>1) Создаём производный класс и уже в нём проводим нужные изменения

Точки кастомизации в библиотеке могут быть организованы множеством способов:
политики, трейтсы, коллбеки, виртуальные функции.
Чисто абстрактные классы (интерфейсы) стоят особняком, само их введение намекает на реализацию пользователем библиотеки.
Если разработчики библиотеки решили использовать кастомизацию через виртуальные функции конкретных (не абстрактных) классов,
то наследование от библиотечных классов будет в рамках их замысла.
Такой подход использовался и в старых ООП библиотеках (OWL, MFC), и в Qt для виджетов применяется.
Например, в MFC можно было пользоваться библиотечным CEdit, а можно было сделать наследованный CMyCoolEdit со свистелками.

V>2) Изменяем классы в сторонней библиотеке

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