Based on feedback on my previous question, I made many small adjustments to the code, added more APIs and tried to follow through with delivering on the excellent advice I have received in regards to my code. I have updated my code with small summaries to provide more documentation.
Here is the updated interface.
namespace Constrainable
{
public interface IConstrainableSet<T>
{
T AlterInput(T Value);
T AlterInputConstrainedStore(T Value);
ConstrainableSet<T> ChainLinkClone();
ConstrainableSet<N> ChainLinkNew<N>(Predicate<N> CheckBeforeSet = null, Func<N, N> ModifyBeforeSet = null, Action<ConstrainableSet<N>> FailAction = null, Action<ConstrainableSet<N>> SuccessAction = null);
T ConstrainedStore(T Value);
T GetStoredData();
bool IsValid(T Value);
}
public interface IConnectable
{
public object Root { get; set; }
public object Next { get; set; }
public object Parent { get; set; }
}
}
And here is the new code.
using System;
namespace Constrainable
{
public class Connectable : IConnectable
{
public object Root { get; set; }
public object Next { get; set; }
public object Parent { get; set; }
}
/// <summary>
/// ConstrainableSet is intended to Allow full control over what data gets set in the inner field `data,
/// You can optioning modify the input. This is also intended to be a test of idea's,
/// new and/or interesting ways of accomplishing tasks.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ConstrainableSet<T> : Connectable, IConstrainableSet<T>
{
public ConstrainableSet(Predicate<T> CheckBeforeSet = default, Func<T, T> ModifyBeforeSet = default, Action<ConstrainableSet<T>> FailAction = default, Action<ConstrainableSet<T>> SuccessAction = default)
{
_failAction = FailAction;
_checkBeforeSet = CheckBeforeSet;
_modifyBeforeSet = ModifyBeforeSet;
_successAction = SuccessAction;
}
#region ControlFlow
/// <summary>
///`_failAction` field recieves the `FailAction` parameter in the constructor.
///This Parameter is a callback that is run when a failure occurs in `ConstrainedStore()`,
///for both Callbacks the whole `ConstrainableSet` Object is passed as a parameter.
/// </summary>
private readonly Action<ConstrainableSet<T>> _failAction;
/// <summary>
/// _successAction field recieves the `SuccessAction` parameter in the constructor.
/// This Parameter is a `Callback` , this function is called on in `ConstrainedStore()`
/// and only after successful set of the inner private field `data`.
/// </summary>
private readonly Action<ConstrainableSet<T>> _successAction;
/// <summary>
/// _checkBeforeSet is a bool lambda expression which evaluates the Parameter
/// to be set, and must return true for the data to be set
/// </summary>
private readonly Predicate<T> _checkBeforeSet;
/// <summary>
/// _modifyBeforeSet this routine Takes type T as input and returns type T
/// this function is for Encryption or Concatenating or anything that involves modifying the parameter passed in to it
/// </summary>
private readonly Func<T, T> _modifyBeforeSet;
#endregion
private T data;
/// <summary>
/// The ConstrainableStore Function is the combined input of
/// 3 parameters in this fashiion (PreCheck ? SuccessAction : FailAction)
/// if there is no _checkBeforeSet function defined then this simply returns the value passed in,
/// if there is an _checkBeforeSet function defined, it calls that function and based on success or failure.
/// if that Action is defined for success it moves the value into storage then calls SuccessAction,
/// if there is no success action it sets the successful checked value and returns the value,
/// if there is no FailAction defined at this point then it returns the default value for this type
/// </summary>
/// <param name="Value"></param>
/// <returns>T</returns>
public virtual T ConstrainedStore(T Value)
{
if (_checkBeforeSet != default)
{
if (_checkBeforeSet(Value))
{
if (_successAction != default)
{
data = Value;
_successAction(this);
}
data = Value;
return Value;
}
if (_failAction != default)
{
_failAction(this);
}
return default;
}
return Value;
}
/// <summary>
/// IsValid runs the define checkBeforeSet function and returns result.
/// </summary>
/// <param name="Value"></param>
/// <returns>bool</returns>
public virtual bool IsValid(T Value)
{
if (_checkBeforeSet != null)
{
return _checkBeforeSet(Value);
}
return false;
}
/// <summary>
/// This is the interesting function,
/// I think with this simple function actionable values can be achieved
/// many tasks could benefit from Actionable `Validated` values imho.
/// Actionable values is values that can be used in some way(s) to get a new value or object,
/// or encrypting the value passed in, this could be used for something like that.
/// </summary>
/// <param name="Value"></param>
/// <returns></returns>
public virtual T AlterInput(T Value)
{
if (_modifyBeforeSet != default)
{
return _modifyBeforeSet(Value);
}
return Value;
}
//Combined usage
public virtual T AlterInputConstrainedStore(T Value)
{
return ConstrainedStore(AlterInput(Value));
}
/// <summary>
/// I made a simple way to link objects together
/// I simply needed to pinpoint Cloning and Creation and
/// push the proper values to these objects during cloning or creation.
/// </summary>
/// <returns></returns>
public ConstrainableSet<T> ChainLinkClone()
{
if(Root == default)
{
Root = this;
}
ConstrainableSet<T> newpo = new ConstrainableSet<T>(_checkBeforeSet, _modifyBeforeSet, _failAction, _successAction);
IConnectable Iterator;
if (Next != default)
{
Iterator = Next as IConnectable;
while (Iterator.Next != default)
{
Iterator = Iterator.Next as IConnectable;
}
Iterator.Next = newpo;
newpo.Root = Root;
newpo.Parent = this;
return newpo;
}
else
{
Next = newpo;
newpo.Root = Root;
newpo.Parent = this;
return newpo;
}
}
public ConstrainableSet<N> ChainLinkNew<N>(Predicate<N> CheckBeforeSet = default, Func<N, N> ModifyBeforeSet = default, Action<ConstrainableSet<N>> FailAction = default, Action<ConstrainableSet<N>> SuccessAction = default)
{
if (Root == default)
{
Root = this;
}
ConstrainableSet<N> newpo = new ConstrainableSet<N>(CheckBeforeSet, ModifyBeforeSet, FailAction, SuccessAction);
IConnectable Iterator;
if (Next != default)
{
Iterator = Next as IConnectable;
while (Iterator.Next != default)
{
Iterator = Iterator.Next as IConnectable;
}
Iterator.Next = newpo;
newpo.Root = Root;
newpo.Parent = this;
return newpo;
}
else
{
Next = newpo;
newpo.Parent = this;
newpo.Root = Root;
return newpo;
}
}
//returns the data field.
public T GetStoredData()
{
return data;
}
}
}
I would also like to provide a example of usage. This example demonstrates the new ChainLinkNew Function which can be used in the SuccessCallback function to chain validations and data stores together, or in the way I accomplish this below. Any input on my approach is highly appreciated.
public Test()
{
ConstrainableSet<int> programmable = new ConstrainableSet<int>(
CheckBeforeSet: x => x == 100
).ChainLinkNew<int>(
CheckBeforeSet: x => x == 10 | x == 1,
ModifyBeforeSet: x => x / 10,
FailureCallBack,
SuccessCallBack)
.Root as ConstrainableSet<int>;
void FailureCallBack(ConstrainableSet<int> set)
{
}
void SuccessCallBack(ConstrainableSet<int> set)
{
}
((ConstrainableSet<int>)programmable.Next).AlterInputConstrainedStore(programmable.ConstrainedStore(100));
}