Плюсы и минусы использования свойств
Вот скажите, люди, как правильнее (с точки зрения "парадигмы") писать
//вместо
Что_то_там = Object.Value;
Но против следующего аргумента возразить будет сложно. Какой код будет "читабельней" и понятней:
// или же
Object.SetValue (постараемся_не_запутаться_в_скобках);
Кроме того, __property прозволяет "скрывать реализацию" чтения/записи значения, что куда больше соотвествует парадигме самого ООП.
Хотя недостатки у __property есть, вот примеры
1. изменение читаемого значения
{
private:
TMyStruct fField;
virtual void SetField (const TMyStruct &Value);
public:
__property TMyStruct Property = {read = fField, write = SetField};
};
//.....
MyObject.Property.My_Struct_Field = Другое_Значение
// Значение изменится, но без вызова метода SetField :(
// Исправляется использованием метода чтения,
// возвращающего значение либо константную ссылку.
Лично я в своих классах явно использую SetValue(zzz) или GetValue(), хотя и оставляю неявный доступ непосредственно к полям класса.
К плюсам можно отнести возможность создания свойств только для чтения
Но против следующего аргумента возразить будет сложно. Какой код будет "читабельней" и понятней
Ну во первых код с __propery вносит путаницу и неоднозначность. При чтении кода не понятно, что происходит - вызов методов или же прямое присваивание публичной переменной.
А во вторых это элементарно делает код зависмым и не соответствующим языку. А ведь я так думаю на Borland C++ Builder не только окошки рисуются, да в окошках что то должно происходить. А вот бизнес логику можно по возможности и универсальной делать, если это явно не сопряженно с какими то трудностями.
А вот бизнес логику можно по возможности и универсальной делать, если это явно не сопряженно с какими то трудностями.
С этим, конечно, трудно поспорить.
Вобще __property задумывался для ObjectInspector'а, т.е. для создания VCL компонент программистом - чтобы кинул на форму свою поделку - а свойства отобразились где надо.
Резонное замечание, порою использование свойства внутри цикла, который не изменяет его значения, может привести к большим расходам, если свойство будет объявлено "вычисляемым".
По-этому, я часто использую следующий код
// Код с использованием MyClass.Items ;
// без изменения кол-ва элементов
Вынося значение неизменяемого свойства в стек, мы тем самым защищаем код от возможных излишних затратах производительности, в случае использования функции чтения.
Вообще-то "универсальность" и "переносимость" кода редко когда бывает обязательным критерием. Тем более, что когда вовсю используются классы и функции "диалекта" (в данном случае - VCL), "переносить" что либо всяко не получится.
P.S. А вот является ли "__closure" стандартным средством С++?
Тем более, что когда вовсю используются классы и функции "диалекта" (в данном случае - VCL), "переносить" что либо всяко не получится.
[/QUOTE]
При чем тут диалекты. Ведь VCL - это в первую очередь оконная библиотека ну и с набором некоторых дополнительных фич. При чем тут сам язык. Повторюсь ведь Borland C++ Builder, не только для рисования графических элементов исспользуется так? А графические элементы без смыслового наполнения тоже не нужны.
В своих программах я использую множество действий, не связанных непосредственно с интерфейсом, при этом:
1. Для работы со строками я использую класс AnsiString, и ещё не встречал "объективных" причин пользовать что-либо другое.
2. Порой мне требуется проверка параметров функций/методов и генерация исключительных ситуаций при некорректных значениях. При этом я, опять таки, генерирую исключения с типами, производными от Exception - удобные они: и обработчик по типу выбрать можно, и текст сообщения выдают, и значения неверных параметров подставить могут, и даже номер страницы справочного файла приложат - красота.
3. Я создаю множество объектов, и порою мне нужно придавать им весьма специфичные особенности, например, двухэтапное создание/удаление - но иных способов, кроме как переопределение виртуальных методов AfterConstruction и BeforeDestruction класса TObject, я не встречал :(. И здесь на этот вопрос ответа тоже не дали.
4. Порою, для облегчения взаимодействия "рабочей" части программы с "интерфейсной", мне требуется использовать "стандартные" классы VCL, например TStrings.
4. Даже в "неинтерфейсной" части программы может потребоваться доступ к данным самого приложения, а то и управление им. Специально для этого, существует глобальный объект Application.
Уф, пока достаточно... Заговорился я что-то :D
1. Для работы со строками я использую класс AnsiString, и ещё не встречал "объективных" причин пользовать что-либо другое.
[/QUOTE]
Просто, ты не используешь или очень огранниченно используешь в своих программах другие объекты предоставляемые языком C++ и в частности STL, а так же видимо не используешь такую "родственную" языку библиотеку, как boost.
Кроме того ты, видимо, не используешь множественное наследование.
[QUOTE=el scorpio]
3. Я создаю множество объектов, и порою мне нужно придавать им весьма специфичные особенности, например, двухэтапное создание/удаление - но иных способов, кроме как переопределение виртуальных методов AfterConstruction и BeforeDestruction класса TObject, я не встречал :(. И здесь на этот вопрос ответа тоже не дали.
[/QUOTE]
Видимо, не видел этого вопроса, т.к. смог бы дать ответ.
[QUOTE=el scorpio]
4. Порою, для облегчения взаимодействия "рабочей" части программы с "интерфейсной", мне требуется использовать "стандартные" классы VCL, например TStrings.
[/QUOTE]
Это просто привычка, возможно, вредная.
[QUOTE=el scorpio]
4. Даже в "неинтерфейсной" части программы может потребоваться доступ к данным самого приложения, а то и управление им. Специально для этого, существует глобальный объект Application.
[/QUOTE]
А вот это однозначно вредная привычка.
Что же касается вопроса топика, то в языке C++ есть множество своих способов предоставления и определения доступа к полям данных:
- прямое обращение,
- через специальные методы,
- по средством переопределенных операторов (обычно operator=, operator-> и т.п.).
Вопрос: зачем применять ещё что-то нестандартное?
Это усложняет понимание кода. Когда я вижу запись
Object.Value = value;
я предполагаю, что value - это поле определенного типа, а значит я могу рассматривать всю запись в контексте именно типа Value, не обращая внимания на тип Object. Видя такое выражение я отметаю Object, т.к. он лишь определяет с каким именно экземпляром типа Value я работаю, и далее изучаю запись лишь в терминах Value.
Другими словами, такой код мысленно преобразуется в код вида:
ValueType& Value = Object.Value;
Value = value;
Причем это может быть не просто мысленное разделение, но и в полне реальное, например, при рефакторинге или оптимизации кода.
Понятно, что при использовании __property разделение, как в уме, так и в реальности, более сложное, т.к. вводится дополнительная косвенность: тип Value становится зависимым от типа Object. Т.о. запись Object.Value = value; оперирует не объектом Value, как это кажется на первый взгляд, а объектом Object. Иными словами, я не могу сказать по одной этой записи, на что повлияет такое присвоение.
Кроме субъективных сложностей, возникают и объективные. Две я уже указал - рефакторинг и оптимизация. Другие, возможно, проявляются при наследовании, например, при переопределении способа доступа.
Кроме того ты, видимо, не используешь множественное наследование.
[/quote]
Касаемо AnsiString - дело в том, сколько у него есть удобных методов по обработке строки, а также внешних функций, и в том, что с точки зрения производительности, куда лучше использовать его, чем заниматься бесконечной конвертацией в string и обратно при активаной работе с интерфейсом.
Давняя тема, сейчас находится на последней странице раздела Builder - если не удалилась за "ненадобностью".
А за хороший ответ я бы искреннее "спасибо" сказал. Хоть и понимаю, что он не булькает :)
StringList - класс тоже довольно "универсальный", как в плане интерфейса, так и в плане алгоритмической части задачи. И кроме него, есть ещё много других классов. Например, для работы с ini или реестром, врядли STL реализует классы для таких действий, а использование API-функций куда меньше подходит под концепцию ООП.
Кроме того, уж если я пишу программу для БД, то там "переносимость" явно отдыхает.
- прямое обращение,
- через специальные методы,
- по средством переопределенных операторов (обычно operator=, operator-> и т.п.).
"Прямое обращение" - для классов, требующих внутреннюю целостность данных, вещь очень даже вредная.
Про "специальные методы" я уже говорил выше.
А "переопределённые операторы" имеют очень огранниченную область применения, ибо выполняемые ими действия должны логически соответствовать их описанию. Иначе такой класс можно будет использовать только в "соревнованиях по запутанному программированию" :)
Object.Value = value;
я предполагаю, что value - это поле определенного типа, а значит я могу рассматривать всю запись в контексте именно типа Value, не обращая внимания на тип Object. Видя такое выражение я отметаю Object, т.к. он лишь определяет с каким именно экземпляром типа Value я работаю, и далее изучаю запись лишь в терминах Value.
Другими словами, такой код мысленно преобразуется в код вида:
ValueType& Value = Object.Value;
Value = value;
Причем это может быть не просто мысленное разделение, но и в полне реальное, например, при рефакторинге или оптимизации кода.
Понятно, что при использовании __property разделение, как в уме, так и в реальности, более сложное, т.к. вводится дополнительная косвенность: тип Value становится зависимым от типа Object. Т.о. запись Object.Value = value; оперирует не объектом Value, как это кажется на первый взгляд, а объектом Object. Иными словами, я не могу сказать по одной этой записи, на что повлияет такое присвоение.
Собственно говоря, имя свойства (как и медота) всегда должно соответствовать его роли в классе. И по имени зачастую вполне можно понять, что оно делает. Например, для массива/списка Count будет соответствать кол-ву элементов, а Items - указанному элементу. Глвное, что можно читать/писать значения. А уж как работают эти свойства - остальную часть программы интересовать не должно, ибо "инкапсуляция".
Правда, есть ещё два аспекта, на которые следует обратить внимание программистам Builder.
1. Если запись значения открытого свойства выполняется прямым обращением к закрытому полю, то следующий код будет работоспособным, а вот если вдруг программист вместо имени поля укажет метод записи, то будет ошибка компиляции
Obj1.Value = Obj2.Value = Obj3.Value = MyValue;
2. Если чтение открытого поля сложного типа выполняется прямым обращением к закрытому полю, то следующий код произведёт изменение поля без вызова метода записи поля.
Obj.Value.ValueField = xyz;
Хотя это вполне законно используется, когда поле является указателем на дочерний объект класса - изменить указатель нельзя, а вот указываемый объект - вполне можно.
3. Если чтение открытого поля сложного типа выполняется методом, возвращающим константную ссылку/, то п2 не получится - вообще, изменить будет нельзя.
4. Если чтение открытого поля (любого типа) выполняется методом, возвращающим значение данного типа, то при изменении полученного значения, автоматически будет вызван метод записи свойства. Тоже самое будет сделано при изменении значения простого типа, полученного прямым обращением к закрытому полю
objArray.Count++; // увеличение кол-ва элементов массива
objArray.Item [0].Property = MyValue;
// Эквивалентно
int tmp = objArray.fCount; //Чтение закрытого поля
tmp ++; // изменение значения
objArray.SetCount (tmp); // вызов функции изменения свойства объекта
TItem tmp2 = objArray.GetItem (0);
tmp2.Property = MyValue;
objArray.SetValue (tmp2);
[/code]
Хорошо всё это или плохо - решайте сами.
Хотя мне п2 порой приносит изрядные сложности :(, вынуждая прибегать к п3, зато п4 при правильном применении - хорошая вещь.