0

As per Robert C. Martin in Clean Architecture, he gives a simple UML diagram to illustrate Dependency Inversion.

To put it simply, HL1 initially referred to ML1 without interface to invoke F() directly, where the source code dependency was in the same direction as the flow of control between HL1 and ML1. However, it's a different story when interface comes in, as you can see in image that they are now in the opposite direction between HL1 and ML1.

UML diagram from clean architecture

Here, I tried to embody this concept in C code, but ran into some difficulties.

/*HL1.c*/
#include "interface.h"

int main(void) {
  invoke_function_pointer(); //segmentation fault
  return 0;
}

/*interface.h*/
#ifndef INTERFACE_H
#define INTERFACE_H

void invoke_function_pointer(void);
void set_function_pointer(void(*input)(void));

#endif

/*interface.c*/
#include "interface.h"

static void(*fptr)(void);
void invoke_function_pointer(void) {
    (*fptr)();
}
void set_function_pointer(void(*input)(void)) {
    fptr = input;
}


/*ML1.c*/
#include <stdio.h>
#include "interface.h"

void ML1_print_andy(void) {
    printf("Andy\n");
}

void ML1_init(void) {
    set_function_pointer(&ML1_print_andy);
}

The result of this program ends up with segmentation fault, which is not surprise at all, because none of code invoke ML1_init(). What really confused me is

Q1: Can interface module (interface.h and interface.c) represent interface <I> in UML?

Q2: Who is responsible for invoking ML1_init()? When to?

Q3: What is missing piece if above C code is not equivalent of UML?

1 Answer 1

2

As you already understood, your program requires an initialization to set up the inversed dependencies. This is often called dependency injection. For small to medium sized programs, the main function can take this responsibility (larger OO programs sometimes use specific components for the initialization, so-called DI containers, or IoC containers). This leads to two phases, the initialization phase and the execution phase. The latter should start at some entry point called from main, after initialization was completed.

So to answer your questions:

Q1: Using UML to describe an interface in C is resonable. Though UML notation was more intended for classes and OOP, there is no "law" which forbids it to use it for modules, and you are not the first person to use it that way. Just think of a modules as a "singleton object".

Q2: main will be responsible to call ML1_init() before the execution phase, or an initialization function which then calls ML1_init. In a more complex scenario, the initialization phase might include a step where it makes a decision between modules ML1, ML2 or ML3, based on some condition, and registers just one of them for fptr.

Q3: UML diagrams show you only an abstraction, a simplification of the program. The class diagram in your question demonstrates what happens during the execution phase, not during initialization. If you want to model initialization as well, you have to put both functions (set_function_pointer as well as invoke_function_pointer) into the class diagram, and model the call to set_function_pointer during initialization.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.