如何处理因实现类而异的静态字段

时间:2020-03-05 18:54:25  来源:igfitidea点击:

我一直都遇到这个问题。假设我正在创建一个命令行界面(Java或者C#,问题与我想的相同,我将显示Chere)。

  • 我定义一个接口ICommand
  • 我创建了一个抽象基类CommandBase,它实现了ICommand,以包含通用代码。
  • 我创建了几个实现类,每个实现类都扩展了基类(并通过扩展了接口)。

现在假设接口指定所有命令都实现Name属性和Execute方法...

对于Name,我的每个实例类都必须返回一个字符串,该字符串是该命令的名称。该字符串(" HELP"," PRINT"等)对于相关类是静态的。我希望能够做的是定义:

公共抽象静态常量字符串名称;

但是(很遗憾)我们不能在接口中定义静态成员。

我多年来一直在为这个问题而苦苦挣扎(几乎在我有一个类似班次的任何地方),因此下面将发表我自己的3种可能的解决方案,供我们投票。但是,由于它们都不是理想的,所以我希望有人会发布一个更优雅的解决方案。

更新:

  • 我无法使代码格式正常工作(Safari / Mac?)。道歉。
  • 我使用的示例很简单。在现实生活中,有时会有数十个实现类和这种半静态类型的几个字段(即实现类为静态)。
  • 我忘了提-理想情况下,我希望能够静态查询此信息:string name = CommandHelp.Name;

在我提出的3个解决方案中,有2个要求实例化该类,然后才能发现此丑陋的静态信息。

解决方案

回答

[建议的解决方案#1,共3]

  • 在接口中定义一个抽象属性Name,以强制所有实现类实现name属性。
  • (在c#中)将此属性作为抽象添加到基类中。
  • 接口要求创建属性。

缺点:

  • 复制(我讨厌)。粘贴到我的每个实现中的属性访问器代码都完全相同。为什么不能在基类中这样做以避免混乱?

回答

我们可能会考虑使用属性而不是字段。

public string Name 
{
  get {return COMMAND_NAME;}
}

回答

只需将name属性添加到基类中,然后将其传递给基类的构造函数,并将派生类的构造函数传递给它的命令名称

回答

[Command("HELP")]
class HelpCommand : ICommand
{
}

就那么简单。为什么要麻烦一个静态场?

观察:不要在抽象类中使用应在子类中初始化的字段(如David B建议)。如果有人扩展了抽象类而忘记了初始化字段怎么办?

回答

我通常做的事(伪):

public interface ICommand {
      String getName();
}

public class RealCommand implements ICommand {
     public String getName() {
           return "name";
     }
}

当然,如果这是其他开发人员将要使用的库,则在属性中添加一些反射来验证当前实例是否确实实现了覆盖或者抛出异常" Not Implemented"也不会有任何问题。

回答

[建议的解决方案#2,共3]

  • 设置一个私有成员变量名称。
  • 在接口中定义一个抽象属性Name。
  • 像这样在基类中实现属性:
abstract class:

private const string nameConstant = "ABSTRACT";

public string Name
{
   get {return this.GetName();}
}

protected virtual string GetName()
{
   return MyAbstractClass.nameConstant;
}

----

class ChildClass : MyAbstractClass
{
   private const string nameConstant = "ChildClass";

   protected override string GetName()
   {
      return ChildClass.nameConstant;
   }
}
  • 强制所有实现在调用抽象基类构造函数时将名称作为构造函数参数传递:
public string Name
{ 
  get {return Name;}
}
  • 现在,我所有的实现都在构造函数中设置了名称:
public abstract class CommandBase(string commandName) : ICommand
{
  name = commandName;
}

好处:

  • 我的访问器代码集中在基类中。
  • 名称定义为常量

缺点

  • Name现在是一个实例变量-我的Command类的每个实例都创建一个新引用,而不是共享一个静态变量。

回答

我的答案将与Java有关,因为这就是我所知道的。接口描述行为,而不是实现。此外,静态字段与类(而不是实例)相关。如果我们声明以下内容:

public class CommandHelp : CommandBase(COMMAND_NAME) {}

然后此代码如何知道要链接到哪个NAME:

interface A { abstract static NAME }
class B { NAME = "HELP" }
class C { NAME = "PRINT" }

我建议如何实现这一点,是以下方法之一:

  • 类名约定,基类从类名派生名称。如果我们希望与此背离,请直接覆盖该接口。
  • 基类有一个带有名称的构造函数
  • 使用批注并通过基类强制其存在。

但是,一个更好的解决方案可能是使用枚举:

void test(A a) {
    a.NAME;
}

这更加干净,并且允许我们使用switch语句,并且很容易获得NAME。但是,我们不能扩展选项运行时的数量,但是可以根据方案描述来扩展,甚至可能不需要。

回答

[建议答案3之3]

我还没有尝试过,而且在Java中还不是很好(我认为呢?),但是我可以用Attributes标记类:

[CammandAttribute(Name =" HELP")]

然后,我可以使用反射来获取该静态信息。需要一些简单的帮助程序方法,以使信息可以轻松地供类的客户端使用,但这可以在基类中使用。

回答

正如我们提到的,没有办法从接口级别强制执行此操作。但是,由于我们使用的是抽象类,因此我们可以在基类中将属性声明为abstract,这将强制继承类将其覆盖。在C#中,它看起来像这样:

public enum Command {
    HELP { execute() }, PRINT { execute() };
    abstract void execute();
}

(请注意,受保护的集可防止外部代码更改名称。)

这可能不完全是我们要找的东西,但它与我认为可以达到的程度非常接近。根据定义,静态字段不变。对于给定的类,我们根本不能拥有既静态又可重写的成员。

回答

从设计的角度来看,我认为需要一个静态的实现成员是错误的。在性能和内存使用之间的相对差异是静态的,而不是示例字符串。顺便说一句,我知道在实施中,所涉及的对象可能具有明显更大的占地面积...

根本问题是,通过尝试建立模型来支持静态实现成员,这些静态实现成员在使用Cis的基础或者接口级别可用,因此我们的选择受到限制。只有属性和方法在接口级别可用。

下一个设计难题是代码​​是基础代码还是特定于实现的代码。通过实现,模型将在编译时得到验证,该代码必须在所有实现中都包含类似的逻辑。使用base时,检定将在运行时发生,但逻辑将集中在一个地方。不幸的是,由于没有逻辑与数据相关联,因此给定的示例是针对特定执行代码的完美展示。

因此,出于示例的考虑,假设存在与数据相关联的某些实际逻辑,并且该逻辑足够广泛/复杂,足以为基础分类提供展示依据。抛开基类逻辑是否使用任何实现细节,我们有确保实现静态初始化的问题。我建议在基类中使用受保护的抽象,以强制所有实现创建所需的静态数据,这些数据将在编译时进行评估。我使用的所有IDE都可以使此操作变得非常容易。对于Visual Studio,只需单击几下鼠标,然后本质上只更改返回值即可。

回到问题的具体性质,而忽略许多其他设计问题……如果我们确实必须将整个问题保持在静态数据的性质范围内,并仍然通过问题的性质加以实施,那么……一定要去通过属性的方法,因为有很多副作用可以利用属性。在基类上使用静态成员,在实现上使用静态构造函数来设置名称。现在请记住,我们必须在运行时而不是编译时验证名称。基本上,基类上的GetName方法需要处理实现未设置其名称时发生的情况。它可能会引发异常,从而使人们很明显地意识到,某种实现可能是由于测试/ QA而不是用户引起的。或者,我们可以使用反射来获取实现名称并尝试生成名称...反射的问题在于,它可能会影响子类并设置代码环境,这对于初级开发人员而言很难理解和维护。 ..

为此,我们始终可以通过反射从类名称生成名称...尽管从长远来看,这可能是一个噩梦,但是它会减少实现上所需的代码量,这似乎更重要比其他任何问题都重要。我们也可以在此处使用属性,但是随后我们将在时间/工作量上等同于静态构造函数的代码添加到实现中,并且在实现不包含该信息时该怎么办仍然存在问题。

回答

像这样的事情呢:

public abstract class MyBaseClass
{
  public abstract string Name { get; protected set; }
}

public class MyClass : MyBaseClass
{
  public override string Name
  {
    get { return "CommandName"; }
    protected set { }
  }
}

现在,我们可以静态访问信息:

abstract class Command {
   abstract CommandInfo getInfo();
}

class CommandInfo {
  string Name;
  string Description;
  Foo Bar;
}

class RunCommand {
  static CommandInfo Info = new CommandInfo() { Name = "Run", Foo = new Foo(42)  };

  override commandInfo getInfo() { return Info; }
}

从基类:

RunCommand.Info.Name;

代码数量不匹配