Полезное для программистов:

Фриланс
Новости
Статьи
   
Рубрики:


Реализация механизма свойств в С++ ...

Поиск:
В языке С++ нет встроенной поддержки свойств, а ведь это мощный и красивый инструмент объектно-ориентированного программирования ! В майкрософтовской модификации языка Managed C++ есть своя реализация свойств, но покажите мне человека в здравом уме, который что-либо написал на таком (не в обиду будь сказано) кривоватом инструменте, как MC++ :) ...

Итак, язык С++ не поддерживает механизм свойств. Сие досадное упущение я попытаюсь исправить в этой теме, и сначала - вкратце, что такое свойство и для чего оно применяется.

Свойства иногда называют "умными полями" класса, так как ведут себя они подобно методам класса, обладая не только значением, но и поведением. "Поведение" свойств - это два действия, которые связаны с чтением и записью значения в свойство: set и get. Или, говоря научным языком, "accessor" и "mutator".

Внешне свойство выглядит как обычная переменная класса: ей можно присваивать и получать из неё значения. Свойство, как и переменная, обладает типом. В качестве типа свойства может быть всё что угодно - от int'а, до навороченного класса (в общем, всё как у обычных переменных).

Вся сила свойств начинает проявляться тогда, когда вы, скажем захотите выполнить какое-то действие, например наложить на переменную ограничение от 0 до 10. В традиционном ООП-программировании это делается так: вы вызываете отдельный метод класса (типа CheckAndModifyValue), который сначала проверит значение, которое вы ему передали, а затем модифицирует переменную. Некоторые программисты поступают ещё проще: вручную проверяют значение переменной после каждого её изменения. Согласитесь, первый способ не слишком красив по сравнению с обычной записью в переменную; второй способ хуже первого с той стороны, что при любом незначительном изменении диапазона переменной, придётся лезть в десять участков программы и править исходный код везде, где проверка диапазона выполняется вручную.

Ещё более наглядный пример выигрыша от применения свойств можно привести на следующем примере. Допустим, в вашем классе ранее была определена переменная Color. Затем, вам понадобилось добавить в класс поддержку перерисовки при каждом изменении цвета. Опять лезть в десять участков программы и править код ? Со свойствами, вам будет достаточно изменить всего лишь один небольшой фрагмент исходного кода: переменная Color абсолютно "прозрачно" заменяется на свойство Color, и в действии set производится вызов процедуры перерисовки.

Наконец, свойство можно заставить работать как средство для анализа каких-то процессов или данных. Так, например, в воображаемом классе, который занимается статистическими вычислениями, можно определить свойства MaxValue, MinValue, AverageValue и т.д. Согласитесь, что простое чтение значения переменной выглядит красивее, чем вызов метода.


Как всё это реализовать на С++ ?
Всё зависит от того, что мы хотим в итоге получить. Хотелось бы иметь следующие возможности:

1) свойство определяется внутри класса, как блок кода, содержащий тип и имя свойства
2) внутри блока кода, определяющего свойство, можно задать set- и get-методы свойства. Метод get не принимает аргументов, и возвращает значение того же типа, что и тип свойства. Метод set не возвращает ничего, и принимает аргумент "value", содержащий новое значение для свойства.
3) внутренние данные свойства (т.е. его текущее значение, если оно вообще существует) по умолчанию содержатся внутри того блока исходного кода, где и методы get и set. Имя переменной - m_Value
4) нужно организовать средства для доступа к объекту-владельцу свойства из методов get и set

Этим требованиям удовлетворяет реализация, приведённая ниже. Замечу, что оператор "=" не наследуется в производных классах, поэтому потребовалось ввести макрос PROP_DECLARE, вводящий оператор= для свойства. Данный макрос создаёт новое определение класса для свойства и открывает программную скобку, внутри которой мы можем написать свою реализацию методов get и set. Макрос PROP_CREATE закрывает программную скобку и создаёт переменную класса, которая и представляет свойство "снаружи".


Исходный код с шестью примерами создания (класс CTest) и практического использования свойств (ф-ция main):

Цитата
#include "stdafx.h"


template<typename T, typename O=void>
class TProperty
{
protected:
    T m_Value;
    O *m_pOwner;

   
virtual void set(T value)
    {m_Value = value;}
   
   
virtual T get()
    {
return m_Value;}
   
public:
   
operator T()
    {
       
return get();
    }
   
void SetOwner(O *pOwner=NULL)
    {
        m_pOwner = pOwner;
    }

    TProperty(): m_pOwner(0), m_Value(T())
    {
    }
    ~TProperty()
    {
    }
};


#define PROP_DECLARE(type,name,owner_type) class class_Property_##name: \
   
public TProperty<type, owner_type> \
    { \
       
public: void operator=(type value) {set(value);}


#define PROP_CREATE(var_name) } var_name;




/////////////////*  Тестовый класс, содержащий свойства   *////////////////////

#include <list>


class CTest
{
public:

   
/* Пример №1:
       как запретить и разрешить запись в переменную */      

   
bool CanChangeNumbers;

   
// Cвойство MyNumber.
    // Мы перекрыли метод "set" таким образом,
    // что внутреннее значение свойства доступно на изменение
    // только когда CTest::CanChangeNumbers = true
   
PROP_DECLARE(int, MyNumber, CTest)
       
void set(int value)
        {
           
if(m_pOwner->CanChangeNumbers)
                m_Value = value;
        }
    PROP_CREATE(MyNumber)





   
/* Пример №2:
       AverageValue - среднее арифметическое от A, B и C.
       Выглядит как переменная, но работает как функция ! */

   
int A, B, C;

    PROP_DECLARE(
int, AverageValue, CTest)
       
int get()
        {
           
return (m_pOwner->A + m_pOwner->B + m_pOwner->C)/3;
        }
       
void set(int value)
        {
        }
    PROP_CREATE(AverageValue)





   
/* Пример №3:
       Ограничиваем возможные значения float-свойства LimitedValue
       максимальным и минимальным пределами */
   
   
PROP_DECLARE(float, LimitedValue, CTest)
       
void set(float value)
        {
           
if(value > 10)
                value = 10;
           
else if(value < 0)
                value = 0;
            m_Value = value;
        }
    PROP_CREATE(LimitedValue)




   
/* Пример №4:
       Строковое свойство FileContents, "напрямую"  связанное с файлом  */

   
PROP_DECLARE(char *, FileContents, CTest)
       
char *get()
        {
            FILE *file = fopen("c:\\1.txt", "r");
            fseek(file, 0, SEEK_END);
           
long len = ftell(file);
            fseek(file, 0, SEEK_SET);
           
char *buf = new char[len+1];
            fread(buf, len, 1, file);
            buf[len]=0;
            fclose(file);
           
return buf;
        }
       
// в set будем записывать данные в файл
       
void set(char *value)
        {
            FILE *file = fopen("c:\\1.txt", "w");
            fwrite(value, strlen(value), 1, file);
            fclose(file);
        }
    PROP_CREATE(FileContents)




   
/* Пример №5:
       Свойство StackHead, работающее как стек. */

   
PROP_DECLARE(int, StackHead, CTest)
       
       
// Так как мы находимся внутри определения класса,
        // то можем объявлять здесь любые поля и методы !
       
std::list<int> m_Stack;

       
int get()
        {
           
int result = 0;
           
if(m_Stack.size() > 0)
            {
                result = *(m_Stack.begin());
                m_Stack.pop_front();
            }
           
return result;
        }
       
void set(int value)
        {
            m_Stack.push_front(value);
        }

    PROP_CREATE(StackHead)




   
/* Пример №6:
       Свойство Color, которое при своей модификации
       вызывает метод CTest::Redraw */

   
bool m_bRedrawed;

   
void Redraw()
    {
       
// ... тут мы как будто перерисовываем что-то
       
m_bRedrawed = true;
    }

    PROP_DECLARE(
int, Color, CTest)
       
void set(int value)
        {
            m_Value = value;
            m_pOwner->Redraw();
        }
    PROP_CREATE(Color)

   



   
// В конструкторе класса, содержащего свойства,
    // нужно проинициализировать указатель m_pOwner
    // тех свойств, которые его используют в get/set.
   
CTest()
    {
        MyNumber.SetOwner(
this);
        AverageValue.SetOwner(
this);
        Color.SetOwner(
this);
       
        m_bRedrawed =
false;
    }
};



////////////////////////* main *///////////////////////////////



int _tmain(int argc, _TCHAR* argv[])
{
    CTest obj;

   
// Пример №1 - запрет и разрешение на запись свойства MyNumber
   
obj.CanChangeNumbers = false// запретили запись MyNumber
   
obj.MyNumber = obj.MyNumber + 1; // MyNumber не изменяется
   
obj.CanChangeNumbers = true; // разрешили изменение MyNumber
   
obj.MyNumber = obj.MyNumber + 1; // MyNumber теперь изменился
   
int result = obj.MyNumber; // = 1



    // Пример №2 - подсчитаем среднее арифметическое (св-во AverageValue)
   
obj.A = 1;
    obj.B = 2;
    obj.C = 3;
   
int result2 = obj.AverageValue; // = 2



    // Пример №3 - ограничение допустимого диапазона (св-во LimitedValue)
   
obj.LimitedValue = 5;
   
float result3 = obj.LimitedValue; // = 5
   
obj.LimitedValue = 1000;
    result3 = obj.LimitedValue;
// = 10
   
obj.LimitedValue = -1000;
    result3 = obj.LimitedValue;
// = 0



    // Пример №4 - свойство FileContents - пишем и читаем файл "напрямую" !!!
   
obj.FileContents = "Text 1";
   
char *buf = obj.FileContents;
   
delete buf;



   
// Пример №5 - свойство StackHead, работающее как стек
   
obj.StackHead = 1;
    obj.StackHead = 2;
    obj.StackHead = 3;
   
int x = obj.StackHead; // = 3
   
int y = obj.StackHead; // = 2
   
int z = obj.StackHead; // = 1



    // Пример №6 - свойство Color, при изменении вызывается метод CTest::Redraw( )
   
obj.Color = 123; // изменили цвет - выполнилась "перерисовка"
   
bool result4 = obj.m_bRedrawed; // = true

   
return 0;
}


P.S.
+ любому, кто найдёт способ обойти невозможность наследования оператора=, и избавиться от макросов !!!
Автор: mr.DUDA
Сайт: http://






Просмотров: 4223

 

 

Новые статьи:


Популярные:
  1. Как сделать цикличным проигрывание MIDI-файла?
  2. Создание AVI файла из рисунков
  3. Как устройство "отключить в данной конфигурации"?
  4. Kто в данный момент присоединен через Сеть?
  5. Как узнать количество доступной памяти?
  6. Как реализовать в RichEdit разноцветный текст?
  7. Как скрыть свое приложение от ProcessViewer
  8. Как программно нажать/скрыть/показ кнопку "Start"?
  9. Модуль работы с ресурсами в PE файлах
10. Функции вызова диалоговых окон выбора
11. Проверка граматики средствами Word'а из Delphi.
12. Модуль для упрощенного вызова сообщений
13. Функции для записи и чтение своих данных в, ЕХЕ- файле
14. Рекурсивный просмотр директорий
15. Network Traffic Monitor
16. Разные модули
17. Универсальная функция для обращения к любым экспортируем функциям DLL
18. Библиотека от VladS
19. Протектор для UPX'а
20. Еще об ICQ, сообщения по контакт листу?
21. Использование открытых интерфейсов
22. Теория и практика использования RTTI
23. Работа с TApplication
24. Примеры использования Drag and Drop для различных визуальных компонентов
25. Что такое порт? Правила для работы с портами
26. Симфония на клавиатуре
27. Загрузка DLL
28. Исправление автоинкремента
29. Взаимодействие с чужими окнами
30. Проверить дубляжи в столбце


 

 

 
 
На главную