Clean Fracanto #3 — DRY
One Pattern, Five Abstraction Levels
Don't Repeat Yourself — every piece of knowledge shall have a single unambiguous representation within the system.
The vtable pattern in fracanto is itself an example of consistent DRY: The same mechanism — a struct of function pointers, an instance with const ops_t *ops and void *ctx, inline wrappers with NULL checks — is reused across five abstraction levels:
- HAL level:
fracanto_hal_ops_tabstracts CAN hardware. - Panel level:
panel_ops_tabstracts physical controls. - IO level:
io_ops_tabstracts analog I/O. - Audio level:
audio_ops_tabstracts DMA-based audio streaming. - Module level:
module_ops_tabstracts the module logic itself.
The pattern need not be reinvented for each use case. Anyone who understands fracanto_hal_ops_t also understands panel_ops_t, io_ops_t, and all other vtables — the structure is identical, only the content of the function pointers differs.
The Dependency Injection in the drivers also follows DRY: All five peripheral drivers (ADS8866, DAC8552, PCM5102A, Encoder, SSD1306) use the same schema — an _hw_t struct with function pointers and void *hw_ctx, passed during initialization and copied internally. Knowing one driver means knowing the pattern of all drivers.
Type erasure through void *ctx is the DRY enabler: Without this mechanism, each abstraction level would need to invent its own polymorphism mechanism. Instead, all use the same one — function pointers plus opaque context — and differ only in the semantics of their operations.
