Search code examples
c#design-patternsarchitecture

What design pattern should use in this case?


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.


Solution

  • It looks like you have at least two choices here:

    • use some collection which stores value to handle
    • an approach inspired by Chain-of-responsibility pattern

    Let's start from collection which stores value to handle

    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
    

    Chain-of-responsibility pattern

    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