Reputation: 1507
I need to create a general way of handling files. Depending on what needs to be done with the file, specific classes should be used. I use ASP and C#. File is uploaded, I analyze the file, create an analysis result object and show analysis results basing on that object to the user. User reads analysis result and proceeds (clicks a button) or not. If he does than I take that analysis result object and perform an action (processing) based on it. I show the results of the processing to the user.
Depending on some query parameters (for sake of simplicity) the file should be processed by different classes. The processor needs analysis result specific for the concrete implementation of the analyzer (result of analyzing one file might inclde list of custom classes, result of different file analysis might contain other specific fields). The processor needs those implementation-specific details to do it's job.
I made an attempt to create general project but am stuck.
The magicId in the factory is a query parameter specifying which class should be used to parse/analyze the file.
Is there a way to create a working factory of classes, which all implement a genericly-typed interface?
abstract class AnalysisResult
{
}
interface IAnalyzer<TAnalysisResult>
where TAnalysisResult : AnalysisResult
{
TAnalysisResult PerformAction(string input);
}
class ConcreteAnalysisResult : AnalysisResult {
public int SpecificProperty { get; set; }
}
class ConcreteAnalyzer : IAnalyzer<ConcreteAnalysisResult>
{
public ConcreteAnalysisResult PerformAction(string input)
{
return new ConcreteAnalysisResult() { SpecificProperty = input.Length };
}
}
class AnAnalyzerFactory
{
IAnalyzer<AnalysisResult> CreateByMagicId(int magicId)
{
return new ConcreteAnalyzer();
}
}
Upvotes: 1
Views: 284
Reputation: 726599
Although you can fix your code by using type covariance, there would remain a place in your code where you dispatch based on the concrete type of the analyzer. This is suboptimal, because the use of generics hides something that needs to be "unhidden" later on (for example, to get to the SpecificProperty
property).
I think that a better approach would be to invert the control on the recipient of the analysis results in a way similar to the visitor pattern, like this:
// The following removes generics from your code
abstract class AnalysisResult {
}
interface IAnalyzer {
AnalysisResult PerformAction(string input);
}
class ConcreteAnalysisResult : AnalysisResult {
public int SpecificProperty { get; set; }
}
class ConcreteAnalyzer : IAnalyzer {
public AnalysisResult PerformAction(string input) {
return new ConcreteAnalysisResult() { SpecificProperty = input.Length };
}
}
// Here is the magic that lets you process results of specific types without generics
public static void main(string[] args) {
var analyzer = new ConcreteAnalyzer();
dynamic res = analyzer.PerformAction(input);
ProcessResult(res); // This gets dispatched dynamically to the correct overload
}
private static void ProcessResult(ConcreteAnalysisResult cr) {
Console.WriteLine(cr.SpecificProperty);
}
private static void ProcessResult(SomeOtherConcreteAnalysisResult cr) {
Console.WriteLine(cr.AnoterhSpecificProperty);
}
private static void ProcessResult(AnalysisResult cr) {
// Throw an error: unexpected result type
}
Note that the above code is free of static type casting, and it does not use generics, which became unnecessary after the refactoring.
Upvotes: 2
Reputation: 11549
Declare TAnalysisResult
as covariant. For details you can check Covariance and Contravariance
interface IAnalyzer<out TAnalysisResult> // note out keyword
where TAnalysisResult : AnalysisResult
{
TAnalysisResult PerformAction(string input);
}
Upvotes: 4