【问题标题】:How do I define the signature of a function that must be implemented by instances of a class?如何定义必须由类的实例实现的函数的签名?
【发布时间】:2017-01-21 22:55:43
【问题描述】:

假设我有一堂课Book

public class Book{
   public string Title {get; set;}
}

我希望每本书都有一个 Read 函数,该函数返回一个字符串并接受一个页码 - 但每本书的内部结构都会有所不同(我知道这是个糟糕的例子)。如何定义必须由此类实例实现的函数的签名?

类似这样的:

public class Book{ // need to make this abstract?
   public string Title {get; set;}
   public abstract string Read(int pageNum);
}

// I want to define instances that define their own behavior...
public static Book It => new Book(){ // can't create instance of abstract...
   Title = "It", 
   Read... // what do I do here?
}

我主要担心的是:

  • 让事情尽可能简单。在抽象类下实现接口是可行的,但是当我添加更多实例时,它会让我担心 n*2 件事情。
  • 我需要添加大量这些自定义函数 - 通过构造函数传递 Funcs 似乎很笨拙。
  • 实际上 - 这用于定义TenantTenants 是在内存中定义的,并且有许多静态属性,如 domainnameadminEmail 等。这些是特定于租户的属性......但现在我正在尝试实现特定于租户的 行为 - 比如GetBooksFilterUsers。我想让实现尽可能简单。现在我有“如果租户A,请执行此操作,否则如果租户B,请执行此操作......”贯穿我的代码。我正在尝试将所有特定于租户的逻辑和细节整合到一个地方 - 在 Tenant 类的实例上。
  • 租户特定行为的更多示例 - 您有一个 SaaS 论坛软件。在论坛 A 的主页上,您 GetCoverPhoto 通过读取静态文件。在论坛 B 的主页上,您 GetCoverPhoto 通过阅读博客主页。目前,我说“如果论坛 A,请执行此操作,否则如果论坛 B,请执行此操作”。这是我想在租户对象上而不是在代码中定义的租户特定行为类型。我不希望在我的核心逻辑中包含任何特定于租户的代码。

C# 语言中是否有一个简单的功能/模式可以实现这一点?

【问题讨论】:

  • @AlexD 我已经试过了 - 我会用我正在挣扎的地方更新我的问题。
  • 啊,好吧,abstract 不适用于这里。您可以考虑让Read 成为代表。
  • “通过构造函数传递 Funcs 看起来很笨拙” 真的吗? Read = pageNumber => LoadPage(pageNumber) 不实用?它准确地说明了您需要说的内容:对于Book 的这个实例,您通过调用 LoadPage 方法来加载页面。为每个 Book 实例指定不同的读取行为的方法是为每个 Book 实例指定不同的读取行为。
  • @JoeWhite - 无需讽刺。我观察到包含 20 个项目的构造函数是不可取的,这并没有错。更不用说类声明中每个函数的 n*2 行(每个函数一个,然后每个用法一个)。连答案作者都同意。
  • 如果我得到正确的要求,仍然不确定这些要求,但在我的编辑中有一些详细的答案。

标签: c# c#-6.0


【解决方案1】:

NineBerry 说的很有道理。

还有另一种方法可以完成您可能想要的事情。如果您想将 read 方法实现动态注入到 Book 中。这可以看作是strategy pattern。并且可以像许多语言一样作为接口完成,但在 C# 中以最简单的形式可以由委托完成。示例:

public class Book{
   Func<int, string> readFunc;
   public Book(Func<int, string> readFunc)
   {
     this.readFunc = readFunc;
   }
   public string Title {get; set;}
   public string Read(int pageNum) { return readFunc(pageNum); }
}

然后将其用作:

public static Book It => new Book(){
   Title = "It", 
   Read = (pageNum) => ... // Do actual reading in delegate
}

编辑:关于要求的更多细节(但仍然不是一切都很明显)我会做这样的事情:

public class Tenant 
{
  // core things go here
  public Extensions Extensions { get; }
}

public class Extensions : IEnumerable<IExtension>
{
  private IList<IExtension> list = new List<IExtension();
  private Tenant { get; set; }

  public Extensions(Tenant tenant)
  {
    Tenant = tenant;
  }

  public void Add(IExtension extension)
  {
    extension.Tenant = Tenant;
    list.Add(extension);
  }
}

public interface IExtension
{
  Tenant { get; set; }
  // shared interface of extensions if any can be abstracted
}

public interface ICoverPhotoExtension : IExtension
{
  Photo GetCoverPhoto();
}

public class FileCoverPhotoExtension : ICoverPhotoExtension 
{
  public Tenant { get; set; }
  Photo GetCoverPhoto() { } // gets photo from file
}

public class BlogCoverPhotoExtension : ICoverPhotoExtension 
{
  public Tenant { get; set; }
  Photo GetCoverPhoto() { } // gets photo from blog
}

用法:

Tenant tenant; // initialized somehow
var coverPhotoExtension = tenant.Extensions.FirstOrDefault<ICoverPhotoExtension>();
Photo photo = coverPhotoExtension?.GetCoverPhoto();

【讨论】:

  • 谢谢你——虽然它看起来有点“脏”——如果我有 20 个不同的函数需要声明呢?
  • 这绝对不是预期用途。
  • 这真的很酷,很可能是我要去的方向 - 谢谢!
  • 我可以通过电子邮件向您寻求(付费)更多详细信息的帮助吗?
  • 你的评论让我想到了“出柜”。只需谷歌我并在第一个链接上联系。
【解决方案2】:
public Interface IBook{ 
   string Title {get; set;}
   func<int,string> ReadPage
}

使用包含而不是继承。在上面的接口示例中,每本书中都有一个实现 IBook 的函数,该函数将为该页面返回一个字符串。

public class MyBook : IBook{

   public Title : {get;set;} = "MyBook";
   public func<int,string> ReadPage =(pagenumber)=>{
       return GetText(pagenumber);
   }  
   public string GetText(int pageNumber){
      //read the page text by number here.
   }
}

我会让GetText成为一个类似这样的扩展方法,所以每本书都不需要自己实现GetText。

public static class XBook{
    public static string GetText(this IBook book, int pageNumber){
        ///do the work here and returng string
    } 
}

使用扩展方法概念:

using XBook;
public class MyBook : IBook{

   public Title : {get;set;} = "MyBook";
   public func<int,string> ReadPage =(pagenumber)=>{
       return this.GetText(pagenumber);
   }  
}

还有更多方法可以做到这一点……现在,试试吧。

【讨论】:

  • 我对 NineBerry 的回答发表了我的担忧 - 这可能会更好,但你能证明它的用法吗?
  • 为您更新帖子
【解决方案3】:

不可能让一个类的不同实例以不同的方式实现功能。相反,您继承了一个新类,您可以在其中实现单独的行为。

public abstract class Book
{
   public string Title {get; set;}
   public abstract string Read(int pageNum);
}

public class ITBook : Book
{
   public override string Read(int pageNum)
   {
      // Code here
   }
}

然后像这样使用类:

public static Book It => new ITBook()
{ 
   Title = "It", 
}

您可以在 Book 类中使用委托类型的属性来在 Book 类的不同实例中使用不同的函数,但这些函数将无法访问它们将被使用的实例的其他属性和方法。

【讨论】:

  • "您可以在 Book 类中使用委托类型的属性来在 Book 类的不同实例中使用不同的函数,但这些函数将无法访问实例的其他属性和方法他们会被使用。”这不是真的。如果委托是一个闭包,但它需要在构造 Book 对象之后再构造。
  • 我正在努力使代码占用尽可能小……如果我有 10 本书 - 那就是 10 种不同的接口和静态类 = n*2 需要担心的事情。有没有一种简单的方法可以让我在担心 n 件事的情况下实现这一目标?
  • 如果您需要在代码中为每本书实现不同的页面检索,那么您的设计中还有其他一些非常错误的地方。如果您告诉我们更多有关问题的实际背景的信息,会更容易提供帮助。
  • @NineBerry 我已经用更多上下文更新了我的问题 - 谢谢。
猜你喜欢
  • 2014-11-30
  • 2010-12-02
  • 2011-12-22
  • 1970-01-01
  • 1970-01-01
  • 2017-12-02
  • 2015-07-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多