Skip to content

Commit dce96b7

Browse files
author
David Fallah
committed
[Breaking] Use builder pattern for store construction
1 parent c037166 commit dce96b7

File tree

4 files changed

+145
-38
lines changed

4 files changed

+145
-38
lines changed

src/AsyncRedux/Store.cs

+3-13
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,10 @@ internal sealed class Store<TState> : IObservableStore<TState>
1717
/// <inheritdoc />
1818
public Store(
1919
[NotNull] Reducer<TState> reducer,
20-
[CanBeNull] TState initialState = default,
21-
[NotNull] [ItemNotNull] params Middleware<TState>[] middleware)
20+
[CanBeNull] TState initialState,
21+
[NotNull] [ItemNotNull] IEnumerable<Middleware<TState>> middleware)
2222
{
23-
if (middleware is null)
24-
{
25-
throw new ArgumentNullException(nameof(middleware));
26-
}
27-
28-
if (middleware.Any(it => it is null))
29-
{
30-
throw new ArgumentException("Middleware delegate cannot be null", nameof(middleware));
31-
}
32-
33-
_reducer = reducer ?? throw new ArgumentNullException(nameof(reducer));
23+
_reducer = reducer;
3424
_dispatcher = CreateDispatcher(middleware);
3525
_bus = BusSetup.CreateBus();
3626
State = initialState;

src/AsyncRedux/StoreSetup.cs

+87-16
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,103 @@
1-
using JetBrains.Annotations;
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using JetBrains.Annotations;
25

36
namespace AsyncRedux
47
{
58
/// <summary>
69
/// Used to configure and return a new Redux store.
710
/// </summary>
8-
/// <remarks>
9-
/// At present, no configuration is possible for the store. This class exists to support future versions
10-
/// of this project that may allow for configuration, but more importantly to ensure clients access the
11-
/// store through an interface and not a direct class.
12-
/// </remarks>
1311
[PublicAPI]
1412
public static class StoreSetup
1513
{
1614
/// <summary>
17-
/// Creates and returns a new store.
15+
/// Returns a <see cref="Builder{TState}" /> to configure and create a new store.
1816
/// </summary>
1917
/// <typeparam name="TState">The type of the state handled by the store.</typeparam>
20-
/// <param name="reducer">The root reducer to apply to dispatched actions.</param>
21-
/// <param name="initialState">The initial state.</param>
22-
/// <param name="middleware">The middleware to apply to the store.</param>
23-
/// <returns>A new store.</returns>
24-
public static IObservableStore<TState> CreateStore<TState>(
25-
[NotNull] Reducer<TState> reducer,
26-
[CanBeNull] TState initialState = default,
27-
[NotNull] [ItemNotNull] params Middleware<TState>[] middleware)
18+
/// <returns>A <see cref="Builder{TState}" />.</returns>
19+
public static Builder<TState> CreateStore<TState>() => new Builder<TState>();
20+
21+
/// <summary>
22+
/// Configures and builds instances of <see cref="IObservableStore{TState}" />.
23+
/// </summary>
24+
/// <typeparam name="TState">The type of the state handled by the store.</typeparam>
25+
/// <remarks>
26+
/// Middleware plugin libraries can and should extend this class with methods to configure middleware they provide.
27+
/// </remarks>
28+
public class Builder<TState>
2829
{
29-
return new Store<TState>(reducer, initialState, middleware);
30+
private readonly List<Middleware<TState>> _middleware;
31+
32+
private TState _initialState;
33+
private Reducer<TState> _reducer;
34+
35+
/// <summary>
36+
/// Initializes a new instance of the <see cref="Builder{TState}" /> class.
37+
/// </summary>
38+
public Builder()
39+
{
40+
_middleware = new List<Middleware<TState>>();
41+
}
42+
43+
/// <summary>
44+
/// Builds and returns a new store based on the configuration of this builder.
45+
/// </summary>
46+
/// <returns>A new store.</returns>
47+
/// <exception cref="InvalidOperationException">This builder has not been configured correctly.</exception>
48+
public IObservableStore<TState> Build()
49+
{
50+
if (_reducer is null)
51+
{
52+
throw new InvalidOperationException("Root reducer for store has not been configured");
53+
}
54+
55+
return new Store<TState>(_reducer, _initialState, _middleware);
56+
}
57+
58+
/// <summary>
59+
/// Configures the root reducer for the store. This <b>must</b> be set before building the store.
60+
/// </summary>
61+
/// <param name="reducer">The root reducer to apply to dispatched actions.</param>
62+
/// <returns>This builder.</returns>
63+
public Builder<TState> FromReducer([NotNull] Reducer<TState> reducer)
64+
{
65+
_reducer = reducer ?? throw new ArgumentNullException(nameof(reducer));
66+
return this;
67+
}
68+
69+
/// <summary>
70+
/// Configures the middleware to apply to the store. Middleware will be applied in the order that it is provided.
71+
/// </summary>
72+
/// <param name="middleware">The middleware.</param>
73+
/// <returns>This builder.</returns>
74+
public Builder<TState> UsingMiddleware([NotNull] [ItemNotNull] params Middleware<TState>[] middleware)
75+
{
76+
if (middleware is null)
77+
{
78+
throw new ArgumentNullException(nameof(middleware));
79+
}
80+
81+
if (middleware.Any(it => it is null))
82+
{
83+
throw new ArgumentException("Middleware delegate cannot be null", nameof(middleware));
84+
}
85+
86+
_middleware.AddRange(middleware);
87+
return this;
88+
}
89+
90+
/// <summary>
91+
/// Configures the initial state for the store. If not specified, defaults to the default value for
92+
/// <typeparamref name="TState" />.
93+
/// </summary>
94+
/// <param name="initialState">The initial state.</param>
95+
/// <returns>This builder.</returns>
96+
public Builder<TState> WithInitialState([CanBeNull] TState initialState)
97+
{
98+
_initialState = initialState;
99+
return this;
100+
}
30101
}
31102
}
32103
}

test/AsyncRedux.Tests/ObservableStoreSpec.cs

+8-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ public class ObservableStoreSpec
2020
/// <inheritdoc />
2121
public ObservableStoreSpec()
2222
{
23-
_store = StoreSetup.CreateStore<State>(Reducers.PassThrough);
23+
_store = StoreSetup.CreateStore<State>()
24+
.FromReducer(Reducers.PassThrough)
25+
.Build();
2426
}
2527

2628
public static IEnumerable<object[]> ActionSequenceExamples
@@ -144,7 +146,11 @@ Task HandleDispatchedAction(object action)
144146
[Fact]
145147
internal void Subscribers_Implementing_IStoreSubscriber_Should_Receive_Store_On_Subscription()
146148
{
147-
var store = StoreSetup.CreateStore(Reducers.Replace, new State(1, true));
149+
var store = StoreSetup.CreateStore<State>()
150+
.FromReducer(Reducers.Replace)
151+
.WithInitialState(new State(1, true))
152+
.Build();
153+
148154
var subscriber = new Subscriber();
149155

150156
store.Subscribe(subscriber);

test/AsyncRedux.Tests/StoreSpec.cs

+47-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Shouldly;
77
using Xunit;
88

9+
// ReSharper disable AssignNullToNotNullAttribute
910
// ReSharper disable MemberCanBePrivate.Global
1011

1112
namespace AsyncRedux.Tests
@@ -68,16 +69,26 @@ public static IEnumerable<object[]> StateExamples
6869
internal void Store_Construction_Should_Throw_For_Null_Middleware_Collection()
6970
{
7071
Should.Throw<ArgumentNullException>(
71-
// ReSharper disable once AssignNullToNotNullAttribute
72-
() => StoreSetup.CreateStore<State>(Reducers.PassThrough, default, null));
72+
() => StoreSetup.CreateStore<State>()
73+
.FromReducer(Reducers.PassThrough)
74+
.UsingMiddleware(null)
75+
.Build());
7376
}
7477

7578
[Fact]
7679
internal void Store_Construction_Should_Throw_If_Any_Middleware_Is_Null()
7780
{
7881
Should.Throw<ArgumentException>(
79-
// ReSharper disable once AssignNullToNotNullAttribute
80-
() => StoreSetup.CreateStore<State>(Reducers.PassThrough, default, Middleware.IncrementInt, null));
82+
() => StoreSetup.CreateStore<State>()
83+
.FromReducer(Reducers.PassThrough)
84+
.UsingMiddleware(Middleware.IncrementInt, null)
85+
.Build());
86+
}
87+
88+
[Fact]
89+
internal void Store_Construction_Should_Throw_If_Reducer_Is_Not_Specified()
90+
{
91+
Should.Throw<InvalidOperationException>(() => StoreSetup.CreateStore<State>().Build());
8192
}
8293

8394
[Theory]
@@ -87,7 +98,10 @@ internal async Task Store_Should_Apply_Actions_In_Order_Of_Dispatch(
8798
object[] actions,
8899
State expectedFinalState)
89100
{
90-
var store = StoreSetup.CreateStore(Reducers.Replace, initialState);
101+
var store = StoreSetup.CreateStore<State>()
102+
.FromReducer(Reducers.Replace)
103+
.WithInitialState(initialState)
104+
.Build();
91105

92106
foreach (var action in actions)
93107
{
@@ -97,11 +111,33 @@ internal async Task Store_Should_Apply_Actions_In_Order_Of_Dispatch(
97111
store.State.ShouldBe(expectedFinalState);
98112
}
99113

114+
[Fact]
115+
internal void Store_Should_Concatenate_Middleware_If_Multiple_Sets_Provided_During_Construction()
116+
{
117+
var initialState = new State(0, false);
118+
var store = StoreSetup.CreateStore<State>()
119+
.FromReducer(Reducers.Replace)
120+
.WithInitialState(initialState)
121+
.UsingMiddleware(Middleware.IncrementInt)
122+
.UsingMiddleware(Middleware.NegateBool)
123+
.Build();
124+
125+
store.Dispatch(new ChangeInt(1));
126+
store.State.IntProperty.ShouldBe(2);
127+
128+
store.Dispatch(new ChangeBool(false));
129+
store.State.BoolProperty.ShouldBe(true);
130+
}
131+
100132
[Theory]
101133
[MemberData(nameof(StateExamples))]
102134
internal void Store_Should_Have_Provided_Initial_State_After_Construction(State initialState)
103135
{
104-
var store = StoreSetup.CreateStore(Reducers.Replace, initialState);
136+
var store = StoreSetup.CreateStore<State>()
137+
.FromReducer(Reducers.Replace)
138+
.WithInitialState(initialState)
139+
.Build();
140+
105141
store.State.ShouldBe(initialState);
106142
}
107143

@@ -110,7 +146,11 @@ internal void Store_Should_Pass_Dispatched_Actions_Through_Middleware_Before_Red
110146
{
111147
var initialState = new State(0, false);
112148
var middleware = new Middleware<State>[] { Middleware.IncrementInt, Middleware.NegateBool };
113-
var store = StoreSetup.CreateStore(Reducers.Replace, initialState, middleware);
149+
var store = StoreSetup.CreateStore<State>()
150+
.FromReducer(Reducers.Replace)
151+
.WithInitialState(initialState)
152+
.UsingMiddleware(middleware)
153+
.Build();
114154

115155
store.Dispatch(new ChangeInt(1));
116156
store.State.IntProperty.ShouldBe(2);

0 commit comments

Comments
 (0)