Design patterns are valuable concepts to add to your mental library. These are non-obvious solutions to scenarios and problems frequently encountered in the programming world.
Exposure to patterns shows you how many of your problems have been previously solved by others, freeing you to focus your limited time and energy reserves on those problems which are truly novel for your product. You will also develop a mental library of patterns that you can work toward in refactoring efforts.
Table of Contents:
- Architecture
- General Software
- Embedded Software
- Distributed Systems
- Security
- Safety
- Data Integrity
- Testing
- Cloud Computing
- Field Atlas Entries
Architecture
Architectural patterns are general, reusable solutions to software architecture problems. They have broader scope than software design patterns and are typically intended to resolve software engineering issues.
General information about architectural patterns:
- Wikipedia: List of Software Architecture Styles and Patterns
- Pattern-Oriented Software Architecture: A System of Patterns by Frank Buschmann et al.
- Software Architecture Patterns by Mark Richards
Common architectural patterns for embedded systems include:
- Layered Architecture, which organizes the various software components into n-tiers or layers, each with a specific role
- Extremely common architectural pattern, especially for embedded systems
- Embedded layers might consist of: HAL/BSP, Drivers/Middleware, Business Logic
- Layered Architecture by Mark Richards
- Presentation Domain Data Layering by Martin Fowler
- Layers, Hexagons, Features, and Components by Simon Brown
- Hexagonal Architecture (also known as Ports & Adapters), which involves creating loosely coupled components that can be connected to each other through ports and adapters
- Hexagonal != Layers by Thomas Pierrain
- The Pattern: Ports and Adapters by Alistair Cockburn
- Layers, Hexagons, Features, and Components by Simon Brown
- Event Driven Architecture, which involves building a system around the production, detection, consumption, and reaction to events.
- Software Architecture Patterns: Event Driven Architecture by Mark Richards
- Microkernel
- Publish-subscribe, which decouples components that publish data on a given topic from the components which want to receive that data
- Domain-Specific Language (DSL), which involves the creation of a language or language dialect suitable for a specific problem domain
- Pipes and Filters, which consists of components (filters) that transform or filter data and pass it to other components by way of connectors (pipes), with potential buffering between stages
General Software
Subsections:
- General Software Design Patterns
- Decoupling Patterns
- Asynchronous and Event-Driven Patterns
- Patterns for Displacing Legacy Systems
- Multi-threading Patterns
- Refactoring Patterns
General Software Design Patterns
For generally useful software architecture patterns, see:
- Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma et al.
- Small Memory Software, by Charles Wier and James Noble, provides patterns for systems with limited memory
Generally useful programming patterns:
Decoupling Patterns
- Pointer Array Pattern – Write generic drivers by accessing registers through a table lookup.
- Callback Function – Provide a reference to a function (or function-like object) that will be invoked when a specific condition holds true.
- Facade Pattern – Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.
- Managing Complexity with the Mediator and Facade Patterns provides descriptions and examples of those patterns
- Mediator Pattern – Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.
- Main Pattern – decouple modules (and keep as many as possible independent of the underlying platform) by making the application responsible for the connections between and configuration of each module. We consider this a variation of the Mediator pattern.
- Managing Complexity with the Mediator and Facade Patterns provides descriptions and examples of those patterns
- Template Method Pattern – Defines the skeleton of an algorithm or operation in high-level steps. Users or subclasses can override or implement the behavior of specific steps within the algorithm, but are not able to modify the general algorithm flow itself.
- Generation Gap – Separate generated code from non-generated code through the Template Method pattern. This ensures that customizing the generated code does not require users to modify generated code.
- Non-Virtual Interface – controls how methods in a base class are overridden. Base classes use public, non-virtual functions that can be called by clients. Overridable methods are defined as
protected,virtualmembers. - Adapter – The adapter pattern allows the interface of an existing module to be used as/with another interface. This is done by adding in a thin “adapter” module that maps the desired interface onto the existing interface.
- Configuration Table Pattern – store configuration and initialization information inside of a table, and pass the table to an initialization routine that iterates over the table entries.
Asynchronous and Event-Driven Patterns
- Active Object – decouples method execution from method invocation for objects that each reside in their own thread of control. Typically, an active object is constructed using an internal thread and a queue of operations or events that will be executed on the active object’s thread.
- Message Passing – invoke behavior by sending messages to communicate what you want done (in contrast to the default approach of directly invoking functions provided by a module/object).
- Message Queue – components used for communication in software systems that represent a queue of messages or events that are awaiting processing.
- Event Loop – waits for and processes (or dispatches) events or messages in a program.
Patterns for Displacing Legacy Systems
- Patterns of Legacy Displacement, by Ian Cartwright, Rob Horn, and James Lewis, is a collection of patterns that can be used to modernize legacy systems. The primary link contains a general discussion on updating legacy systems, as well as an example for removing a Middleware
- Critical Aggregator – Combine data from different parts of the business to support making critical decisions
- Divert the Flow – First divert cross-organization activities away from legacy
- Extract Product Lines – Identify and separate systems by product line.
- Feature Parity – Replicate existing functionality of a legacy system using a new technology stack.
- Legacy Mimic – New system interacts with legacy system in such a way that the old system is not aware of any changes.
- Transitional Architecture – Software elements installed to ease the displacement of a legacy system that we intend to remove when the displacement is complete.
Multi-threading Patterns
- Singleton
- Using Singletons in Embedded C++ by Stratify Labs
-
- Multi-threaded Singleton Access in embedded C++ by Stratify Labs
Refactoring Patterns
- Martin Fowler’s Refactoring website documents a number of refactoring patterns. However, the patterns are fully documented in the corresponding book.
- Refactoring to Patterns by Joshua Kerievsky provides a pattern-directed refactoring approach.
Embedded Software
For a general discussion of embedded software patterns, see:
- Practical UML Statecharts in C/C++: Event-Driven Programming for Embedded Systems, by Miro Samek, which provides a selection of state machinepatterns for event-driven embedded systems
- Small Memory Software, by Charles Wier and James Noble, provides patterns for systems with limited memory
- The full book is available for free online
- Summary of Patterns
- Thinking Small: The Processes for Creating Small Memory Software, by James Noble and Charles Wier
Subsections:
- General
- Watchdog Timers
- Hardware Interfaces
- State Machines
- Memory Allocation
- Managing Limited Memory
- Small Data Structures
- Compression
- Secondary Storage
- Fail-Safe Data Storage
- User Interfaces
General
- Monitor-Actuator Pair Design Pattern
- How can we avoid a single-point of failure in a safety-critical component?
- Use two independent hardware components operating as a “monitor-actuator” CPU pair. The actuator CPU is the component that actually performs the computation or control function. An independent monitor Chip is used to detect when the actuator has failed and mitigate the failure (e.g., by resetting the actuator or triggering an emergency stop)
- Digital twin – a virtual representation that serves as the real-time digital counterpart of a physical object or process.
- Desired state vs. actual state – an application of a “digital twin” to a connected embedded device
Watchdog Timers
For general watchdog timer usage patterns:
- Proper Watchdog Timer Use, by Phil Koopman
Hardware Interfaces
- Protection Class: A Solution for Resolving I2C Communication Issues in Complex Systems – describes a pattern for handling intermittent I2C communication failures “under the hood” without alerting the higher application layers.
- High Speed Serial Port Design Pattern – describes how to interface with high-speed serial communication devices that require the use of DMA. The main objective is to encapsulate the interface with the device and provide a hardware-independent interface to the high-speed serial port.
State Machines
- Ultimate Hook
- How can you provide a consistent policy for handling events, while allowing clients to override every aspect of the default behavior?
- Hierarchical state nesting – a composite state can define the default behavior (the common look and feel) and supply an “outer shell” for nesting client substates
- Reminder
- How can you prevent loosely related functions of a system from becoming tightly coupled by a common event?
- Consider, for example, periodic data acquisition, in which a sensor producing the data needs to be polled at a predetermined rate
- Assume that a periodic
TIMEOUTevent is dispatched to the system at the desired rate to provide the stimulus for polling the sensor. - Because the system has only one external event (the
TIMEOUTevent), it seems that this event needs to trigger both the polling of the sensor and the processing of the data - We don’t want that
- Invent a stimulus to propagate a reminder that data is ready for processing
- Moreover, you can use state nesting to arrange these two functions in a hierarchical relation, which gives you even more control over the behavior
- How can you prevent loosely related functions of a system from becoming tightly coupled by a common event?
- Deferred Event
- How can we defer an event (which can be postponed within a certain limit) that is received at an inconvenient moment until the system is finished processing the current activity?
- Defer the new request and handle it at a more convenient time, which effectively leads to altering the sequence of events presented to the state machine
- Orthogonal Component
- How can we model objects consisting of relatively independent parts with their own state behavior without using orthogonal reasons?
- Use object composition. Concurrency virtually always arises within objects by aggregation; that is, multiple states of the components can contribute to a single state of the composite object.
- Transition to History
- How can we handle a high-priority event during a state transition in a composite state, and then return to the most recent substate of the composite state after processing completes?
- Store the most recently active leaf substate as a dedicated “history” data member, and use that member during a “transition to history” staet
- State-Local Storage
- How can we reduce the runtime memory requirements for complex state machines, where we have multiple complex states that don’t require use of all of the extended state variables needed by other states?
- Allow state machine designer to reduce the memory footprint of a state machine by providing variables local to states. As the state machine transitions from one state to another, the SLS mechanism automatically overlays the extended-state variables for the target state configuration on top of the no-longer needed variables for the source state configuration. This results in a lower memory footprint.
Memory Allocation
- How do you allocate memory to store your data structures?
- Choose the simplest allocation technique that meets your need.
- Small Memory Software Chapter: Memory Allocation
- Fixed Allocation
- How can you ensure you will never run out of memory?
- Pre-allocate objects during initialization.
- Variable Allocation
- How can you avoid unused empty space?
- Allocate and deallocate variable-sized objects as and when you need them.
- Memory Discard
- How can you allocate temporary objects?
- Allocate objects from a temporary workspace and discard it on completion.
- Pooled Allocation
- How can you allocate a large number of similar objects?
- Pre-allocate a pool of objects, and recycle unused objects.
- Compaction
- How do you recover memory lost to fragmentation?
- Move objects in memory to remove unused space between them.
- Reference Counting
- How do you know when to delete a shared object?
- Keep a count of the references to each shared object, and delete each object when its count is zero.
- Garbage Collection
- How do you know when to delete shared objects?
- Identify unreferenced objects, and de-allocate them.
Managing Limited Memory
- How can you manage memory use across a whole system?
- Make every component responsible for its own memory use.
- Small Memory Software Chapter: Small Architecture
- Memory Limit:
- How can you share out memory between multiple competing components?
- Set limits for each component and fail allocations that exceed the limits.
- Small Interfaces
- How can you reduce the memory overheads of component interfaces?
- Design interfaces so that clients control data transfer.
- Captain Oates
- How can you fulfill the most important demands for memory?
- Sacrifice memory used by less vital components rather than fail more important tasks.
- Read-Only Memory
- What can you do with read-only code and data?
- Store read-only code and data in read-only memory.
- Hooks
- How can you change information in read-only storage?
- Access read-only information through hooks in writable storage and change the hooks to give the illusion of changing the information.
Small Data Structures
- How can you reduce the memory needed for your data?
- Choose the smallest structure that supports the operations you need.
- Small Memory Software Chapter: Small Data Structures *Packed Data
- How can you reduce the memory needed to store a data structure?
- Pack data items within the structure so that they occupy the minimum space. *Sharing
- How can you avoid multiple copies of the same information?
- Store the information once, and share it everywhere it is needed. *Copy-on-Write
- How can you change a shared object without affecting its other clients?
- Share the object until you need to change it, then copy it and use the copy in future. *Embedded Pointer
- How can you reduce the space used by a collection of objects?
- Embed the pointers maintaining the collection into each object. *Multiple Representations
- How can you support several different implementations of an object?
- Make each implementation satisfy a common interface.
Compression
- How can you fit a quart of data into a pint pot of memory?
- Use a compressed representation to reduce the memory required.
- Small Memory Software Chapter: Compression
- Table Compression
- How do you compress many short strings?
- Encode each element in a variable number of bits so that the more common elements require fewer bits.
- Difference Coding
- How can you reduce the memory used by sequences of data?
- Represent sequences according to the differences between each item.
- Adaptive Compression
- How can you reduce the memory needed to store a large amount of bulk data?
- Use an adaptive compression algorithm.
Secondary Storage
- What can you do when you have run out of primary storage?
- Use secondary storage as extra memory at runtime.
- Small Memory Software Chapter: Secondary Storage
- Application Switching
- How can you reduce the memory requirements of a system that provides many different functions?
- Split your system into independent executables, and run only one at a time.
- Data File
- What can you do when your data doesn’t fit into main memory?
- Process the data a little at a time and keep the rest on secondary storage.
- Resource Files
- How can you manage lots of configuration data?
- Keep configuration data on secondary storage, and load and discard each item as necessary.
- Packages
- How can you manage a large program with lots of optional pieces?
- Split the program into packages, and load each package only when it’s needed.
- Paging
- How can you provide the illusion of infinite memory?
- Keep a system’s code and data on secondary storage, and move them to and from main memory as required.
Fail-Safe Data Storage
- How to Protect Non-Volatile Data by Niall Murphy
- Ensuring fail-safe data storage in battery-powered IoT sensor nodes by Nilesh Badodekar and Girija Chougala
User Interfaces
- User Involvement: Techniques for Handling Memory Constraints in the UI, by Charlies Wier and James Noble
Distributed Systems
- Distributed Embedded Scheduling
- Distributed Time
- Circuit Breaker Pattern
The Circuit Breaker pattern is designed to detect failures and encapsulates the logic of preventing a system from executing an operation that’s set to fail. Instead of repeatedly making requests to a service that is likely unavailable or facing issues, the circuit breaker stops all attempts for a while, giving the troubled service time to recover.
- Patterns of Distributed Systems, a project by Unmesh Joshi, documents a number of patterns. The primary page also describes general problem situations where these patterns can help, as well as how patterns can be sequenced together.
- Clock-Bound Wait – Wait to cover the uncertainty in time across cluster nodes before reading and writing values so values can be correctly ordered across cluster nodes.
- Consistent Core – Maintain a smaller cluster providing stronger consistency to allow large data cluster to coordinate server activities without implementing quorum based algorithms.
- Emergent Leader – Order cluster nodes based on their age within the cluster to allow nodes to select a leader without running an explicit election.
- Fixed Partitions – Keep the number of partitions fixed to keep the mapping of data to the partition unchanged when size of a cluster changes.
- Follower Reads – Serve read requests from followers to achieve better throughput and lower latency
- Generation Clock) – A monotonically increasing number indicating the generation of the server
- Gossip Dissemination – Use random selection of nodes to pass on information to ensure it reaches all the nodes in the cluster without flooding the network
- Heartbeat – Show a server is available by periodically sending a message to all the other servers.
- High-water Mark – An index in the write ahead log showing the last successful replication.
- Hybrid Clock – Use a combination of system timestamp and logical timestamp to have versions as date-time, which can be ordered
- Idempotent Receiver – Identify requests from clients uniquely so they can ignore duplicate requests when client retries
- Key-Range Partitions – Partition data in sorted key ranges to efficiently handle range queries.
- Lamport Clock – Use logical timestamps as a version for a value to allow ordering of values across servers
- Leader and Followers – Have a single server to coordinate replication across a set of servers.
- Lease – Use time bound leases for cluster nodes to coordinate their activities.
- Low-water Mark – An index in the write ahead log showing which portion of the log can be discarded.
- Paxos – Use two consensus building phases to reach safe consensus even when nodes disconnect
- Quorum – Avoid two groups of servers making independent decisions, by requiring majority for taking every decision.
- Replicated Log – Keep the state of multiple nodes synchronized by using a write-ahead log that is replicated to all the cluster nodes.
- Request Batch – Combine multiple requests to optimally utilise the network.
- Request Pipeline – Improve latency by sending multiple requests on the connection without waiting for the response of the previous requests.
- Request Waiting List – Track client requests which require responses after the criteria to respond is met based on responses from other cluster nodes.
- Revert to Source – Identify the originating source of data and integrate to that
- Segmented Log – Split log into multiple smaller files instead of a single large file for easier operations
- Single Socket Channel – Maintain order of the requests sent to a server by using a single TCP connection
- Singular Update Queue – Use a single thread to process requests asynchronously to maintain order without blocking the caller
- State Watch – Notify clients when specific values change on the server
- Two Phase Commit – Update resources on multiple nodes in one atomic operation.
- Versioned Value – Store every update to a value with a new version, to allow reading historical values.
- Version Vector – Maintain a list of counters, one per cluster node, to detect concurrent updates
- Write-Ahead Log – Provide durability guarantee without the storage data structures to be flushed to disk, by persisting every state change as a command to the append only log
Security
For general discussion of security patterns:
- SEI: Secure Design Patterns
- Black Hat Briefings: Security Design Patterns (presentation slides)
- ACM: Software-Security Patterns: Degree of Maturity, by Michaela Bunke
Security anti-patterns:
- Embedded Systems Security Pitfalls, by Phil Koopman (lecture)
- Top 16 Embedded Security Pitfalls by Phil Koopman covers common pitfalls to avoid when designing your security plan.
- Secrecy vs. Integrity and Why Encryption Might Be the Wrong Choice by Phil Koopman shows that encryption doesn’t solve all of our security problems. In many cases, authentication and integrity are more important.
- Security Pitfalls in Cryptography contains some lessons learned that you can apply to avoid creating a bad cryptographic system.
Safety
For a general discussion of safety architectural patterns:
- Safety Architecture Patterns by Phil Koopman
Subsections:
Avoiding Single Points of Failure
For a general discussion on patterns for single points of failure:
- Single Points of Failure by Phil Koopman (YouTube lecture)
- Safety Requires No Single Points of Failure by Phil Koopman
- Self-Monitoring and Single Points of Failure by Phil Koopman
Patterns:
- Monitor-Actuator Pair Design Pattern
- How can we avoid a single-point of failure in a safety-critical component?
- Use two independent hardware components operating as a “monitor-actuator” CPU pair. The actuator CPU is the component that actually performs the computation or control function. An independent monitor Chip is used to detect when the actuator has failed and mitigate the failure (e.g., by resetting the actuator or triggering an emergency stop)
Critical System Isolation
For a general discussion on critical system isolation patterns:
- Critical System Isolation (slides) by Phil Koopman
Redundancy Management
For a general discussion on redundancy management patterns:
- Redundancy Management (slides)
- Redundant Input Processing for Safety, by Phil Koopman
Data Integrity
For a general overview on data integrity patterns:
- Data Integrity (slides only)
Testing
For a general overview of testing patterns, see:
- xUnit Test Patterns, by Gerard Meszaros
- xUnit Test Patterns (website)
Design for test patterns:
- Dependency Injection
- How do we design the system-under-test so that we can replace its dependencies at run time?
- The client provides the depended-on object to the system-under-test
- Dependency Lookup
- How do we design the system-under-test so that we can replace its dependencies at run time?
- The system-under-test asks another object to return the depended-on object before it uses it
- Humble Object
- How can we test a component that is tightly coupled to a framework which we cannot easily integrate into our test environment?
- We extract all the logic from the hard-to-test component into a component that is testable via synchronous tests. As a result, the Humble Object component becomes a very thin adapter layer that contains very little code. Each time the Humble Object is called by the framework, it delegates to the testable component.
- Test Hook
- How can we make code testable when it is coupled with hard-coded dependencies?
- We modify the behavior of the system-under-test or its dependencies to support testing by putting adding a functional hook or testing flag that can be changed during runtime
Cloud Computing
For a general overview of cloud computing patterns, see:
- Wikipedia: Cloud Computing, which discusses a variety of deployment, service, and architectural models

One Reply to “Design Pattern Catalogue”