|
|
C нами с 09.06.2005 Репутация: 548.8
|
|
Petro, если вместо того, чтобы задействовать эти три идеи, мы начинаем прикручивать некие суррогаты, то это уже не ООП, а онанизм какой-то получается. Но если их (идеи) можно применить и избежать применения костылей, то почему бы это не сделать? Здравый смысл тут простой: надо делать все в разумные сроки, т.е. стоимость человеко-часов разработки не должна превышать сумму, которую платит заказчик за продукт (а не за идеологию его построения, кстати!).
Хотя, надо все-таки не доводить дело до абсурда и фанатизма, chaser описал вполне жизненную ситуацию (причем, регулярно возникающую). А если все пытаться свести к "чистому" ООП, то результата (продукта) можно и не достигнуть никогда или достигнуть в сроки, за которые конкуренты окучат весь рынок, и остаться с пустым карманом.
|
|
|
|
|
|
|
|
Возраст: 35 C нами с 04.02.2005 Репутация: 121.3
|
|
Petro, неважно, на каком языке. Просто это кусок кода, оторванный от контекста, но больше всего он похож на "старое доброе структурное программирование". Массив из структур это еще не ООП
Можно, например, написать такой код:
/* Яблоко, возможно сегментированное */
class Apple
{
public:
unsigned parts; // количество кусочков
float part_weight; // вес одного кусочка
};
// далее где-то в коде
void f()
{
Apple *a = new Apple;
a->parts = 1; // целое яблоко
a->part_weight = 150; //весом в 150 грамм
//... тут что-то делается ...
if (1 == a->parts) //если яблоко целое...
{
a->parts = 4; //разрезать на 4 равных кусочка
a->part_weight /= 4;
}
//...
// Если голоден и яблоко еще осталось, съесть кусочек
if (hungry && a->parts)
if (!(--a->parts)) // если доели яблоко...
{
delete a; //удалить его
a = 0;
}
//...
}
|
Но на ООП это не тянет. Дело в том, что объект должен быть не просто упорядоченным набором данных — он должен проявлять некие способности. Именно этим он отличается от структуры. Например, в данном примере следовало бы реализовать у яблока, как минимум, методы "разрезать на n частей" и "взять кусочек", а также конструктор. И вообще, у объекта не должно быть открытых данных-членов, только если это не примитивный POD-тип.
|
|
|
|
|
|
|
|
C нами с 25.01.2005 Репутация: 97.3
|
|
как выбрать яблоко среди прочих фруктов ;)
вариант1: шаблон не знаю, наверно, template method
в интерфейс фрукта добавляется метод getFruitLikeThis (извлечь похожий фрукт)
class Fruit
public:
Fruit* getFruitLikeThis(FruitContainer* list){
Fruit* item;
while(item = list->getNextFruit())
if(item->getType() == this->getType()) return item;
return 0;
}
private:
// Type - что угодно, что можно сравнить
virtual Type getType() = 0;
|
клиент использует так
FruitContainer* basket = new FruitContainer;
// ... заполнение контейнера
// образцы для извлечения
Fruit* apple = new Apple;
Fruit* orange = new Orange;
// 2 яблока из корзинки
Fruit* firstAppleFromContainer = apple.getFruitLikeThis(basket);
Fruit* secondAppleFromContainer = apple.getFruitLikeThis(basket);
// апельсин из корзинки
Fruit* orangeFromContainer = orange.getFruitLikeThis(basket);
|
клиент имеет возможность взять фрукт конкретного типа из контейнера
клиент не знает о том, что есть метод getType()
почему яблоко должно уметь выбирать себя их контейнера?
по той же причине, что треугольник умеет рисовать себя на экране ;)
|
|
|
|
|
|
|
|
Возраст: 36 C нами с 22.01.2006 Репутация: 256.8
|
|
Madlax, ну тогда можно и наоборот - сунуть корзине пример и сказать давай мне такой же, т.е. сделать это заботой корзины.
|
|
|
|
|
|
|
|
C нами с 25.01.2005 Репутация: 97.3
|
|
splav, я тоже склоняюсь к тому, что в данном случае лучше сунуть яблоко корзине в качестве образца ;)
первый вариант походит, когда разные фрукты нужно брать из корзинки _по-разному_
еще вариант2: шаблон Visitor
в интерфейс фрукта добавляется виртуальный метод acceptVisitor(FruitVisitor*)
class Fruit
public:
virtual void acceptVisitor(FruitVisitor*) = 0;
|
добавляется класс FruitVisitor, который обладает методами для обработки каждого типа фрукта
class FruitVisitor
public:
virtual void visitApple(Fruit*);
virtual void visitOrange(Fruit*);
|
сколько типов фруктов, столько методов
это большой минус, т.к. нужно знать все типы фруктов заранее
от абстрактного FruitVisitor наследуются конкретные посетители для решания разных задач
один конктерный тип посетителя - одна задача
class AppleCollector : public FruitVisitor
public:
void visitApple(Fruit* fr){
basket->addFruit(fr);
}
void visitOrange(Fruit* fr){
// ничего собирать не нужно
}
FruitContainer* getCollectedFruits();
private:
FruitContainer* basket;
|
аналогично для апельсинов
еще один большой минус, - число классов сборщиков совпадает с числом типов фруктов
class OrangeCollector : public FruitVisitor
public:
void visitApple(Fruit* fr){
// ничего собирать не нужно
}
void visitOrange(Fruit* fr){
basket->addFruit(fr);
}
FruitContainer* getCollectedFruits();
private:
FruitContainer* basket;
|
для конкретного типа фрукта определяется метод acceptVisitor
название вызываемого метод должно согласовываться с названием класса
class Apple -> visitApple
class Apple
void acceptVisitor(FruitVisitor* visitor){
visitor->visitApple(this);
}
|
клиент использует так
FruitContainer* basket = new FruitContainer;
// ... заполнение контейнера
FruitVisitor* ac = new AppleCollector;
Fruit* item;
while(item = basket->getNextFruit()){
item->acceptVisitor(ac);
}
// из коллектора яблок достаются все собранные яблоки
FruitContainer* collectedApples = ac->getCollectedFruits();
|
этот шаблон очень сильный, т.к. позволяет расписать решение разных задач для структурированной коллекции объектов
|
|
|
|
|
|
|
|
Возраст: 35 C нами с 04.02.2005 Репутация: 121.3
|
|
Проблема вот появилась. Далее предполагается, что используется язык C++.
Есть иерархия контейнеров с абстрактным базовым классом Container, способных хранить объекты одного и того же класса Thing или производных от него.
Так вот, для этих контейнеров нужны однонаправленные итераторы. Ясно, что у разных контейнеров итераторы внутри могут быть устроены совершенно по-разному.
Однако итераторы должны реализовывать общий интерфейс, как и контейнеры. То есть необходима возможность обойти с помощью итератора все элементы контейнера, имея указатель типа Container*.
Функция Container::begin() должна возвращать итератор на начало последовательности. Получается, что сам итератор не может быть полиморфным — мы должны возвращать объект, а не выделенный указатель.
Тогда напрашивается следующее решение. Описывается абстрактный класс iterator_rep (от слова representation), в котором объявляются функции next, equal и get для получения следующего итеатора, сравнения двух итераторов и получения указателя на Thing соответственно.
Класс iterator содержит в себе указатель [или std::auto_ptr] на iterator_rep, и операции ++, ==, *, -> реализуются через соответствующие функции хранимого iterator_rep.
Казалось бы, все хорошо, однако при создании нового итератора приходится динамически выделять память под этот самый iterator_rep (а потом еще освобождать память в деструкторе). Дело в том, что такие контейнеры будут обходиться крайне часто, поэтому эти постоянные выделения-освобождения не очень хорошо скажутся на производительности программы.
Можно предложить решение в стиле C. Класс итератора остается неполиморфным, однако теперь содержит в себе некий указатель void *data. Кроме того, итератор объявлен friend'ом класса Container. Интерфейс, который в предыдущем случае предоставлял класс iterator_rep, теперь будет предоставлять сам Container. Функции next, equal и get будут пользоваться этим самым указателем data, преобразовывая его к нужному типу. Например, если контейнер представляет собой список, data будет указывать на звено списка; если массив — просто на элемент массива. Недостатки тут очевидны, думаю.
Можно, конечно, отказаться от итераторов и объявить в Container'е виртуальную функцию for_each_child, вызывающую переданную ей "callback"-функцию или функциональный объект для каждого своего элемента. Снова есть несколько недостатков. Во-первых, придется реализовывать функции, которые часто будут состоять из одной-двух строк и затруднять чтение программы. Во-вторых, есть более неприятный недостаток. Предположим, что классу Thing в одной из его функций нужно просмотреть элементы некоторого контейнера и для каждого из них вызвать некоторую защищенную функцию-член. С итераторами это не составляет никакой трудности:
for (Container::iterator i = c.begin(); c.end() != i; ++i)
{
i->some_method();
//..сделать что-то еще с *i
}
|
Однако "callback"-функция или функциональный объект не будет иметь доступа к защищенным членам класса Thing, поэтому придется объявлять все такие функции друзьями класса, что может привести к разрастанию списка друзей, чего крайне не хотелось бы. Да и не так гибок вообще этот способ, как итераторы.
В общем, не могу придумать решения, которое мне бы во всем понравилось. Может у кого-нибудь есть идеи?
|
|
|
|
|
|
|
|
C нами с 09.06.2005 Репутация: 548.8
|
|
chaser писал(а): |
Есть иерархия контейнеров с абстрактным базовым классом Container, способных хранить объекты одного и того же класса Thing или производных от него.
...
Функция Container::begin() должна возвращать итератор на начало последовательности. Получается, что сам итератор не может быть полиморфным — мы должны возвращать объект, а не выделенный указатель.
|
ИМХО это и есть источник всей головной боли. Если объекты в контейнере имеют общего предка, то зачем возвращать именно объект и терять полиморфность содержимого? Явно что-то непроработали!
|
|
|
|
|
|
|
|
Возраст: 35 C нами с 04.02.2005 Репутация: 121.3
|
|
Имеется в виду, что возвращаемый итератор должен быть объектом (внутри итератора конечно же лежит указатель на элемент контейнера). Если сделать его указателем, придется ведь возвращать указатель на динамически выделенную для итератора память, которую пользователь нашего итератора должен будет сам удалить. Это неудобно, противоречит принципам ООП и, кроме того, неусточиво по отношению к исключениям:
Container::iterator* i = c.begin();
f(); // f выкидывает исключение, i не будет уничтожен
delete i;
|
В варианте с классом iterator_rep мы как раз сохраняем полиморфизм, создавая для выделенного выделенного объекта "умную" оболочку iterator. Однако она мне не нравится благодаря этим самым выделениям памяти.
|
|
|
|
|
|
|
|
C нами с 09.06.2005 Репутация: 548.8
|
|
Как-то совсем нестандартно, надо сказать... Обычно бывет
Container::iterator i = c.begin();
i->doSmth(); // действо над объектом, указуемым итератором
f(*i); // передача объекта указуемого по ссылке
//...
|
т.е. используется сам итератор, а не указатель на него...
|
|
|
|
|
|
|
|
Возраст: 35 C нами с 04.02.2005 Репутация: 121.3
|
|
Так вот я про то и говорю, что используется сам итератор
То есть мы не можем сам итератор сделать полиморфным (содержащим виртуальные функции).
|
|
|
|
|
|
|
|
Возраст: 35 C нами с 04.02.2005 Репутация: 121.3
|
|
В общем, в итоге я отказался от полиморфных контейнеров — не лучшая была идея, надо сказать.
|
|
|
|
|
|
|
|
C нами с 15.07.2005 Репутация: 133.9
|
|
Почему объектно-ориентированное программирование провалилось?
link
|
_____________________________ С дивана видно всё, ты так и знай!
|
|
|
|
|
|
|
C нами с 16.11.2006 Репутация: 455.1
|
|
Собственно, вся суть Прям в точку
|
|
|
|
|
|
|
|
|