вторник, 11 февраля 2014 г.

Си и ООП

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

Итак, что такое ООП? Это паттерн (шаблон, архитектура приложения), построенный на идее, что все - объект. А объект, в свою очередь, это данные и действия по обработке этих данных. Будь то автомобиль: текущие координаты - это данные, доступно действие "переместить". Если смотреть на ООП в философском смысле, то открываются три волшебных заповеди ООП: инкапсуляция, полиморфизм, наследование. В С ничего для этого нет: единственные доступные агрегаты, структуры, никак не скрывают свои члены.

Наследование

Общеизвестно, что члены объекта структуры располагаются один за другим ( + выравнивание, почему рекомендуется выкладывать от меньших по размеру членов к большим), потому мы можем на некую "сырую" область памяти "наложить" тип структуры, разметив этим области, которые относятся к отдельным членам. Это мы делаем приведением указателя с void на наш тип. Или же наоборот, чтобы записать структуру в файл, нам выгодно представить ее отдельными байтами, для этого мы приводим указатель на структуру к char и записываем в файл sizeof(имя_типа) количество байт, начиная с того-самого указателя

Что нам даст это знание? А то, что мы можем включать структуру (не указатель, а именно объект) в другую структуру, таким образом наследуя все данные первой структуры. Теперь у нас есть два шаблона: исходная структура и исходная структура + новые данные. А имея указатель на начало такой комплексной структуры (нового типа), мы можем приводить его в тот, или иной тип.

struct Base
{
    // ... Данные базового типа
};

struct Derived
{
    struct Base header;
    //... Данные наследника
} Base;

// Пример использования:

struct Derived *der = (struct Derived*)malloc( sizeof(struct Derived) ); // Память выделяется единожды!
struct Base *bas = (struct Base*)der;

// Можем работать с объектом как с базовым - через bas
// или как с наследником - через der. 
// Ну и, конечно же, к членам Base через der можем 
// обращаться как der->header.имя_члена

Полиморфизм

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

void DoPolymorphicAction(Base *baseRef)
{
    switch(baseRef->type)
    {
        //... Смотрим на тип, делаем действие
    }
}

ЗЫ

В заключение, маленький пример. Можно было бы избежать дублирования логики в конструкторе наследника, это потребует, как вариант, конструктор копирования, но пусть это будет твоим "домашним заданием" =)

enum ObjType { /* ... , */ T_DERIVED };

// Базовый класс
typedef struct tagBase
{
    ObjType        type; // Метаданные
    // Данные базового класса
    struct tagBase  *parent;
} Base;

// Класс-наследник
typedef struct tagDerived
{
    Base header; // Унаследованные данные базового класса
    char *name;
    } Derived;

// Конструкторы
Base* CreateBase(Base *parent)
{
    Base *newObj = (Base*)malloc( sizeof(Base) );
    newObj->parent = parent;
    return newObj;
}

Derived* CreateDerived(Base *parent, const char *name)
{
    Derived *newObj = (Derived*)malloc( sizeof(Derived) );
    newObj->header.parent = parent;
    newObj->name = (char*)malloc( sizeof(char) * (strlen(name) + 1) );
    strcpy(newObj->name, name);
    return newObj;
}

// Универсальный деструктор
void ReleaseNode(Base **base)
{
    switch( (*base)->type)
    {
        //...
        case T_DERIVED:
            free( ( (Derived*)(*base) )->name );
            break;
    };
    free( *base );
    base = NULL;
}

Комментариев нет:

Отправить комментарий