I have big model, that aggragates data for buisness entity.
class BigObject
{
TypeA DataA { get;set; }
TypeB DataB { get;set; }
TypeC DataC { get;set; }
}
and have service, which fill fields of model from differents sources. Some data depends from another data
class DataService
{
public BigObject GetModel()
{
var model = new BigObject();
model.DataA = sourceServiceA.GetData();
model.DataB = sourceServiceB.GetData(model.DataA.Id);
model.DataC = sourceServiceC.GetData();
}
}
In method GetModel()
I need to configure, which fields should be filled, which should not. For example, I want to fill DataA
property, but don't want fill others. First idea is pass in method object BigObjectFilter
public BigObject GetModel(BigObjectFilter filter)
class BigObjectFilter
{
bool FillDataA { get; set; }
bool FillDataB { get; set; }
bool FillDataC { get; set; }
}
and initialize this object in DataService clients.
In GetObject
method I was going to add conditions like
if (filter.FillDataA)
{
model.DataA = sourceServiceA.GetData();
}
if (filter.FillDataC)
{
model.DataC = sourceServiceC.GetData();
}
I see, that this solution looks like bad practice. I would like to improve this construction. How can i improve it? I can't see, how to use builder pattern in this case, because i have requeired and optional data, one depends on the other.
It looks like you have at least two choices here:
At first, we need our class with properties to be filled:
public class BigObject
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
}
Then this is our class which will handle all your properties:
public class BigObjectHandler
{
Dictionary<string, Action> _handlerByproperty = new ();
BigObject _bigObject;
public BigObjectHandler(BigObject bigObject)
{
_bigObject = bigObject;
_handlerByproperty.Add("A", GetDataA);
_handlerByproperty.Add("B", GetDataB);
_handlerByproperty.Add("C", GetDataC);
}
public void Handle(string propertyName) =>
_handlerByproperty[propertyName].Invoke();
private void GetDataA()
{
_bigObject.A = 1; // sourceServiceA.GetData();
}
private void GetDataB()
{
_bigObject.B = 1; // sourceServiceA.GetData();
}
private void GetDataC()
{
_bigObject.C = 1; // sourceServiceA.GetData();
}
}
And then you can call the above code like this:
IEnumerable<string> propertiesToFill = new List<string> { "A", "B" };
BigObject bigObject = new ();
BigObjectHandler bigObjectMapHandler = new (bigObject);
foreach (var propertyToFill in propertiesToFill)
{
bigObjectMapHandler.Handle(propertyToFill);
}
OUTPUT:
A = 1
B = 1
If you have many if else
statements, then you can try to use "Chain-of-responsibility pattern". As wiki says:
the chain-of-responsibility pattern is a behavioral design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain. A mechanism also exists for adding new processing objects to the end of this chain
However, we will not stop execution if some of condition is met. Let me show an example.
At first, we need some abstraction of handler:
public abstract class BigObjectHandler
{
private BigObjectHandler _nextBigObjectHandler;
public void SetSuccessor(BigObjectHandler bigObjectHandler)
{
_nextBigObjectHandler = bigObjectHandler;
}
public virtual BigObject Execute(BigObject bigObject,
BigObjectFilter parameter)
{
if (_nextBigObjectHandler != null)
return _nextBigObjectHandler.Execute(bigObject, parameter);
return bigObject;
}
}
Then we need concrete implemenatation of these handlers for your properties. This properties will be filled
by your sourceServiceX.GetData()
:
public class BigObjectAHandler : BigObjectHandler
{
public override BigObject Execute(BigObject bigObject, BigObjectFilter filter)
{
if (filter.FillA)
{
bigObject.A = 1; // sourceServiceA.GetData();
}
return base.Execute(bigObject, filter);
}
}
And:
public class BigObjectBHandler : BigObjectHandler
{
public override BigObject Execute(BigObject bigObject, BigObjectFilter filter)
{
if (filter.FillB)
{
bigObject.B = 2; // sourceServiceB.GetData();
}
return base.Execute(bigObject, filter);
}
}
And:
public class BigObjectCHandler : BigObjectHandler
{
public override BigObject Execute(BigObject bigObject, BigObjectFilter filter)
{
if (filter.FillC)
{
bigObject.C = 3; // sourceServiceC.GetData();
}
return base.Execute(bigObject, filter);
}
}
And these are object with data:
public class BigObject
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
}
And some filter which will contain settings of what property should be filled:
public class BigObjectFilter
{
public bool FillA { get; set; } = true;
public bool FillB { get; set; }
public bool FillC { get; set; }
}
And then we can call the above code like this:
BigObjectHandler chain = new BigObjectAHandler();
BigObjectHandler objectBHandler = new BigObjectBHandler();
BigObjectHandler objectCHandler = new BigObjectCHandler();
chain.SetSuccessor(objectBHandler);
objectBHandler.SetSuccessor(objectCHandler);
BigObjectFilter bigObjectFilter = new BigObjectFilter();
bigObjectFilter.FillA = true;
BigObject vehicle = chain.Execute(new BigObject(), bigObjectFilter); // A = 1
It can be seen after code execution that onle property A
is handled. Output is:
A = 1
B = 1