Подтвердить что ты не робот

MVC реализован в чистом C

Кто-нибудь знает о каких-либо ресурсах, которые дают прямой пример попыток создания шаблона проектирования Model View Controller в контексте C? И, в частности, встроенная система?

Чтобы уточнить, меня не интересуют С#, С++, Objective-C, Java, PHP или любые примеры языка более высокого уровня. Я хочу знать, что люди думают о том, как подойти к этому шаблону дизайна с чистым ansi C99 или даже C89. Может быть, это даже не имеет смысла в C из-за отсутствия официальных конструкций языка ООП?

В некотором контексте: мои коллеги и я работаем над встроенными системами, основанными на чипах PSoC на основе Arm. У нас есть контроль над аппаратным дизайном и печатными платами, и мы должны сделать разработку программного обеспечения, чтобы улучшить набор функций продукта. Наша модель, как правило, состоит из сбора данных от аналоговых в цифровые преобразователи в продукте. Представления могут представлять собой веб-страницу с встроенным веб-сервером или ЖК-экран с емкостным сенсорным управлением. Наши контроллеры были бы более или менее логикой клея, которая управляет отношениями между этими двумя областями кода. У нас есть много разных продуктов и вариантов для поддержки, поэтому желательно повторное использование кода.

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

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

4b9b3361

Ответ 1

Pshew... это может быть длинный ответ... но здесь идет...

Сначала начнем с этого утверждения:

Maybe this doesn't even make sense in C because of the lack of formal OOP language constructs?

Не удалось не согласиться с этим утверждением. Как я покажу позже; просто потому, что у C нет отличных ключевых слов, таких как "класс", это не значит, что вы не можете выполнить одни и те же вещи.

Я постараюсь сделать это шаг за шагом, насколько это возможно, после вашего вопроса.

ООП в C

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

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

101

Сначала начнем с базового "класса" (пойдите со мной на это):

foo.h:

typedef struct Foo Foo;
Foo * FooCreate(int age, int something);
void FooSetAge(Foo * this, int age);
void FooFree(Foo * this);

Foo_Internal.h: (вы поймете, почему я сломал это через секунду)

#include "Foo.h"

struct Foo { 
     int age;
     int something;
};

void FooInitialize(Foo * this, int age, int something);

Foo.c:

#include "Foo_Internal.h"

// Constructor:
Foo * FooCreate(int age, int something) { 
    Foo * newFoo = malloc(sizeof(Foo));

    FooInitialize(newFoo);

    return newFoo;
}

void FooInitialize(Foo * this, int age, int something)
{
    this->age = age;
    this->something = something;
}

// "Property" setter:
void FooSetAge(Foo * this, int age) {
    this->age = age;
}

void FooFree(Foo * this) { 
    // Do any other freeing required here.
    free(this);
}

Пара замечаний:

  • Мы скрыли детали реализации Foo за непрозрачным указателем. Другие люди не знают, что находится в Foo, потому что эта деталь реализации находится во внутреннем заголовочном файле, а не в "общедоступном" заголовке.
  • Мы реализуем "методы экземпляра", как и язык ООП, за исключением того, что нам нужно вручную передать указатель "this" - другие языки просто сделают это для вас - но это не имеет большого значения.
  • У нас есть "свойства". Опять же, другие языки завершают свойства getters/settings в более удобном синтаксисе, но все, что они действительно делают за кулисами, создает для вас метод getter/setter и перевод вызовов в "вызовы" в вызовы методов.

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

Итак, что, если нам нужен "подкласс" Foo - который добавляет только дополнительные функции, но может быть заменен на Foo? Простой:

FooSubclass.h:

typedef struct FooSubclass FooSubclass;
FooSubclass * FooSubclassCreate(int age, int something, int somethingElse);
void FooSubclassSetSomethingElse(FooSubclass * this, int somethingElse);
void FooSubclassFree(FooSubclass * this);

FooSubclass_Internal.h:

#include "FooSubclass.h"
#include "Foo_Internal.h"

struct FooSubclass { 
     Foo base;
     int something;
};

void FooSubclassInitialize(FooSubclass * this, int age, int something, int somethingElse);

FooSubclass.c

#include "FooSubclass_Internal.h"

// Constructor:
Foo * FooSubclassCreate(int age, int something, int somethingElse) { 
    FooSubclass * newFooSubclass = malloc(sizeof(FooSubclass));

    FooSubclassInitialize(newFooSubclass, age, something, somethingElse);

    return newFooSubclass;
}

void FooSubclassInitialize(FooSubclass * this, int age, int something, int somethingElse) {
    FooInitialize(this, age, something);
    this->somethingElse = somethingElse;
} 

void FooSubclassSetSomethingElse(Foo * this, int somethingElse)
{
    this->somethingElse = somethingElse;
}

void FooSubclassFree(FooSubclass * this) { 
    // Do any other freeing required here.
    free(this);
}

Теперь я должен упомянуть, так же, как мы сделали "инициализаторы", которые на самом деле не называет malloc, но отвечают за инициализацию переменных-членов - нам также действительно нужны дезактиваторы, которые фактически не освобождают структуру - но вместо этого освобождайте/освобождаете любые "владеющие" ссылки и т.д. Однако... Я на самом деле собираюсь упомянуть что-то в разделе ниже, что может объяснить, почему я еще не потрудился с этим.

Теперь вы должны заметить, что поскольку наш FooSubclass первый член является, по сути, структурой Foo, что любая ссылка на a FooSubclass также является действительной ссылкой на Foo - это означает, что это может быть используется как таковое почти везде.

Однако есть несколько небольших проблем с этим - как я уже упоминал в предыдущем параграфе, этот метод фактически не позволяет вам изменять поведение базового класса. (Например, что-то, что мы хотели бы сделать для освобождения нашего экземпляра).

Полиморфизм

Скажем, у нас есть какой-то метод - мы придумаем случайный пример BS - под названием calculate.

Мы хотим вызвать calculate на Foo, чтобы вернуть одно значение, но другое значение, если оно было вызвано на FooSubclass.

Это просто в C - это действительно вопрос создания метода-оболочки, который фактически вызывает функцию, на которую ссылается указатель на функцию. Языки ООП делают это за кулисами и обычно реализуются с помощью VTable.

Вот пример (я собираюсь прекратить давать полные примеры и вместо этого сосредоточиться на соответствующих частях):

Сначала мы определяем сигнатуру метода. Здесь мы говорим, что "calculateMethod" есть: указатель на метод, который принимает один параметр (указатель) и возвращает int.

typedef int (*calculateMethod)(void *);

Затем добавим переменную-член в нашем базовом классе, которая укажет на некоторую функцию:

struct Foo { 
    // ...
    calculateMethod calc;
    // ...
}

Мы инициализируем это с некоторым начальным значением в методе FooInitialize (для нашей базовой реализации):

int FooCalculate(Foo * this)
{
    this->calc(this);
}

int FooCalculateImplementation(void * this)
{
    Foo * thisFoo = (Foo *)this;
    return thisFoo->age + thisFoo->something;
}

void FooInitialize(Foo * this, ...)
{
    // ...
    this->calc = &FooCalculateImplementation;
    // ...
}

Теперь мы сделаем некоторый способ для подклассов переопределить этот метод - скажем, например, метод, объявленный в файле Foo_Internal.h с именем void FooSetCalculateMethod(Foo * this, calculateMethod value); - и voila! Методы, которые могут быть переопределены в подклассах.

Model

Our model would typically consist of data acquisition from Analog to Digital converters in the product.

ОК - так, модель, вероятно, самая простая вещь для реализации - простые "классы", которые используются в качестве механизмов хранения данных.

Вам нужно будет что-то выяснить для своего конкретного сценария (будучи встроенной системой, я не уверен, что ваши точные ограничения будут - и если вы беспокоитесь о RAM/persistence/etc) - но я думаю, что вы не хочешь, чтобы я все время погружался в это.

Вид

The views might be a web page powered by an embedded web server, or else an LCD screen with capacitive touch control.

Для физических вещей ваш "вид" может быть фиксированными кнопками на панели управления - или, как вы сказали, это может быть ЖК-дисплей или HTML.

В нижней строке здесь вам просто нужны классы, которые способны представить остальную часть вашей системы "простым" интерфейсом для отображения/изменения вещей в представлении - и инкапсулировать детали ввода-вывода пользователю.

Обычно "I" часть "IO" требует по крайней мере небольшого клина кода в представлении.

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

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

контроллер

Our controllers would more or less be the glue logic that manages the relationship between these two areas of code.

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

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

Сообщите мне, хотите ли вы большего, или если это вообще поможет.

Ответ 2

моя структура MVC!

typedef struct  
{
    int x;
} x_model;

typedef void (*f_void_x)(x_model*);

void console_display_x(x_model* x)
{
    printf("%d\r\n",x->x);
}

typedef struct  
{
    f_void_x display;
} x_view;

typedef struct 
{
    x_model* model;
    x_view* view;
} x_controller;


void create_console_view(x_view* this)
{
    this->display = console_display_x;
}

void controller_update_data(x_controller* this, int x)
{
    this->model->x = x;
    this->view->display(this->model);
}

void x_controler_init(x_controller* this, x_model* model, x_view* view)
{
    this->model = model;
    this->view = view;
}

int main(int argc, char* argv[])
{
    x_model model;
    x_view view;
    x_controller controller;

    create_console_view(&view);
    x_controler_init(&controller, &model, &view);

    controller_update_data(&controller, 24);
}

Вероятно, вы, вероятно, получите немного больше, чем это. Если у вас несколько просмотров на одном контроллере, вам нужно что-то вроде шаблона наблюдателя для управления представлениями. Но при этом у вас есть подключаемые виды. Я бы, наверное, был немного более строгим в реальности и только позволил модели быть изменен через функцию, а указатель функции отображения "вид" также можно было вызывать только через функцию (я их прямо вызываю). Это позволяет использовать различные крючки (для начала, проверьте, не указана ли модель или указатель вида/функции). Я оставил управление памятью, так как его не так уж сложно добавить, но все выглядит бесполезно.

Ответ 3

Меня интересуют, какие предложения могут иметь люди, но я думаю, что вы ударили ноготь по голове. Это, вероятно, не имеет смысла из-за отсутствия формальных конструкций ООП.

Тем не менее; возможно введение концепций ООП в ANSI-C; У меня была ссылка на этот PDF-код на некоторое время, и хотя я никогда не поглощал его (из-за отсутствия контакта с C в повседневной работе), это, безусловно, выглядит плодотворным:

http://www.planetpdf.com/codecuts/pdfs/ooc.pdf

Это большая задача, но в конечном итоге вы можете придумать какую-то работу с шаблоном/фреймом, которая упростит запись дальнейшего развития стиля MVC; но я думаю, что компромисс - можете ли вы позволить себе время? Являются ли ограничения встроенных платформ такими, что преимущества ясности, предоставляемые MVC, перевешиваются из-за отсутствия производительности/защиты памяти/сбора мусора и, конечно же, усилий, связанных с необходимостью изобретать колесо?

Я желаю вам удачи, и мне будет очень интересно увидеть, что вы придумали!

Изменить:

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

Этот другой stackoverflow имеет дело с внедрением OOP в Ansi C, это интересное чтение, но ссылки на тот же pdf: Можете ли вы написать объектно-ориентированный код в C?