From f7a90eb43a2e08d4d4cf928f2133ee69712ca580 Mon Sep 17 00:00:00 2001 From: wgroeneveld Date: Fri, 3 Aug 2018 14:11:41 +0200 Subject: [PATCH] ddd in c article --- content/post/domain-driven-design-in-c.md | 160 ++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 content/post/domain-driven-design-in-c.md diff --git a/content/post/domain-driven-design-in-c.md b/content/post/domain-driven-design-in-c.md new file mode 100644 index 00000000..6c21167c --- /dev/null +++ b/content/post/domain-driven-design-in-c.md @@ -0,0 +1,160 @@ +--- +title: Domain Driven Design in C +bigimg: /img/Faking domain logic.jpg +date: '2018-08-03' +subtitle: Who says imperative languages don't do DDD? +tags: ['domain driven design', 'C', 'C++' ] +published: true +--- + +As old as the language C may be, it took other languages a long time to catch up with something as mundane as a pointer. Pointers are the bread and butter of any C program and are widely regarded as a horrifying thing to work with. It might be a drag and require some plumbing (as with any language) but the payoff is extreme flexibility and control. + +An address can point to anything - that includes functions. Function pointers are mostly used as callbacks but in combination with structures, they become dynamic class members! That might be a little bit exaggerated. + +Let's take a look at simple concepts: a person has a certain age. + +{{}} +graph LR; + A{Person} + B[Age] + C[Is Old?] + A --> B + A -.-> C +{{< /mermaid >}} + +I can ask the person if he's old, and depending on the age the response will be yes (`true`) or no (`false`). Sounds almost too simple to implement in an object-oriented language like C#: + +```C# +class Person { + public Person(int age) { this.Age = age; } + public int Age { get; private set; } + public bool IsOld => Age > 60; +} +Person george = new Person(65); +Debug.WriteLine(george.IsOld); // true +``` + +Thinking about the `class` concept in OO nets you the `struct` concept in C that will hold the age field nicely, but problems arise when we try to tackle the `Is Old?` piece of the domain model: + +```C +struct Person { + int age; +} +int is_old(Person* p) { + return p->age > 60; +} +typedef struct Person Person; +Person* george = malloc(sizeof(Person)); +george->age = 65; +prinf("%d", is_old(george)); // 1 +``` + +A couple of remarks: + +1. C has no `bool`. +2. If you want to use your structure as a class, `typedef` is pretty much required. +3. This introduces a memory leak if you don't `free(george)`. + +And of course, our main concern is the absence of any link between `struct Person` and `int is_old`. If the function is defined in a random header, the programmer has to remember it's location and it's name. It only sounds logical that it should be an integral part of the structure. + +And it shall be - function pointers to the rescue! + +```C +struct Person { + int age; + int (*is_old)(); +} +``` + +That makes expressions as `george->is_old()` legal, but they won't be bind to anything. The pointer doesn't point to anything and we can't define the function within the structure. It needs to be **defined** somewhere else, and **rewired** to the pointer: + +```C +struct Person { + int age; + int (*is_old)(); +} +int person_is_old(Person* p) { + return p->age > 60; +} +typedef struct Person Person; +Person* george = malloc(sizeof(Person)); +george->is_old = &person_is_old; +george->age = 65; +prinf("%d", is_old(george)); // 1 +``` + +Isn't that beautiful? + +It's rather cumbersome indeed, because the creation of a new person requires you to manually wire up the function to the function pointer of the struct instance and that hurts - a lot. That's the price to pay for emulating member functions on structures as classes... + +Our last cleanup action would be to group every person-related thing into `person.h` and `person.c`, and lastly create a nice factory method for allocation of a new person: + +```C +Person* newPerson(int age) { + Person* p = malloc(sizeof(Person)); + p->age = age; + p->is_old = &person_is_old; + return p; +} +``` + +### What about C++? + +Are you wondering what the ++ in C++ might stand for? The addition of "real" classes is a revelation to old-time C programmers that tried aiming for readability in a "domain". The above example implemented in C++ is trivial: + +```C +class Person { +private: + int age; +public: + Person(int age) : age(age) {} + bool isOld() { return age > 60 ? true : false; } +}; +``` + +The `isOld` member function of Person is a one-liner that can be an `inline` function - if it's defined within the class definition structure that usually lives in the header file. Otherwise; `bool Person::isOld() {}` has to be present in some cpp source file. + +### But... C's function pointer is _dynamic_! + +That's right! That means it's much more than an emulated member function of a structure. You can rewire it at any time - it works just like a callback. Of course that is clearly not what we want here. Also notice the complete lack of any accessor modifiers in C: any function can fuck up our passed Person: + +```C +void haxx(Person* p) { + p->is_old = 0; // whoops +} +``` + +Fixed using C's pass-by-value system by leaving out the pointer `*` in the argument list. But you really don't want to do that with a structure as copying something like that requires quite a bit of memory overhead and beats the whole purpose of using C in the first place... + +Remember Java fan-boys, we had to wait until Java 8 in 2014 for the language to catch up with C using lambda's. Take a look at what function pointers are really for: + +```C +int addOne(int g) { return g + 1; } +int multiplyTwo(int g) { return g * 2; } + +int main() { + int (*op)(int) = &addOne; + printf("%d", op(5)); // prints 6 + return 0; +} +``` + +Can you imagine doing that in Java 7? With anonymous inner classes and a lot of interface clumsiness, maybe. It's a much less worse now with v8's lambda's and the new function reference operator `::`: + +```Java +class Stuff { + private static int addOne(int g) { return g + 1; } + private static int multiplyTwo(int g) { return g * 2; } + + public static void main(String[] args) { + Function op = Stuff::addOne; + System.out.println(op.apply(5) + ""); // prints 6 + } +} +``` + +That leaves us to conclude that if you're still writing C on some embedded hardware, don't forget that it is possible to group functions using structures but requires some plumbing that you may or may not like. + +The C++ language is continually evolving while the ANSI C standard is "done"[^1] - the C++11 standard eased a lot of the mystical syntax pain. So if you can migrate from `gcc` to `g++`, I don't see why not. + +[^1]: Well, not completely, [C11](https://en.wikipedia.org/wiki/ANSI_C#C11) introduced an cross-platform multi-threading API. \ No newline at end of file