Home > database >  Transfer and wait for data between classes that are triggered together by same trigger C#
Transfer and wait for data between classes that are triggered together by same trigger C#

Time:09-22

I have 2 different classes (under same namespace) which implement some same abstract method.

public class SomeClass1 : SomeBaseClass
    {    
        public override ISomeTable Calculate(string s)
        {
            //some logic
            return someTable1;
        }    
    }

public class SomeClass2 : SomeBaseClass
        {    
            public override ISomeTable Calculate(string s)
            {
                //wait for logic from SomeClass1 to the another logic in SomeClass2
                return someTable2;
            }    
        }

Calculate method is triggered by same source for the two classes. Can I transfer some logic data (a dict for example) from class 1 to class 2 with waiting for class 1 to finish its calculations?

CodePudding user response:

What you're looking for is the "chain of responsibility" design pattern.

There's quite a few crucial design decisions that are impossible to make given the limited information provided, but need careful consideration:

  • Since ISomeTable appears to be an interface, how will you decide which implementing class is used for your context data (someTable2)? Does the caller decide that, or your event handlers? It's clearly mutable, but what about polymorphic? Having the Calculate method's return type being ISomeTable implies it could return any number of implementers of that interface. And chaining those method calls together could become problematic.
  • Is the caller responsible for providing the someTable instance? Or is it provided by the initial handler?
  • Should the handlers be aware of who they're passing it on to? Or should there be an event "manager" that handles passing it around to the next one? My example below is an intrusive implementation (self-aware), but that's certainly not the only way of implementation.
  • Returning ISomeTable from your abstract Calculate method may not be the optimal choice; especially since we're handing it around as a parameter anyways.
  • There are no class constructors in this implementation; you may want to store the context data (someTable) as a member instance in your base class, and access it through your inheritance instead of passing it on with the method call. This context data needs to be shared per your requirements, but the most efficient (and safe) way of doing so should be decided and tested sooner than later in the development process, since it does affect the API's primary method (Calculate).
  • I would probably enforce the factory creation pattern by providing static Create methods for your event handlers to ensure initialization is done correctly, and enforce correct usage of the handler classes.

I threw together a very basic implementation based on what I could extrapolate (with a few assumptions) from your example code. This is primarily to point you in the right direction; it's definitely not production-ready, but hopefully will be helpful.

Calling the below program returns the final combined string, built by the chained event handlers from my dummy SomeClass:

PS ~\so\testapp> dotnet run
"some string (from TableHandler1), some string (from TableHandler2)"
namespace so {

    // Our base class all handlers should derive from
    public abstract class TableHandler
    {
        public TableHandler? NextHandler { get; init; }
        public abstract ISomeTable Calculate(ISomeTable table, string str);
    }

    // First Implementation for TableHandler
    public class TableHandler1 : TableHandler
    {
        public override ISomeTable Calculate(ISomeTable table, string str) {
            // Ensure we were given a valid instance for storing our logic data
            if(table is null)
                throw new ArgumentNullException();

            // Perform our logic with `str` argument
            table.AddString(str   " (from TableHandler1)");

            // Hand off to next handler, returning the result that propogates back up the chain
            if(NextHandler is not null)
                table = NextHandler.Calculate(table, str);

            return table;
        }
    }

    // Second implementation (less verbose) for TableHandler
    public class TableHandler2 : TableHandler
    {
        public override ISomeTable Calculate(ISomeTable table, string str) {
            if(table is null) throw new ArgumentNullException();
            table.AddString(str   " (from TableHandler2)");
            return NextHandler?.Calculate(table, str) ?? table;
        }
    }

    // Dummy api interface for testing
    public interface ISomeTable {
        public string Data { get; set; }
        void AddString(string str);
    }

    // Dummy implementation for testing
    public class SomeTable : ISomeTable {
        public string Data { get; set; } = String.Empty;
        public void AddString(string str) => $"{Data}, {str}";
    }

    // Run it
    public class Program
    {
        // Example callsite; note how I'm initializing the chained handlers
        public static void Main(string[] args)
        {
            var handler = new TableHandler1() { NextHandler = new TableHandler2() };
            var someTable = handler.Calculate(new SomeTable(), "some string");
            Console.WriteLine($"\"{someTable.Data}\"");
        }
    }
}

CodePudding user response:

This is a forced approach that doesn't scale very well if you have multiple classes which should have the same functionality. Keep in mind that caching some values and modifying the actual baseclass SomeBaseClass can be way more efficient and easier to maintain. However the provided infos you are currently giving are limited. Letting SomeClass2 inherit from SomeClass1 and calling base() before calculation could also be an option.

public class SomeClass1 : SomeBaseClass
{
    public override ISomeTable Calculate(string s)
    {
        //some logic
        return someTable1;
    }

    public ISomeTable Calculate(ISomeTable st)
    {
        //Calc new table
        return newtable;
    }
}

public class SomeClass2 : SomeBaseClass
{
    public override ISomeTable Calculate(string s)
    {
        ISomeTable t1=SomeClass1.Calculate(s);
        //Modify the t1 according to SomeClass 2 modifications and save results in someTable2
        return someTable2;
    }

}
  • Related