LINUX.ORG.RU

В чём суть ООП?


3

5

С точки зрения манеры проектирования, а не особенностей реализации С++ там CLOS. Вот например я использую определения классов и даже иногда наследование как узнать пишу ли я в ООП стиле?

★★★★★

Ответ на: комментарий от drBatty

И тут не надо думать. У тебя есть некий мифический FILE о в нутреннем устройстве которого тебее знать и не нужно. И который может меняться от версии libc к версии. А есть интерфейсы, которые закреплены в стандарте. Вот и все.

Dudraug ★★★★★ ()
Ответ на: комментарий от Dudraug

Но парадигма ООП не говорит, что каждый объект должен быть или унаслдован или иметь возможность наследовать, а лучше все вместе.

с этим я согласен, не говорит. С другой стороны, без наследования, у тебя не получится нормальной инкапсуляции и нормального полиморфизма (в C++). Хотя ты можешь конечно что-то накостылить шаблонами и дружественностью. Можешь и препроцессором тоже что-то изобразить. Но зачем?

Полиморфизм - это тоже понятие из ООП, но сущности в ООП языке не обязательно должны быть полиморфны.

ну все может и не должны, но какое-же ООП без полиморфизма? Может в теории это и возможно, но на практике такое ООП мне не нужно.

Наследование и полиморфизм - это отличительные черты ООП

ИМХО правы предыдущие ораторы, которые считают, что наследование не нужно. Я тоже считаю, что вообще в сферическом ООП не нужно. Но в C++ без наследования никак.

ООП - это подход к проектирвоанию, когда все сущности - объекты. Вот инкапсуляция ключевое тут понятие, да.

с этим я согласен, однако, в объектах мало практического смысла, если они не полиморфны. Ну например у нас есть утка, и нам без разницы, как к этой утке подойти, утка_летай() это и процедурная функция, и метод ООП, работает это при любом подходе, и разница в подходах работает не в сторону ООП - проще иметь процедуру утка_летай(), чем метод утка.летай(), конечно удобно инкапсулировать некоторые ненужные кишочки в «class утка», но это вопрос удобства. В С мы тоже можем что-то инкапсулировать в статические переменные файла «утка.c», и внутрь функции утка_летай() тоже можно инкапсулировать что угодно.

Совсем другое дело, когда у нас есть абстрактный класс «птица», в котором есть чистый виртуальный метод «летай()». Вот от него можно унаследовать класс «утка», и это будет очень удобно и совсем Ъ ООП.

drBatty ★★ ()
Ответ на: комментарий от Dudraug

И тут не надо думать. У тебя есть некий мифический FILE о в нутреннем устройстве которого тебее знать и не нужно. И который может меняться от версии libc к версии. А есть интерфейсы, которые закреплены в стандарте. Вот и все.

вот это:

/* знать не нужно */
struct FILE {
.....
};
/* знать нужно */
сложно назвать «инкапсуляцией». А полиморфизма нет вообще.

drBatty ★★ ()
Ответ на: комментарий от drBatty

ИМХО правы предыдущие ораторы, которые считают, что наследование не нужно.

Вообще наследование - реализация обобщения. А обобщения понятие через которое дается понятие полиморфизма. Я склолен считать так.

Dudraug ★★★★★ ()
Ответ на: комментарий от drBatty

Ну ты не по .h файлу смотри, а по доке на библиотеку.

Dudraug ★★★★★ ()
Ответ на: комментарий от drBatty

Совсем другое дело, когда у нас есть абстрактный класс «птица», в котором есть чистый виртуальный метод «летай()». Вот от него можно унаследовать класс «утка», и это будет очень удобно и совсем Ъ ООП.

Так оно понятно, так оно и есть, что это Ъ ООП. Никто не спорит. Просто ООП может быть еще и менее Ъ, но оставаться при этом ООП.

Dudraug ★★★★★ ()
Ответ на: комментарий от drBatty

А что, в пользовательских хедерах libc где-то прямо так и объявлена FILE?

AptGet ★★★ ()
Ответ на: комментарий от drBatty

сложно назвать «инкапсуляцией».

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

Begemoth ★★★★★ ()
Ответ на: комментарий от Dudraug

Вообще наследование - реализация обобщения. А обобщения понятие через которое дается понятие полиморфизма. Я склолен считать так.

наверное правильно. Но, IRL полиморфизм - нужен. И в C++ полиморфизм делается через наследование. Вывод - наследование нужно.

drBatty ★★ ()
Ответ на: комментарий от Dudraug

Ну ты не по .h файлу смотри, а по доке на библиотеку.

дока отправляет меня читать .h для лучшего понимания ;)

drBatty ★★ ()
Ответ на: комментарий от Dudraug

Так оно понятно, так оно и есть, что это Ъ ООП. Никто не спорит. Просто ООП может быть еще и менее Ъ, но оставаться при этом ООП.

ИМХО такой неЪ ООП не нужен и не даёт профита. В отличие от Ъ ООП, который профит даёт - не нужно реализовывать class птица, достаточно быстренько накостылить производный класс «утка». Возможно, в этом суть ООП.

drBatty ★★ ()
Ответ на: комментарий от drBatty

Кстати мы будем обсуждать вопрос по обработке ошибок? Просто из этого следует нужность ООП для стримов.

Dudraug ★★★★★ ()
Ответ на: комментарий от AptGet

А что, в пользовательских хедерах libc где-то прямо так и объявлена FILE?

да.

drBatty ★★ ()
Ответ на: комментарий от Begemoth

Если все операции принимают и возвращают FILE*

если-бы у бабушки был... Но определение FILE есть в твоём проекте. Если оно изменится, то ВСЁ придётся пересобирать.

drBatty ★★ ()
Ответ на: комментарий от Dudraug

Кстати мы будем обсуждать вопрос по обработке ошибок?

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

drBatty ★★ ()
Ответ на: комментарий от drBatty

Ок. Тогда сразу и для FILE привду

 FILE *is = fopen("file.txt", "r");
 if (3 != fscanf(is, "%d%d%d", &a, &b, c&){
    printf("error\n");
 }
  std::ifstream is("file.txt");
  
  is >> a >> b >> c;
  
  if (is.rdstate()){
    std::cout << "error" << std::endl;
  }
Dudraug ★★★★★ ()
Последнее исправление: Dudraug (всего исправлений: 2)
Ответ на: комментарий от drBatty

данная обёртка некошерна в смысле ООП

Всем по 5.1. Все юзают обобщенное программирование :)

slackwarrior ★★★★★ ()
Ответ на: комментарий от Boy_from_Jungle

Довольно таки удобно

удобно (относительно), но к ООП имеет крайне опосредованное отношение

jtootf ★★★★★ ()
Ответ на: комментарий от drBatty

звёздочка не нужна.

ты не нужен.

Если звездочка не нужна, может тогда подскажешь простой способ формирования в рантайме форматной C-style строки, которую можно будет передать в твою функцию? Т.е. вот есть мой код со звездочкой:

void print_precision(size_t precision, double value)
{
    printf("%.*f\n", precision, value);
}

/* ... */
size_t precision = get_precision();

print_precision(precision, .12345678);

Я жду твой эквивалентный код в формате:

void print_precision(const char *prc, double x)
{
    return printf(prc, x);
}

/* ... */
size_t precision = get_precision();
char *fmt;

/* Сюда вставь код, формирующий fmt на основе precision */

print_precision(fmt, .12345678);

А когда ты его мне предоставишь, прошу тебя самому сравнить, что использовать проще, удобнее и безопаснее.

theNamelessOne ★★★★★ ()
Ответ на: комментарий от theNamelessOne

Если звездочка не нужна, может тогда подскажешь простой способ формирования в рантайме форматной C-style строки, которую можно будет передать в твою функцию?

может для начала расскажешь, _зачем_ понадобилось передавать в функцию число символов в каком-то поле, да ещё и в рантайме? А то лично мне такая задача как-то не встречалась. Потому-то я маны на этот предмет и не рыл.

drBatty ★★ ()
Ответ на: комментарий от drBatty

А то лично мне такая задача как-то не встречалась.

А, ну это сразу доказывает ненужность, да.

может для начала расскажешь, _зачем_ понадобилось передавать в функцию число символов в каком-то поле, да ещё и в рантайме?

Как пример, который сразу же пришел в голову: формирование табличных данных, в которых размер поля становится известен на этапе выполнения программы (например, из конфига берется).

PS. Уровень твоей аргументации ошеломляет.

theNamelessOne ★★★★★ ()
Ответ на: комментарий от theNamelessOne

Как пример, который сразу же пришел в голову: формирование табличных данных, в которых размер поля становится известен на этапе выполнения программы (например, из конфига берется).

если-бы можно было задать спецификатор ширины типа «08» или «4.2», то в этом бы-бы смысл. Однако задать можно только целые числа вместо звёздочки. В итоге, тебе придётся парсить конфиг, выдирать оттуда строки с числами, потом выдёргивать эти числа и передавать их своей printf. По количеству и сложности кода это получится не намного больше и сложнее, чем если-бы ты просто вставил «4.2» в нужное место строки формата (например используя strcat(3)).

PS. Уровень твоей аргументации ошеломляет.

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

drBatty ★★ ()
Ответ на: комментарий от drBatty

если-бы можно было задать спецификатор ширины типа «08» или «4.2», то в этом бы-бы смысл.

printf("%*.*f\n", width, precision, value);
theNamelessOne ★★★★★ ()
Ответ на: комментарий от theNamelessOne

printf(«%*.*f

о чём и речь - тебе надо распарсить конфиг, выдернуть 4 из „4.2“, и оттуда же выдернуть 2. И потом вставить их вместо двух звёздочек. В итоге, вместо того, что-бы составить строчку в памяти, тебе придётся парсить строчку выдёргивая целые числа. То на то и выйдет.

drBatty ★★ ()
Ответ на: комментарий от drBatty

В итоге, вместо того, что-бы составить строчку в памяти, тебе придётся парсить строчку выдёргивая целые числа.

Лол, а чтобы составить строчку в памяти, конфиг парсить не нужно? Или необходимые значения мы из libastral-а возьмем? Или ты хочешь, чтобы в конфиге задавалась сама форматная строка?! И кто вообще сказал, что в конфиге значения будут заданы именно в таком формате, а не в следующем, например:

FieldWidth: 4
# skipped lines
Precision: 2

А если значение будет браться не из конфига, а вычисляться в рантайме? Конфиг, я напомню, - это был всего лишь пример.

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

theNamelessOne ★★★★★ ()
Ответ на: комментарий от theNamelessOne

Лол, а чтобы составить строчку в памяти, конфиг парсить не нужно?

нужно. Я не говорю, что этот вариант проще, я говорю, что разница невелика.

Или ты хочешь, чтобы в конфиге задавалась сама форматная строка?! И кто вообще сказал, что в конфиге значения будут заданы именно в таком формате

Я сказал. Это вообще чей конфиг? Дядин? Нет, мой. Какой формат захочу, такой и сделаю. Кстати, формат printf очень удобен, особенно если его допилилить (добавить свои специфические опции).

А если значение будет браться не из конфига, а вычисляться в рантайме?

конфиг тоже в рантайме парсится.

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

да ничего я не придумываю! просто _на практике_ эта звёздочка мне настолько не нужна, что я о ней давно и успешно забыл. Это тебе надо придумывать какие-то случаи, и получается у тебя это не очень. Пример с конфигом это только доказывает...

ЗЫЖ по любому - твоя звёздочка - оффтоп. Признаю, читал ман по диагонали, про звёздочку прочитал бегло, и решил, что она не нужна. И забыл. Вот и удивился, дескать WTF?...

Удовлетворён?

drBatty ★★ ()
Ответ на: комментарий от jtootf

выбирать объект по тому, как он крякает - точно такой-же быдлокод

ты когда-нибудь пользовался STL? знаешь, как там итераторы проверяются на соответствие контракту?

бугага

именно тут — на итераторах — ты промахнулся

а так вообще да, утиная типизация во время компиляции — т.е. не стоит это сравнивать напрямую с утиной типизацией во время выполнения, как скажем бывает в питоне

ладно, вернемся к итераторам

простейший код:

#include <iostream>
#include <iterator>
using namespace std;

class MyIterator
{
public:
#ifndef  DUCK
  typedef std::forward_iterator_tag iterator_category; 
  //      ^^^^^^^^^^^^^^^^^^^^^^^^^  вот тут четко написано че за итератор мы определяем
#endif  
  typedef int value_type;
  typedef std::ptrdiff_t difference_type;
  typedef int& reference;
  typedef int* pointer;

  MyIterator(int value): value(value) {}
  MyIterator operator ++ () { ++value; return *this; }
  int operator * () { return value; }
  int operator != (const MyIterator& that) { return value!=that.value; }
private:
  int value;
};

int main() {
  copy( MyIterator(0), MyIterator(10), ostream_iterator<int>(cout, " ") );
  cout << '\n';
  return 0;
}

при попытке сделать чистую утиную типизацию, наприме с помощью clang -DDUCK тебе выплюнет ошибку

т.е. именно в stl немного че-то проверяют, но ты в своем коде можешь ограничиться утиной типизацией... правда не ясно, как ты будешь различать input iterator и forward iterator — у них сигнатуры одинаковы

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от tailgunner

Template, or generic functions or methods apply the duck test in a static typing context

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

у них все правильно написано если внимательно читать (или знать заранее, что они хотят сказать)

впрочем, можно попытаться уточнить:

Template, or generic functions or methods apply the duck test in evaluation context of (static) typing compiler phase.

к макросам немерле 1 претензии, кстати, ровно те же, хотя там возможностей поставить костыли типа typedef std::forward_iterator_tag iterator_category несравненно больше

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от drBatty

если уж юзаешь и пытаешься тут защищать printf, так знай ее фичи типа звездочки

короче, учись

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от www_linux_org_ru

вот тут четко написано че за итератор мы определяем

ок, мой пример сосёт. впрочем, такой закат солнца вручную от проверки соответствия контракту, мягко говоря, далёк

как ты будешь различать input iterator и forward iterator — у них сигнатуры одинаковы

а как их различишь ты? если в итераторе с семантикой одного будет стоять тег другого, компилятор тебе ни слова не скажет

jtootf ★★★★★ ()
Ответ на: комментарий от jtootf

впрочем, такой закат солнца вручную от проверки соответствия контракту, мягко говоря, далёк

однозначно это костыли (о чем я написал чуть выше)

а как их различишь ты? если в итераторе с семантикой одного будет стоять тег другого, компилятор тебе ни слова не скажет

ну тут ситуация почти ничем не лучше, чем написать

class MyIterator: public ForwardIterator { ...

тогда как на самом деле код внутри класса заслуживает не большего, чем

class MyIterator: public InputIterator { ...

компилятор у нас больше чем сигнатуры проверять не умеет; а вообще хороший язык должен поддерживать проверку инвариантов заданных программистом

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от www_linux_org_ru

хороший язык должен поддерживать проверку инвариантов заданных программистом

да

jtootf ★★★★★ ()
Ответ на: комментарий от www_linux_org_ru

если уж юзаешь и пытаешься тут защищать printf, так знай ее фичи типа звездочки. короче, учись

я много чего юзаю, что, я ВСЕ фичи обязан знать? Заняться мне больше не чем...

drBatty ★★ ()
Ответ на: комментарий от dave

Ну, и как? Разобрались в чем суть ООП?

Я так понял, техника ООП заключается в придерживании полимофизму объектов, их инкапсуляции и наследовательности. Первые два понятия я уже могу увидеть в своём коде, так как стараюсь стандартизовать API модулей (как объектов) и есть средства разделения объектов работающих только внутри модуля и объектов на экспорт.

Вот с наследовательностью не всё так просто. Конечно можно использовать с CLOS для создания эффективных методов, но ООП я так понял это именно систематичное использование наследования. Т.е. чувствуется натягивание на задачу какой-то утопичной идеи. Особенно хорошо напрашивается этот субъективный вывод после того, что наследовательность вроде как главный изъян в критике ООП.

ados ★★★★★ ()
Последнее исправление: ados (всего исправлений: 1)
Ответ на: комментарий от korvin_

ИМХО, правильно сравнил. Так же, как и макросы, шаблоны упрощают визуальное представление кода, но не сам код. Только в отличие от относительно предсказуемых сишных макросов, шаблоны ещё и имеют свойство ломаться в зависимости от компилятора.

hobbit ★★★★★ ()
Ответ на: комментарий от ados

Я так понял, техника ООП заключается в...

Неа. Техника ООП заключается в .... . Вообщем, когда надо сделать что-то быстро и|или универсально - то ООП выкидывают. В иных случаях используют ООП.

anonymous ()
Ответ на: комментарий от anonymous

Только вот <censored> 95% на unix-way ложат половой член.

ados ★★★★★ ()
Ответ на: комментарий от ados

Только вот <censored> 95% на unix-way ложат половой член.

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

1. Ну а незнание - это беда. Беда, началом которой является тупое стремление к упрощению.

2. Однажды один знакомый (слепой фанат python'а) сказал следующее (стараюсь стиль повествования выдержать): «][ули сложного накодить? Делай все объектами и на][уй.»

3. Суть ООП скрыта в предыдущем пункте.

Успехов!

anonymous ()

С точки зрения манеры проектирования, а не особенностей реализации С++ там CLOS. Вот например я использую определения классов и даже иногда наследование как узнать пишу ли я в ООП стиле?

SOLID-принцип:

http://ru.wikipedia.org/wiki/SOLID_(объектно-ориентированное_программирование)

anonymous ()
Ответ на: комментарий от ados

но ООП я так понял это именно систематичное использование наследования. Т.е. чувствуется натягивание на задачу какой-то утопичной идеи.

неа.

«систематическое использование наследования» — это Simula способ организации ООП (который без особых изменений перекочевал в C++/Java/C#), где в сильно типизированный язык со статической типизацией вкорячивается какие-то «новые» типы языка: классы, обобщённые классы (дженерики). Понятное дело, что наследование — всего лишь способ совместить в системе типов языка эти новые типы. Вообще говоря, вместо наследования с тем же эффектом и большей гибкостью можно использовать агрегирование или композицию, реализовав паттерн типа Proxy. Отсюда вывод: наследование не необходимо для ООП языка, может быть эмулировано агрегированием. Наследование удобно в связке с полиморфизмом, то есть полиморфизм — более важный «кирпичик» ООП, чем наследование. Скажем, это синтаксический сахар для удобной концепции.

Сама концепция может быть реализована и как в прототипно-ориентированном ООП (языки Io, JavaScript, в некоторой степени метатаблицы в Lua) — когда классов нет, есть только объекты.

Или как в Smalltalk, когда наследование отведено на откуп рантайм-системы: за счёт динамичности типизации не нужно знать все типы до компиляции, тот же вызов super метода выполняется однотипно с остальными, реализацией диспетчеризации в рантайм-системе.

anonymous ()
Ответ на: комментарий от anonymous

Ну простой вывод:

ООП удобно для доработок реорганизацией структуры классов: когда нужно переместить метод/свойство в другой класс, при этом возможно понадобится изменять связанные классы, которые вызывают/вызываются этим классом.

Процедурное программирование удобно когда нужно добавить новый метод, не изменяя структуру класса. Которая уже сложилась и изменяться не будет. Добавление нового «метода» (функции с первым параметром this, остальными параметрами метода) не требует изменений в других местах.

То есть: когда объектная модель уже сложилась и достаточно её «расширять» вот так — хватит и процедурного программирования. Когда нужно изменять структуру классов, отношения — нужно ООП в том или ином виде.

anonymous ()
Ответ на: комментарий от drBatty

С другой стороны, без наследования, у тебя не получится нормальной инкапсуляции и нормального полиморфизма (в C++). Хотя ты можешь конечно что-то накостылить шаблонами и дружественностью.

можно накостылить паттерн типа Proxy, или сделать миксин. В дочерний класс завёрнут (агрегирован) экземпляр базового класса, при вызове методов/свойств экземпляра дочернего на самом деле вызываются методы дочернего, свойства через геттеры/сеттеры.

Но зачем?

да, странно зачем. например, чтобы показать что без наследования в языке его можно свелосипедить. то есть, наследование не необходимо для ООП и реализуемо из других кирпичиков.

Но в C++ без наследования никак.

патаму что C++ — это язык реализации объектной модели VTABLE (с таблицей виртуальных методов).

однако, в объектах мало практического смысла, если они не полиморфны.

более того, если они таки полиморфны, но недостаточно полиморфны — в таких объектах тоже не очень много смысла, не шибко повторно используемы. а дальше начинается, что такое «достаточно полиморфны», полиморфизм методов, шаблоны, дженерики, интерфейсы.

Ну например у нас есть утка, и нам без разницы, как к этой утке подойти, утка_летай() это и процедурная функция, и метод ООП, работает это при любом подходе, и разница в подходах работает не в сторону ООП - проще иметь процедуру утка_летай(), чем метод утка.летай(), конечно удобно инкапсулировать некоторые ненужные кишочки в «class утка», но это вопрос удобства

в языках D есть UFCS : когда утка_летай(this* Утка, ...) = Утка.Летай(...) — синтаксический сахарок на уровне конпелятора

похожая фича есть в Vala. В языке Orc который как и вала транслируется в Си, тоже есть такое.

В С мы тоже можем что-то инкапсулировать в статические переменные файла «утка.c», и внутрь функции утка_летай() тоже можно инкапсулировать что угодно.

шото можем. Но вот реализовать наследование и корректный полиморфизм без поддержки рантайма — не можем.

Просто так же как Vala, например — официальный язык для поддержки ООП модели GObject, так и С++ — язык для поддержки модели VTABLE, и натянуть на язык другую модель можно с большим скрипом и закатом солнца вручную в рантайме.

Совсем другое дело, когда у нас есть абстрактный класс «птица», в котором есть чистый виртуальный метод «летай()»

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

anonymous ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.