'Avoiding generic class with many type parameters, when the consumer of those types are tightly coupled

I am trying to come up with a way to do this where I can have type safety but also loose coupling of a couple of different components in my class.

I am running a calibration analysis which in general has 3 parts. Analyze, Verify, and Generate Report.

So what I would like to do is have a base calibration class with a method DoCalibration. I can then have child classes inherit from Calibration and inject the correct dependencies in order to run the above steps. My initial thought was to have something as follows

public class Calibration 
{
  readonly IAnalyzer _analyzer;
  readonly IVerifier _verify;
  readonly IReportGenerator _reportGen;

  public Calibration(IAnalyzer analyzer, 
                     IVerifier verifier, 
                     IReportGenerator generator)
 {
     _analyzer = analyzer;
     _verifier = verifier;
     _reportGen = generator;
 }

  public void DoCalibration()
  {
     var data; //would get most likely from a data reader
     var analyzeResult = _analyzer.Analyze(d);
     var verifyResult = _verifer.Verify(analyzeResult);
     var report = _reportGen.Generate(verifyResult);     
  }
}

However, my analyzer, verifier, and report generation are very tightly coupled, as the type of analyzeResult is coupled to the verify method of the verify result, and so on. So if I want this to be generic, I have to do something like

public class Calibration<TAnalyzeResult, TVerifyResult>
{
   IAnalyzer<TAnalyzeResult> _analyzer;
   IVerifier<TAnalyzeResult, TVerifyResult> _verifier;
   IReportGenerator<TVerifyResult> _reportGen
}

public interface IAnalyzer<TAnalyzeResult>{
    TAnalyzeResult Analyze(Data d);
}

public interface IVerifier<TAnalyzeResult, TVerifyResult>{
    TVerifyResult Verify(TAnalyzeResult analyzeResult);
}

//etc

However, if I want to add something like, say, some sort of processing to the report that is dependent on a specific report type, I have to keep adding type parameters to my calibration class. This to me is a code smell, but I can't figure out how to get type safety but also enforce the fact that every calibration calls Analyze, verify, generate report.

So to summarize, I would like to use the fact that I can have multiple calibration child types which all follow a similar processing flow which I can push into the base class, but I can't figure out how to decouple them while keeping type safety.



Solution 1:[1]

Lets first create some base classes for your calibration. This includes an interface

// Calibration Base class
public class Calibration<TAnalyzeResult, TVerifyResult>
    : ICalibration<TAnalyzeResult, TVerifyResult>
{
    IAnalyzer<TAnalyzeResult> _analyzer;
    IVerifier<TAnalyzeResult, TVerifyResult> _verifier;
    IReportGenerator<TVerifyResult> _reportGen

    public virtual void DoCalibration()
    {
        // Do your base calibration code here
    }
}

public interface ICalibration<TAnalyzeResult, TVerifyResult>
{
    void DoCalibration();
}

Now what we do is we get a child that inherrits from the base calibration class, similar we do a interface for this class as well. This will allow is to do dependency injection fairly easily.

// An interface for your chilc calibration
public interface IIndepthCalibration
    : ICalibration<MyChosenAnalyzeResylt, MyChosenVerifyResylt> { }

// Class for the cild calibraiton
public class IndepthCalibration
    : Calibration<MyChosenAnalyzeResylt, MyChosenVerifyResylt>, IIndepthCalibration
{
    public IndepthCalibration(
        IAnalyzer analyzer, // Required Injection
        IVerifier verifier, // Required Injection
        IReportGenerator generator, // Required Injection
        IMyCustomInjection)         // Injection used in this class alone
        : base(analyzer, verifier, generator)
    {
        // Constructor for the new calibration type
    }

    // OPTIONAL
    // If you want to add custom code to your DoCalibration Funciton
    public override void DoCalibration()
    {
        // Maybe you want to run the Base then do some additional
        base.DoCalibration();

        // Now you do your own
        // HERE
    }
}

Finally register your new class dependencies

builder.services.AddTransient<IIndepthCalibration, IndepthCalibration>();

this will allow use to inject the custom class if needed:

public class MyApp
{

    void MyApp(IIndepthCalibration indepthCalc)
    {
        indepthCalc.DoCalibration();
    }
}

Hopewfully this will help you to keep the typesafety without adding too many things.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Steven