Hyrum’s Law

Hyrum’s Law: “With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody.”

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.

SOLID

SOLID is a mnemonic acronym for five software design principles: Single Responsibility Principle, Open-Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, Dependency Inversion Principle.

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.

Continuous Delivery [CD]

Continuous delivery (CD), sometimes called continuous deployment, is a software development practice where changes to a code base are automatically built, tested, and deployed for release.

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.

Side-Channel Attack

A side-channel attack involves exploiting information leaked during the execution of a computer program/system. Side-channel attacks take advantage of unintended side effects in normal operations that can reveal sensitive information (often cryptographic keys or higher access privileges). Common side-channel attack vectors include power consumption, EM radiation, timing variations, acoustic signals (e.g., keystrokes), temperature, and processor caches.

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.

Kconfig

Kconfig is a software configuration system. It is most notable for its use with the Linux kernel and Zephyr projects.

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.

Devicetree

Devicetree is a data structure and language used to describe the hardware configuration of a device. It is used primarily by Zephyr and the Linux Kernel.

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.

Strategy Pattern

The Strategy pattern separates the interface of an algorithm, operation, or behavior from the implementation, allowing implementations to become interchangeable. The implementation can then be varied independently from the client code that uses the interface.

Table of Contents:

  1. Aliases
  2. Context
  3. Forces
  4. Solution
  5. Consequences
  6. Known Uses
    1. Examples
  7. Implementation Variations
  8. Related Patterns
  9. References
Note

For simplicity’s sake, we use the word “algorithm” below to stand in for “algorithm, operation, or behavior.” These terms are equivalent in the Strategy pattern context.

Aliases

  • Algorithm Object

Context

Systems can be broken down into components with distinct purposes. For example, you can find operations that create or read data, that operate on the data, and that output the data somewhere else.

Sometimes, a single implementation for an operation will suffice. In other cases, the system would benefit from being able to support multiple implementations. For example:

  • New hardware revisions may have hardware-enabled capabilities that previous revisions did not (e.g., a cryptographic co-processor, additional memory)
  • Different data streams may benefit from different compression algorithms
  • Users may desire different output formats
  • Filtering methods may vary depending on the situation

Different algorithm implementations will be appropriate at different times. Sometimes, you will need to support run-time selection, and other times compile-time. Sometimes, the user of an algorithm will require an implementation that you didn’t think of, and will benefit from being able to add their own.

You need some way to separate the implementation of the algorithm from the code that uses the algorithm. It is difficult to add new implementations and change existing ones if they are integral (i.e., tightly coupled) to the client code.

How can you structure your system to support algorithm selection and extension with minimal overhead and rework?

Forces

This pattern attempts to balance the following forces:

Solution

The basic principle behind the Strategy pattern is to decouple the algorithm use from the implementation(s) of the algorithm. With the Strategy pattern, a component forwards or delegates some aspect of its behavior to a separate Strategy component. The behavior can be changed by selecting the desired Strategy implementation.

By decoupling the algorithm implementation from the code that uses the algorithm, you can enable users to customize the implementation without requiring changes to the code that interacts with the algorithm.

You can implement the Strategy pattern through this process:

  1. Identify an algorithm that needs to be configurable or varied (or might need to be configured in the future)
  2. Define a standard interface for the algorithm
    • This should be an interface that supports all possible desirable implementations of the algorithm
  3. Couple client code to the standard interface rather than a specific implementation
  4. Create implementations of the algorithm interface
  5. Provide a mechanism for selecting the desired algorithm

Conceptually, the structure of the pattern is:


Strategy is an abstract interface with one or more concrete implementations. The client code interacts with the abstract strategy interface, rather than with a specific implementation.

Client code only knows about the algorithm abstraction, not the particular implementation. This allows any suitable implementation of the abstraction to work with the client code.

Successfully implementing the strategy pattern requires that the Strategy interface is sufficiently well defined and general enough to support desired implementations. Interface stability is critical. You should not have to change the Strategy interface or the client code to support a new algorithm.

Consequences

Benefits of the pattern include:

  • The implementation of an algorithm is decoupled from the code that interacts with that operation, allowing them to change independently.
  • Strategies provide different implementations of the same behavior, allowing users to select or create implementations with the desired performance/memory tradeoffs.
  • The design reflects a better Separation of Concerns: use of the algorithm is separated from implementation of the algorithm.
  • The design enables use of the Open-Closed Principle, since implementations can be added or extended without requiring changes to code that interacts with the core algorithm interface.

Tradeoffs include:

  • Changes to the Strategy interface will propagate not only to the caller but also to every implementation of the Strategy. Up-front effort must be invested in getting this interface right.
  • Strategy implementations must be discoverable by users, and they must have enough context/documentation for users to evaluate them.
  • Strategy adds a layer of indirection. While suitable for most cases, there are some cases where tighter coupling will be beneficial.
  • To support a wide range of implementations, the Strategy interface must support sufficient data exchange. It is likely that some implementations will not require all the provided data, resulting in data/space overhead. If this is a measured problem, tighter coupling might be warranted.

Known Uses

  • The primary use of the Strategy pattern is to support different variants for an algorithm. These might represent different space/time tradeoffs or simply different approaches.
    • Design Patterns: Elements of Reusable Object Oriented Software provides the following examples:
      • Using the Strategy pattern for a text compositing algorithm, where you might have a SimpleLayoutStrategy and a TeXLayoutStrategy.
      • A compiler might use the Strategy pattern to allow different register allocation schemes for different target machines.
  • If you have several similar classes or modules that differ only in behavior, you can use the Strategy pattern to reduce duplication. A core structure can be created, with variable behavior handled as separate Strategy implementations. The desired behavior configured by selecting the appropriate implementation.
  • The Strategy pattern provides an alternative to having many conditional statements for controlling behavior. You can encapsulate the conditioned behaviors in a Strategy. Behavior is then controlled by selecting the desired implementation, allowing you to eliminate the conditional branching.
  • Strategy can hide the details of the algorithm implementation (e.g., a complex, implementation-specific data structure) in adherence to the principle of Information Hiding.
  • Library and framework authors can use the Strategy pattern to allow users to override default implementations,. This way, users can to fine-tune the library as needed..
  • Even if you don’t need to change algorithms or operations at run-time, being able to adjust implementations during the development process while mitigating changes in other components.

Examples

  • The C++ standard library frequently uses the Strategy pattern (often via “Policies” supplied via template parameters). Some examples include:
  • Locking can be easily implemented as a Strategy, allowing users to customize locking behavior for the system (e.g., use the RTOS mutex, disable interrupts). You could also disable locking by providing an implementation that does nothing.
    • C++ has a BasicLockable requirement, for example, which allows the use of any type that provides lock and unlock methods.
  • The Embedded Artistry Arduino Logging Library (GitHub) uses the Strategy pattern by providing a standard logging interface along with several logger implementations. The implementation can be changed while allowing the logger clients to remain unchanged.
  • Practical Decoupling Techniques Applied to a C-based Radio Driver shows a use of the Strategy pattern to decouple the driver from the underlying SPI communication bus implementation.
  • Payments are useful to handle in a Strategy-based approach (one which can be selected by users at runtime). Different users wish (or need) to use different payment processors, while the applications’s need to process a payment is fixed.
  • In Making Embedded Systems: Design Patterns for Great Software, Elecia White uses an example of processing ADC data.

    Let’s go back to the data-driven system in Figure 6-2, where analog data is digitized by an ADC, attenuated by the processor, and then sent back to analog via the DAC. What if you weren’t sure you wanted to attenuate the signal? What if you wanted to invert it? Or amplify the signal? Or add another signal to it?

    You could use a state machine, but it would be a little clunky. Once on every processing pass, the code would have to check which algorithm to use based on the state variable.

    Another way to implement this is to have a pointer to a function (or object) that processes data. You’ll need to define the function interface so every data-processing function takes the same arguments and returns the same data type. But if you can do that, you can change the pointer on command, thereby changing how your whole system works (or possibly how a small part of your system modifies the data).

  • Some examples from a discussion in the community forum:
    • Strategy for radio scheduling algorithms
    • Battery charging strategies and protocols
    • For a product that provides a UI of a magnetic sensor and LED, define a strategy interface that allows the UI to be customized per product (e.g., some interact by swiping the magnet, others by attaching/removing the magnet).
  • The following pieces of software analyzed in the Designing Embedded Software for Change course make use of the Strategy pattern to decouple the driver control code from the communication bus read and write operations:

Implementation Variations

The core idea of the Strategy pattern is to define an interface for an algorithm and decouple from the specific algorithm used to allow ease of variation. This can be applied in several ways.

The canonical approach has the Strategy pattern implemented through an (abstract) base class interface with one or more derived implementations. A function pointer or other Callable type (or struct of several Callables) works just as well and is often a superior way to handle small Strategy interfaces.

The binding time of the pattern can also vary. The canonical presentation of the Strategy pattern uses virtual inheritance, which allows for changing the desired strategy at run-time. You can also control which implementation is used at compile-time (hard-coding, C++ templates, C++ concepts, Rust traits, preprocessor conditionals, etc.) or link time (e.g., selectively compiling and linking in the desired implementation for an interface).

Futher reading

Policy-Based Design and the Curiously Recurring Template Pattern (CRTP) in C++ are often described as a compile-time implementation of the Strategy pattern.

You could also view Strategy methods/objects similarly to “optional steps” in the Template Method Pattern. If there’s a defined Strategy (e.g., a valid pointer to a Strategy object), the caller will use it normally. If one isn’t supplied, a default behavior is carried out instead. This can simplify the situation for users, as they do not have to bother with defining a Strategy at all unless the default behavior is insufficient.

You might also implement an optional strategy by defaulting to a “Null Strategy” implementation (or “Null Object”), which does nothing. For example, a locking/exclusion strategy might provide a NullLock implementation that does nothing, allowing locking to be disabled.A common example in this category is providing a NullLock implementation for a locking strategy. If locking is not needed, the NullLock can be used. Otherwise, users could select from a MutexLock, InterruptLock, etc.

Another considerable source of variation is how data is exchanged with the Strategy implementation:

  • Data can be passed in as parameters to the Strategy operations (“take the data”)
  • A context/reference (e.g., to the calling code) is passed as an argument, and the Strategy implementation can request data as needed.
  • The Strategy implementation can request data from another source (e.g., a central data store)
  • The Strategy Pattern can be viewed a larger-scale extension of the ideas behind the Template Method Pattern: Strategy varies an algorithm or operation in its entirety, where Template Method is used to vary individual steps of an algorithm while enforcing an overall algorithm structure.
    • Others distinguish these two patterns by saying that Template Method uses inheritance while Strategy uses delegation (delegating the algorithm to another section of code). We find this distinction as artificially limiting, since the idea behind Template Method can be applied even without inheritance and can also be viewed as delegation.
  • The State pattern is like the Strategy pattern in structure and implementation, but the intent of the pattern is different (encapsulate state-dependent behavior vs encapsulate an algorithm).
  • The Null Object or Nullable pattern is often used with the Strategy pattern.

References

  • Design Patterns: Elements of Reusable Object-Oriented Software by Gamma et al.

    The key to applying the Strategy pattern is designing interfaces for the strategy and its context that are general enough to support a range of algorithms.

  • Making Embedded Systems: Design Patterns for Great Software, 2nd edition, by Elecia White

    Sometimes it isn’t the data that needs to be selected, depending on the situation. Instead, sometimes the path of your code needs to change based on the environment.

    Some embedded systems are too constrained to be able to change the algorithm on the fly. However, you probably still want to use the strategy pattern concepts to switch algorithms during development. The strategy pattern helps you separate data from code and enforces a relatively strict interface to different algorithms.

  • Real-Time Software Design for Embedded Systems by Hassan Gomaa

    An algorithm object encapsulates an algorithm used in the problem domain. This kind of object is more prevalent in real-time, scientific, and engineering domains. Algorithm objects are used when there is a substantial algorithm used in the problem domain that can change independently of the other objects. Simple algorithms are usually operations of an entity object, which operate on the data encapsulated in the entity object. However, in many scientific and engineering domains, complex algorithms need to be encapsulated in separate objects because they are frequently improved independently of the data they manipulate, for example, to improve performance or accuracy.

  • The Strategy Pattern – MC++ BLOG

  • How to model the strategy pattern in Rust? – Stack Overflow

git

Git is an open-source distributed version control system, and currently reigning champion among the VCS options. This page collects articles, useful references, and tips for using git.

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.

Elliptic Curve Cryptography [ECC]

3 November 2023 by Phillip Johnston • Last updated 29 November 2023Elliptic Curve Cryptography (ECC) is an alternative to prime-number based cryptographic algorithms like RSA. ECC is especially interesting to embedded developers due to its efficiency gains. ECC provides higher security with shorter key lengths (a 256-bit ECC key is roughly equivalent to a 3072-bit RSA key in terms of security) It requires less computational power and memory Elliptic curves are applicable for key agreement, digital signatures, and pseudo-random generators. ECC is more often used for signature generation than full-on encryption, but it can be used for such purposes by …

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.