Russian Qt Forum
Ноябрь 23, 2024, 11:30 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: Базовый класс  (Прочитано 8801 раз)
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« : Июнь 27, 2013, 11:46 »

Добрый день

Ситуация типичная. Есть абстрактный базовый класс, все вполне "академично"
Код
C++ (Qt)
class taskScheduler {
public:
 virtual void start( void ) = 0;
 virtual void stop( void ) = 0;
 virtual void addTask( void ) = 0;
 ...
};
 
И есть порожденный класс
Код
C++ (Qt)
class defautrScheduler : public taskScheduler {
 void start( void );
 void stop( void );
 void addTask( void );
 ...
};
 
Ну разумеется все пользующие видят taskScheduler *, а реально создается defautrScheduler. Ладно, но вот выяснилось что нужна ф-циональность checkForAbort - куда ее лепить? Я пошел по пути наименьшего сопротивления - расширил базовый класс. Оправдаться очень легко - является ли проверка на отмену (checkForAbort ) "неотъемлемой" частью taskScheduler? Да, конечно! (ну а почему нет?). Но все же у меня смутное чувство недовольства. Эдак что угодно можно признать "совершенно необходимым" и слить в базовый класс, который очень скоро превратится в хз что. 

Мнения? (понимаю что мой пост может "слишком субъективен")

Спасибо
Записан
Пантер
Administrator
Джедай : наставник для всех
*****
Offline Offline

Сообщений: 5876


Жаждущий знаний


Просмотр профиля WWW
« Ответ #1 : Июнь 27, 2013, 11:54 »

Если метод является "неотъемлемой" частью, то нет причины его не добавить. В других случаях можно использовать конкретное подходящее решение (есть несколько паттернов).
Записан

1. Qt - Qt Development Frameworks; QT - QuickTime
2. Не используйте в исходниках символы кириллицы!!!
3. Пользуйтесь тегом code при оформлении сообщений.
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #2 : Июнь 27, 2013, 12:01 »

Если метод является "неотъемлемой" частью, ...
А как определить является ли? Это можно (с успехом) утверждать и подогнать под это все что угодно.

В других случаях можно использовать конкретное подходящее решение (есть несколько паттернов).
Прошу показать на примере того же ф-ционала (юзверь отменяет операцию)
Записан
Пантер
Administrator
Джедай : наставник для всех
*****
Offline Offline

Сообщений: 5876


Жаждущий знаний


Просмотр профиля WWW
« Ответ #3 : Июнь 27, 2013, 12:19 »

Определить можно логически подумав. Улыбающийся

Допустим, есть Декоратор
Записан

1. Qt - Qt Development Frameworks; QT - QuickTime
2. Не используйте в исходниках символы кириллицы!!!
3. Пользуйтесь тегом code при оформлении сообщений.
_Bers
Бывалый
*****
Offline Offline

Сообщений: 486


Просмотр профиля
« Ответ #4 : Июнь 27, 2013, 17:40 »

Ну разумеется все пользующие видят taskScheduler *, а реально создается defautrScheduler. Ладно, но вот выяснилось что нужна ф-циональность checkForAbort - куда ее лепить? Я пошел по пути наименьшего сопротивления - расширил базовый класс. Оправдаться очень легко - является ли проверка на отмену (checkForAbort ) "неотъемлемой" частью taskScheduler? Да, конечно! (ну а почему нет?). Но все же у меня смутное чувство недовольства. Эдак что угодно можно признать "совершенно необходимым" и слить в базовый класс, который очень скоро превратится в хз что. 

Мнения? (понимаю что мой пост может "слишком субъективен")
Спасибо

Сама по себе формулировка "выяснилось что нужна ф-циональность checkForAbort" слишком расплывчатая.
Видимо, вы сами не вполне осознаете, кому и зачем понадобился этот checkForAbort.

На самом деле все просто: база хранит только общее для всех потомков.
Если checkForAbort нужен всем - тогда в базу.
Если одному - только этому одному потомку.
Если части наследников - значит нужно сделать дополнительный интерфейс, и наследовать от базы и от дополнительного интерфейса тех, кому этот функционал действительно нужен.

На плюсах оч хорошо действует правило: "то, чего быть не должно - не должно быть в принципе".
Смысл в том, что бы сделать так, что бы этого метода не было у тех классов, кому этот метод не нужен.


Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #5 : Июнь 28, 2013, 08:45 »

Сама по себе формулировка "выяснилось что нужна ф-циональность checkForAbort" слишком расплывчатая.
Видимо, вы сами не вполне осознаете, кому и зачем понадобился этот checkForAbort.
Очень даже хорошо представляю. Классы что я привел взяты из open-source. Подключил в свой код - все норм, работает. Однако расчеты могут длиться десятки секунд - оставить пользователя "с замерзшим UI" я не могу. И отменить операцию он имеет право. При этом scheduler должен свернуть все задачи и дождаться завершения всех запущенных ниток.

На самом деле все просто: база хранит только общее для всех потомков.
Если checkForAbort нужен всем - тогда в базу.
Если одному - только этому одному потомку.
Мне кажется лучше исходить из того что "все здесь достаточно грамотны" - и не нужно объяснять то что всем известно Улыбающийся Как уже было дважды сказано - легко и просто объявить, "нужен всем", мол, базовое свойство. Ведь это наиболее легкий путь реализации. Но так ли это на самом деле? Посмотрим во что вылилось расширение
Код
C++ (Qt)
class taskScheduler {
public:
 virtual void start( void ) = 0;
 virtual void stop( void ) = 0;
 virtual void addTask( void ) = 0;
 ...
 typedef bool (*abortProcCallback)( int percent );
 
private:
 abortProcCallback abortProc;
 bool isAborted;
 
public:
 void setAbortProc( abortProcCallback );
 bool isAborted( void ) const;
};
Возможно переменные перенести в порожденный класс, а новые методы объявить pure virtual - ну не вижу что это здесь особо меняет. В любом случае базовый класс заметно растерял свою общность/абстрактность.

На плюсах оч хорошо действует правило: "то, чего быть не должно - не должно быть в принципе".
Смысл в том, что бы сделать так, что бы этого метода не было у тех классов, кому этот метод не нужен.
Да, в принципе возможен такой потомок у которого нет feedback'а с юзером. Хотя пока таких нет и вероятность этого мала. И что? Если не расширять базовый, как тогда др клиенту достучаться до интерфейса abort? Хорошо известно что следует за гордыми утверждениями - что-то типа "дальше надо смотреть по задаче" (вильнул хвостиком - и все Улыбающийся). А вот задача дана - смотрите, решайте.
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4350



Просмотр профиля
« Ответ #6 : Июнь 28, 2013, 11:39 »

Вы никогда не задумывались, почему Шолохов описывает природу не так как Толстой?
Сейчас, вы пытаетесь выяснить, как правильно описывать природу. Есть ли ответ на этот вопрос... думаю что нет.

Посмотрим во что вылилось расширение
Для чего в интерфейсе - реализация? Она должна быть в defaultSheduler:
Код
C++ (Qt)
class taskScheduler {
public:
 virtual void start( void ) = 0;
 virtual void stop( void ) = 0;
 virtual void addTask( void ) = 0;
 ...
 typedef bool (*abortProcCallback)( int percent );
 
 void setAbortProc( abortProcCallback ) = 0;
 bool isAborted( void ) const = 0;
};
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #7 : Июнь 28, 2013, 12:10 »

Вы никогда не задумывались, почему Шолохов описывает природу не так как Толстой?
Сейчас, вы пытаетесь выяснить, как правильно описывать природу. Есть ли ответ на этот вопрос... думаю что нет.
А я и не спрашивал "правельный ответ", а предложил поделиться мнениями  Улыбающийся

Для чего в интерфейсе - реализация? Она должна быть в defaultSheduler:
Ну можно и вообще в духе паттерна "обстервер" (to make theoretics happy)
Код
C++ (Qt)
class taskScheduler {
public:
...
 void setAborter( CAborter * ) = 0;
 CAborter * getAborter( void ) = 0;
};
Но это "наведение песиков" никак не меняет ф-ционал и ничего не обобщает.
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4350



Просмотр профиля
« Ответ #8 : Июнь 28, 2013, 12:16 »

Ну можно и вообще в духе паттерна "обстервер" (to make theoretics happy)
Можно. Все можно. Главное, что бы вас это устраивало.
Записан
_Bers
Бывалый
*****
Offline Offline

Сообщений: 486


Просмотр профиля
« Ответ #9 : Июнь 28, 2013, 16:54 »

Очень даже хорошо представляю.

Тогда что именно не понятно?

Как уже было дважды сказано - легко и просто объявить, "нужен всем", мол, базовое свойство. Ведь это наиболее легкий путь реализации. Но так ли это на самом деле?

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


Да, в принципе возможен такой потомок у которого нет feedback'а с юзером. Хотя пока таких нет и вероятность этого мала. И что?

Если 9 из 10 актуально эксплуатируют метод базы, а 1 из 10 его не использует, то по опыту могу сказать, что в этом 1 из 10 отсутствует здравый смысл использовать такую функцию.

Я в таких случаях говорю так: "а для тебя эта функция означает ничего не деланье".
Я бы добавил метод в интерфейс, который по дефолту ничего не делает. И переопределял бы его везде, где это необходимо.
Записан
popper
Гость
« Ответ #10 : Июнь 28, 2013, 17:20 »

На мой взгляд, здесь подходит трехуровневая иерархия:
Код
C++ (Qt)
taskScheduler
abortedTaskScheduler
concreteTaskScheduler
 

Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #11 : Июнь 28, 2013, 17:43 »

На мой взгляд, здесь подходит трехуровневая иерархия:
Код
C++ (Qt)
taskScheduler
abortedTaskScheduler
concreteTaskScheduler
 
Ну выделять это в класс мне явно не хочется - это скорее "свойство" а не новая "сущность"

Тогда что именно не понятно?
А можно не так резво? Улыбающийся Не все темы создаются теми кому "непонятно", иногда интересно услышать мнение других о проблеме.

Если 9 из 10 актуально эксплуатируют метод базы, а 1 из 10 его не использует, то по опыту могу сказать, что в этом 1 из 10 отсутствует здравый смысл использовать такую функцию.

Я в таких случаях говорю так: "а для тебя эта функция означает ничего не деланье".
Я бы добавил метод в интерфейс, который по дефолту ничего не делает. И переопределял бы его везде, где это необходимо.
Когда создается класс довольно трудно предсказать его эволюцию. Сегодня 9/10 пользуют - не аргумент. Напр загрузчик/обработчик имеджей. Некоторые имеют альфа-канал, др нет. Должны ли альфа-методы быть в базовом классе? Если нет, то какой механизм взамен?

Ну то еще цветочки, о существовании альфы всем известно. А вот появились имеджи у которых значения по каналу float и могут быть напр мульен. Разумеется появилась серия методов чисто для них. Где они должны быть?
 
Записан
_Bers
Бывалый
*****
Offline Offline

Сообщений: 486


Просмотр профиля
« Ответ #12 : Июнь 28, 2013, 20:59 »

А можно не так резво? Улыбающийся Не все темы создаются теми кому "непонятно", иногда интересно услышать мнение других о проблеме.

Есть два хороших фактора при разработке:
1. Делать так, как сделать проще и быстрее.
2. Делать так, что бы на сопровождении не отгребсти проблем.

Если 9 из 10 возможных наследников захотят иметь такой метод, а 1 из 10 не нуждается, но и не против, то очевидно, что добавление такого метода в базовый интерфейс - это самое просто и быстрое решение, которое не создает проблем.

Когда создается класс довольно трудно предсказать его эволюцию. Сегодня 9/10 пользуют - не аргумент. Напр загрузчик/обработчик имеджей. Некоторые имеют альфа-канал, др нет. Должны ли альфа-методы быть в базовом классе? Если нет, то какой механизм взамен?

При разработке класса не нужно думать о его возможной эволюции вообще.

Все, о чем стоит думать в момент создания класса:
1. Дизайн использования (как это работает на практике)
2. Требования к механизму (условия эксплуатации)
3. Гарантии рабостопособности и стабильности.

Ваш пример можно перефразировать:

Механизм ImageProcess, должен уметь обрабатывать картинки с альфа-каналом, при этом, картинки могут быть как с альфа-каналом, так и без него. Таким то, и таким то образом.

Лично я:

1. Вообще не стал бы городить тут интрфейсов, ибо по задаче в этом нет необходимости.
2. Дизайн проект. Как пользователи хотя бы приблизительно хотели бы это видеть?

Допустим, действия наиболее частые и характерные для любого множества картинок я бы определил в жесткий "стандартный интерфейс" - в набор паблик методов ImageProcess:

ImageProcess ip("Example.png"); //любое множество картинок умеет загружаться с диска

ip.ScaleXY(100,100); //любое множество картинок можно масштабировать

//некий специфичный обработчик. Умеет делать узко-специализированные действия над буфером цветов картинки
AlphaSpecific as;

ip.Work(as); //AlphaSpecific знает о существовании ImageProcess. Вытряхивает из него нужные данные и работает.

Мы обеспечиваем удобные для пользователя интуитивно понятные методы работы с наиболее распространенными вариантами.
Эти методы должны перекрыть порядка 95% использования.

И предоставляем пользователям способ, как удобно организовать оставшиеся 5% специфических обработок:
Пользователь порождает от ISpecific класс обработки, и кормит своего потомка ImageProcess::Work(ISpecific*);


Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #13 : Июнь 29, 2013, 10:12 »

Если 9 из 10 возможных наследников захотят иметь такой метод, а 1 из 10 не нуждается, но и не против, то очевидно, что добавление такого метода в базовый интерфейс - это самое просто и быстрое решение, которое не создает проблем.
Именно так и происходит в моей практике, наверное и в практике других тоже. Однако начав расширять базовый класс - остановиться очень трудно. Напр некоторые форматы имеют background color - прозрачные области показываются не черным а этим цветом, его можно менять. Нет, таких явно не большинство, скорее наоборот, пусть 1/10. Но будете ли Вы с этим возиться? Ведь гораздо проще уговорить себя "это никому не мешает" - и слить в базовый класс. В результате в него все льют и льют. В результате раздутый базовый класс "на все случаи жизни"
Записан
_Bers
Бывалый
*****
Offline Offline

Сообщений: 486


Просмотр профиля
« Ответ #14 : Июнь 29, 2013, 15:05 »

Если 9 из 10 возможных наследников захотят иметь такой метод, а 1 из 10 не нуждается, но и не против, то очевидно, что добавление такого метода в базовый интерфейс - это самое просто и быстрое решение, которое не создает проблем.
Именно так и происходит в моей практике, наверное и в практике других тоже. Однако начав расширять базовый класс - остановиться очень трудно. Напр некоторые форматы имеют background color - прозрачные области показываются не черным а этим цветом, его можно менять. Нет, таких явно не большинство, скорее наоборот, пусть 1/10. Но будете ли Вы с этим возиться? Ведь гораздо проще уговорить себя "это никому не мешает" - и слить в базовый класс. В результате в него все льют и льют. В результате раздутый базовый класс "на все случаи жизни"

Для этого и существует документ "требования к механизму".
Одну и ту же задачу можно решить десятком различных способов. Какой из них выбрать?
Тут даже думать не нужно: самое простое в плане реализации, которое удовлетворяет требованиям к механизму.

У меня был случай на практике: класс Picture. Содержал в себе весь джентельменский набор для тех самых 95% случаев использования: масштабирование, угол поворота, прозрачность по альфе, и тп вещи.

Моя первая реакция: мне угнетало такое большое количество разных методов в одном классе. Какой-то божественный класс.
Я ещё тогда подумал: а как можно сократить количество паблик-методов, не потеряв удобства использования?

Ну так вот: никак. Как бы вы ни выкручивались, по сути вы просто просто перетаскиваете код из одного места в другое:

Код:
//самый простой и очевидный ОО-способ. Удобно для 95% случаев
//Но не позволяет изменять алгоритм по которому выполняется масштабирование
img.Scale(param);

//процедурный стиль. Увеличивает сложность проекта из-за отрыва данных от их обработчиков.
Scale(img, param);

// паттерн "стратегия".
// Позволяет использовать собственный алгоритм обработки,
//без необходимости знать строение Image
img.Process( Scale(param) );

То есть, если функционал Scale стабильно часто требуется пользователям, то его в любом случае придется реализовать.
Вопрос только: где он будет?

Если пользователям не нужно никакие собственные алгоритмы втюхивать, а нужно только максимально просто взять и сразу использовать: то всякого рода стратегии и процедурщина только усложнят код на ровном месте.

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

Вот если в ходе развития проекта, вдруг потребовалась возможность заменять алгоритм обработки на свой собственный: вот только тогда, по мере поступления проблемы, можно уже в середине разработки воткнуть метод принимающий стратегию.


Итого:
1. Проблемы нужно решать по мере их наступления. А не пытаться заранее решать ещё не существующие проблемы.
2. Делать нужно так, как можно сделать проще и быстрее.
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.216 секунд. Запросов: 23.