5

我想问的问题是:

从抽象类内部向下转换继承树(即朝向更专业的类)是可以原谅的,甚至是一件好事,还是它总是一个糟糕的选择,有更好的选择?

现在,为什么我认为它可以用于良好的例子。

我最近在 C# 中从 BitTorrent 协议实现了 Bencoding 。一个足够简单的问题,如何表示数据。我选择这样做,

我们有一个abstract BItem类,它提供了一些基本功能,包括static BItem Decode(string)用于将 Bencoded 字符串解码为必要结构的类。

还有四个派生类,、BString和,表示要编码的四种不同数据类型。现在,这是棘手的部分。和have和accessors 分别允许访问这些数据类型的类似数组的质量。BIntegerBListBDictionaryBListBDictionarythis[int]this[string]

潜在的可怕部分现在来了:

BDictionary torrent = (BDictionary) BItem.DecodeFile("my.torrent");
int filelength = (BInteger)((BDictionary)((BList)((BDictionary)
             torrent["info"])["files"])[0])["length"];

好吧,你明白了……哎呀,这对眼睛来说很难,更不用说大脑了。所以,我在抽象类中引入了一些额外的东西:

public BItem this[int index]
{
    get { return ((BList)this)[index]; }
}
public BItem this[string index]
{
    get { return ((BDictionary)this)[index]; }
}

现在我们可以将旧代码重写为:

BDictionary torrent = (BDictionary)BItem.DecodeFile("my.torrent");
int filelength = (BInteger)torrent["info"]["files"][0]["length"];

哇,嘿presto,更具可读性的代码。但是我是否只是为了将子类的知识暗示到抽象类中而出卖了我的一部分灵魂?

编辑:作为对一些答案的回应,你完全偏离了这个特定问题的轨道,因为结构是可变的,例如我的例子torrent["info"]["files"][0]["length"]是有效的,但也是如此torrent["announce-list"][0][0],两者都将在 90% 的 torrent 文件中在那里。泛型不是要走的路,至少有这个问题:(。点击我链接的规范,它只有 4 个小点。

4

10 回答 10

5

我想我会将 this[int] 和 this[string] 访问器设为虚拟并在 BList/BDictionary 中覆盖它们。访问器没有意义的类应该强制转换 NotSupportedException() (可能通过在 BItem 中具有默认实现)。

这使您的代码以相同的方式工作,并为您提供更具可读性的错误,以防您应该编写

 (BInteger)torrent["info"][0]["files"]["length"];

因为失误。

于 2008-09-17T11:02:16.053 回答
3

你真的不应该从基类访问任何派生类,因为它几乎打破了 OOP 的想法。可读性当然有很长的路要走,但我不会用它来换取可重用性。考虑需要添加另一个子类的情况 - 您还需要相应地更新基类。

于 2008-09-17T10:57:54.610 回答
1

如果文件长度是您经常检索的东西,为什么不在 BDictionary (?) 类中实现一个属性......这样您的代码就变成了:

BDictionary torrent = BItem.DecodeFile("my.torrent");
int filelength = torrent.FileLength;

这样,实现细节就对用户隐藏了。

于 2008-09-17T11:02:02.620 回答
1

在我看来,并非所有 BItem 都是集合,因此并非所有 BItem 都有索引器,因此索引器不应该在 BItem 中。我将从 BItem 派生另一个抽象类,我们将其命名为 BCollection,并将索引器放在那里,例如:

abstract class BCollection : BItem {

      public BItem this[int index] {get;}
      public BItem this[string index] {get;}
}

并使 BList 和 BDictionary 继承自 BCollection。或者你可以加倍努力,让 BCollection 成为一个泛型类。

于 2008-09-17T11:07:33.527 回答
1

我的建议是引入更多抽象。我发现 BItem 有一个返回 BDictionary 的 DecodeFile() 令人困惑。我不知道,这可能是在 torrent 域中做的合理的事情。

但是,我会发现类似以下的 api 更合理:

BFile torrent = BFile.DecodeFile("my.torrent");
int filelength = torrent.Length;
于 2008-09-17T18:56:48.407 回答
0

您是否考虑过解析一个简单的“路径”,以便您可以这样编写:

BDictionary torrent = BItem.DecodeFile("my.torrent");
int filelength = (int)torrent.Fetch("info.files.0.length");

也许不是最好的方法,但可读性增加(一点)

于 2008-09-17T11:04:05.097 回答
0
  • 如果您可以完全控制自己的代码库和思维过程,那么一定要这样做。
  • 如果没有,那么当某个新人将您没有看到的 BItem 派生注入您的BList 或 BDictionary 的那一天,您会后悔的。

如果你必须这样做,至少将它(控制对列表的访问)包装在一个具有强类型方法签名的类中。

BString GetString(BInteger);
SetString(BInteger, BString);

接受并返回 BStrings,即使您在内部将它存储在 BItems 的 BList 中。(让我在做我的 2 B 或不做 2 B 之前分开)

于 2008-09-17T11:06:43.607 回答
0

唔。我实际上认为第一行编码比第二行更具可读性 - 弄清楚它发生了什么需要更长的时间,但更明显的是你将对象视为 BList 或 BDictionary。将方法应用于抽象类会隐藏该细节,这会使您更难弄清楚您的方法实际上在做什么。

于 2008-09-17T11:07:06.290 回答
0

如果引入泛型,则可以避免强制转换。

class DecodedTorrent : BDictionary<BDictionary<BList<BDictionary<BInteger>>>>
{
}

DecodedTorrent torrent = BItem.DecodeFile("mytorrent");
int x = torrent["info"]["files"][0]["length"];

嗯,但这可能行不通,因为类型可能取决于您通过结构所采用的路径。

于 2008-09-17T11:31:21.927 回答
0

只有我吗

BDictionary torrent = BItem.DecodeFile("my.torrent");int filelength = (BInteger)((BDictionary)((BList)((BDictionary)             torrent["info"])["files"])[0])["length"];

您不需要将 BDictionary 强制转换“torrent”声明为 BDictionary

public BItem this[int index]{&nbsp; &nbsp; get { return ((BList)this)[index]; }}public BItem this[string index]{&nbsp; &nbsp; get { return ((BDictionary)this)[index]; }}

这些不能达到预期的结果,因为返回类型仍然是抽象版本,所以你仍然需要强制转换。

重写的代码必须是

BDictionary torrent = BItem.DecodeFile("my.torrent");int filelength = (BInteger)((BList)((BDictionary)torrent["info"]["files"])[0])["length"];

哪个和第一批一样糟糕

于 2008-09-17T12:10:33.203 回答