Series, click the above [Microcontroller and Microcontrol Development Journal] to follow this account to avoid losing it.Introduction: This article will briefly describe the Decorator Pattern from “Head First Design Patterns” and implement this pattern using C language. It will also explain the C language features used in the implementation;Background of the Decorator Pattern Implementation:
Starbucks offers various coffee types, such as House Blend, Dark Roast, Decaf, Espresso, etc.. Customers can add various condiments like Steamed Milk, Soy, Mocha, or Whip according to their preferences when purchasing coffee, and different condiments will have different costs, so the ordering system needs to consider the prices of these condiments..
If a simple inheritance approach is used to create a subclass for every combination of coffee and condiment to calculate the prices, it will lead to an exponential growth in the number of classes, resulting in a “class explosion” problem, making the system difficult to maintain. Moreover, if the price of a certain coffee or condiment needs to be modified, a large number of related classes will need to be changed; if new coffee or condiments are added, a large number of new classes will also need to be created, violating the “open for extension, closed for modification” principle. Additionally, for some newly developed beverages, certain condiments may not be suitable, but according to this inheritance design, subclasses will still inherit methods that are not needed..
To solve these problems, the book introduces the Decorator Pattern, which uses beverages as the main subject and dynamically “decorates” beverages with condiments at runtime, adding responsibilities/features/functions to objects, thus providing a more flexible functional extension solution than inheritance.
For example, if a customer orders a Dark Roast coffee with Mocha and Whip, the preparation process is as follows:
First, make a cup of Dark Roast coffee, then “decorate” it with Mocha, and finally “decorate” it with Whip.

Dark Roast, Mocha, and Whip all have their own cost() methods. When calculating the price, the outermost Whip.cost() is called to invoke the next layer’s cost(), ultimately calculating the total price.
Decorator Pattern Definition:The Decorator Pattern dynamically adds responsibilities/features/functions to an object. To extend functionality, decorators provide a more flexible alternative to inheritance.Implementation Idea: Based on the above background, definition, and diagram, we need to define a Beverage class, which should include information such as coffee price and records of the decorators (decorators[n]) that decorate it. The decorator (CondimentDecorator) needs to decorate the coffee, and each decorator also has its own price (cost), and a decorator can also be decorated by other decorators or itself.Therefore, the type definitions of both the decorator and the coffee class need to be consistent.
The above is the component diagram of the example code, where the Espresso and House Blend coffee classes and condiment decorator classes all inherit from the Beverage class, while the specific Mocha and Whip classes inherit from CondimentDecorator.In the diagram, an Espresso object is created, along with two Mocha and one Whip objects to decorate the Espresso coffee object.Core Code Snippet:Considering the length and readability, only the code snippets that express the concept are provided here. The complete code can be found in the code repository at the end.
- Define the Beverage class and specific beverage classes according to the component diagram:
/* Define maximum string length and maximum depth of decorators chain */#define MAX_DESC_LENGTH (100)#define MAX_DECORATORS (5)/* Beverage class */typedef struct Beverage {char description[MAX_DESC_LENGTH];float (*cost)(struct Beverage*);int decorator_count; /* Record the current number of decorators */struct Beverage *decorators[MAX_DECORATORS]; void (*getDescription)(struct Beverage*,char*);}Beverage;/* Espresso */typedef struct{Beverage base;}Espresso;/* HouseBlend */typedef struct{Beverage base;};
- Define the condiment decorator class, Mocha class, and Whip class:
/* CondimentDecorator class */typedef struct CondimentDecorator{Beverage base;Beverage* beverage; /* Pointer to the decorated beverage */}CondimentDecorator;/* Mocha */typedef struct{CondimentDecorator base;}Mocha;/* Whip */typedef struct{CondimentDecorator base;}Whip;
- Define the initialization function for Espresso:
void Espresso_init(Espresso* this){Beverage *base = &this->base;base->cost = Espresso_cost; /* Price calculation */base->getDescription = Espresso_getDescription;base->decorator_count = 0;}
- Define Mocha initialization (Whip is similar to this):
void Mocha_init(Mocha* this,Beverage* beverage){CondimentDecorator* base = &this->base;base->base.cost = Mocha_cost;base_>base.getDescription=Mocha_getDescription;base->beverage = beverage;/* Add the decorator to the decorator chain of the decorated object */if(beverage->decorator_count<MAX_DECORATORS){beverage->decorators[beverage->decorator_count++]=&base->base;}
- Define the price calculation function:
/* Espresso cost */float Espresso_cost(Beverage* self){return 1.99f;}/* Mocha cost */float Mocha_cost(Beverage* self){CondimentDecorator* decorator=(CondimentDecorator*)self;return 0.20f + decorator->beverage->cost(decorator->beverage);}/* Whip cost *//* Similar to Mocha_cost. Omitted */
- Test code:
int main() {/* Create objects */Espresso espresso;Mocha mocha1;Mocha mocha2;Whip whip;/* Initialize objects */Espresso_init(&espresso);Mocha_init(&mocha1,&espresso.base);Mocha_init(&mocha2,&mocha1.base.base);Mocha_init(&whip,&mocha.base.base);/* Use the outermost decorator */Beverage * myBeverage = &whip.base.base;/* Calculate price */char desc[MAX_DESC_LENGTH];myBeverage->getDescription(myBeverage,desc);printf("Description: %s\n",desc);printf("cost $%.2f\n", myBeverage->cost(myBeverage));return 0;}
- Test result output:
Involved OO (Object-Oriented) Principles:Open-Closed Principle: Classes should be open for extension and closed for modification.Being open for extension means a class can be extended, while being closed for modification means that the existing implementation cannot be modified. This is achieved through composition rather than inheritance.Involved C Language Features:1. Simulating objects with structures2. Simulating inheritance and composition with nested structures
- The CondimentDecorator structure contains a Beverage* pointer, pointing to the decorated object, achieving dynamic composition.
- Through the base field, all specific beverages and decorators inherit the interface of Beverage.
3. Using recursive calls in price calculation. The recursion depth is 4 layers: Whip>Mocha2>Mocha1>EspressoConclusion:This article explores the design of the Decorator Pattern using C language.This implementation does not require dynamic memory allocation, managing objects through pre-allocated static arrays and stack space, suitable for resource-constrained environments. The recursive call chain is determined at compile time, and the runtime overhead is only function calls, ensuring efficiency.Code Repository:
https://github.com/MicroDevLog/DesignPatternInC
References:
[1]. “Head First Design Patterns” China Electric Power Press.
[2]. “Pattern-Oriented Software Architecture: Volume I” People’s Posts and Telecommunications Press
Previous Issues:Design Patterns: Exploring Embedded C Language Implementation – 0. IntroductionDesign Patterns: Exploring Embedded C Language Implementation – 1. Strategy PatternDesign Patterns: Exploring Embedded C Language Implementation – 2. Observer PatternClick the card to follow this public account: Remember to “Share” and click “Looking” or “Recommend” or “Like” or “Comment“.