Skip to content

Optimizing IStagedStrategyChain interface #227

@ENikS

Description

@ENikS

Strategy Chains

Historically, each Unity Container had a chain of build strategies that determines resolution algorithm. To manage these chains Unity provided two interfaces:

  • Interface IStagedStrategyChain
  • Interface IStrategyChain

Interface IStagedStrategyChain

Interface IStagedStrategyChain allowed creation and storage of a chain of strategies required to operate the container.

IStagedStrategyChain in Unity v4.0.1

Unity 4.0.1 defined this interface as follows:

/// <summary>
/// This interface defines a standard method to convert any <see cref="StagedStrategyChain{TStageEnum}"/> regardless
/// of the stage enum into a regular, flat strategy chain.
/// </summary>
public interface IStagedStrategyChain
{
    /// <summary>
    /// Convert this <see cref="StagedStrategyChain{TStageEnum}"/> into
    /// a flat <see cref="IStrategyChain"/>.
    /// </summary>
    /// <returns>The flattened <see cref="IStrategyChain"/>.</returns>
    IStrategyChain MakeStrategyChain();
}

Management of the strategies was implemented in StagedStrategyChain<TStageEnum> class.

The container had two of these chains, one for strategies such as array resolution, lifetime management, or building types.
The other chain was used inside BuilderStrategy to build types.

As you can see above the main purpose of the interface was to MakeStrategyChain once ready or changed.

IStagedStrategyChain in Unity v5.x

Unity v5 expanded the interface to include a method and an event:

/// <summary>
/// This interface defines a standard method to create multi staged strategy chain.
/// </summary>
/// <typeparam name="TStrategyType">The <see cref="System.Type"/> of strategy</typeparam>
/// <typeparam name="TStageEnum">The stage enum</typeparam>
public interface IStagedStrategyChain<in TStrategyType, in TStageEnum>
{

    /// <summary>
    /// Adds a strategy to the chain at a particular stage.
    /// </summary>
    /// <param name="strategy">The strategy to add to the chain.</param>
    /// <param name="stage">The stage to add the strategy.</param>
    void Add(TStrategyType strategy, TStageEnum stage);

    /// <summary>
    /// Signals that chain has been changed
    /// </summary>
    event EventHandler<EventArgs> Invalidated;
}

Interface IStrategyChain

Interface IStrategyChain allowed execution of created chain.

IStrategyChain in Unity v4.0.1

Unity 4.0.1 defined the interface as follows:

/// <summary>
/// Represents a chain of responsibility for builder strategies.
/// </summary>
public interface IStrategyChain : IEnumerable<IBuilderStrategy>
{
    /// <summary>
    /// Reverse the order of the strategy chain.
    /// </summary>
    /// <returns>The reversed strategy chain.</returns>
    IStrategyChain Reverse();

    /// <summary>
    /// Execute this strategy chain against the given context,
    /// calling the Buildup methods on the strategies.
    /// </summary>
    /// <param name="context">Context for the build process.</param>
    /// <returns>The build up object</returns>
    object ExecuteBuildUp(IBuilderContext context);

    /// <summary>
    /// Execute this strategy chain against the given context,
    /// calling the TearDown methods on the strategies.
    /// </summary>
    /// <param name="context">Context for the teardown process.</param>
    void ExecuteTearDown(IBuilderContext context);
}

IStrategyChain in Unity v5.x

With advent of LINQ the interface became redundant and was deprecated in favor of simple ToArray().

Problem

With new features being implemented these interfaces do not provide adequate functionality and require either expansion or replacement.

Solution

Instead of creating custom solution and adding missing API to the interface the decision has been made to derive IStagedStrategyChain from IDictionary. The interface is defined as follows:

public interface IStagedStrategyChain : IDictionary<UnityBuildStage, BuilderStrategy>
{
    /// <summary>
    /// Signals that the chain has been changed
    /// </summary>
    event StagedChainChagedHandler Invalidated;

    /// <summary>
    /// Add a stage
    /// </summary>
    /// <param name="stage"><see cref="ValueTuple"/> pairs to add</param>
    void Add((UnityBuildStage, BuilderStrategy) stage);

    /// <summary>
    /// Add a range of build stages
    /// </summary>
    /// <remarks>
    /// This method adds stages and fires notifications only once
    /// </remarks>
    /// <param name="stages">Array of <see cref="ValueTuple"/> pairs to add</param>
    void Add((UnityBuildStage, BuilderStrategy)[] stages);
}

and change event handler is declared like this:

public delegate void StagedChainChagedHandler(object sender, Type type);

Breaking Change

Hierarchy

Unity v4 and v5 allowed each container to have its own chain of strategies with child containers inheriting strategies from parent and adding their own as required.

This feature added significant overhead to overall performance and was replaced with simplified approach with only one strategy chain for all containers within child hierarchy.
In new implementation only root container has the chain of strategies and all child containers reuse the same chain.

To allow functionality similar to these available in previous version and custom strategy could check a Name of the container or the Level of the hierarchy to perform required initialization.

List vs Dictionary

The chain created in Unity v4 or v5 would be a list of strategies. The strategies could be added to the chain multiple times and the chain would accumulate them all.

Removing of added strategies was not possible with provided API.

Implementation of the chain as a dictionary places several restrictions on how strategies are managed:

  1. Only one strategy is allowed per step

    1.1 Adding the same step twice throws an exception
    1.2 Setting the step with indexer chain[step] = strategy replaces the strategy

  2. Number of steps is determined by the number of elements in TStep enumerable, it can not be expanded. When expansion of existing step is required consider deriving from provided strategy and replacing it with custom solution.

See Also #219

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions