简体   繁体   中英

Interface returning derived types

class Result
{
   public string Data { get; set; }
}

interface IRepository
{
   Result[] Search(string data);
}

I have a fairly generic interface that searches for "something" and returns a Result . The IRepository interface can be implemented by several classes, each returning their own Result with their own unique metadata. For example, I can have a DiskRepository that searches for data on disk:

class DiskResult : Result
{
   public int FileSize { get; set; }
   public DateTime LastModifiedDate { get; set; }
}

class DiskRepository : IRepository
{
   public Result[] Search(string data) 
   {
      // ...
      DiskResult[] results = GetDataFromSomewhere();
      return results;
   } 
}

The DiskResult contains extra information about the result that is specific to the DiskRespository . If I created another class that implements IRepository , that specific implementation may have its own set of metadata unique to that class.

In the end, I'd like my search controller to look like this:

class SearchController 
{
   private IRepository[] _repositories;

   public SearchController(IRepository[] repositories)
   {
      _repositories = repositories;
   }

   public void Display(string data)
   {
      Result[] results = _repositories.Search(data);
      // Display results
   }
}

I can easily display the Data property on my Result class, but is there a good pattern to display the metadata for each class that derives from Result ? I could have a bunch of if statements to check if the class is of a type, but that feels a bit clunky. Is there a better way to do what I'm trying to achieve?

One of the comments correctly points out that adding a virtual Display() to your Result class is a violation of the Single Responsibility Principle. Completely true.

Here's the rub with your question: because you want to do things like this:

private IRepository[] _repositories;

... there's no way to avoid doing a type check at run time. The compiler has no idea what subclass type-derived-from-Result will be returned. All that it knows it that your Repository's return a Result derived object.

On the other hand, if you use generics:

interface IRepository<T> where T : Result
{
    T[] Search(string data);
}

... you will, at compile time, know what subclass type of Result you're dealing with, thus obviating the need for type checking, and the long string of "if" statements that follow from the first approach.

If you can stick with using generics, then you can do stuff like this:

interface IResultDisplayService<T> where T : Result
{
    void Display(T result);
}

So, I suppose my question is: is it imperative to store an array of these repositories? What real world usage scenario is there?

I would not use the repository interface for that, but create a new one:

public interface ISearchProvider
{
    IEnumerable<SearchResultItem> Search(string keyword);
}

public interface ISearchResultItem
{
    string Title {get; }
    string Description {get; }
    NameValueCollection Metadata {get; }
}

Title and description should be enough for 90% of the search cases. For instance, a DiskResult might include folder etc in the Description property. The metadata can be displayed in a tooltip or a details view.

If that's not enough I would create a render interface too:

public interface ISearchResultRenderer
{
    bool IsValidFor(Type type);
    void Render(Stream stream);
}

And have a DiskResultHtmlRenderer implementation which goes through the metadata and structure it properly.

You could make IRepository a generic interface like:

interface IRepository<T>
{
    T[] Search(string data);
}

You can have a virtual method in result class which will display the results. Your child class can override it and can give their own implementation. Doing so when you call Display method your Result object will call respective method to do the display.

Something like this

class Result
{
     public virtual void Display()
     {
          //Your Code

     }
     //Your Code   
}

class DiskResult : Result
{
     public override void Display()
     {
          //Your Code
     }
     //Your Code
}

Your Display method

public void Display(string data)
{
   Result[] results = _repositories.Search(data);

   // Display results
   foreach(var result in results)
   {
      result.Display();
   }

}

Hope this helps you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM