【问题标题】:Something similar to an abstract with a definition c#类似于具有定义 c# 的抽象的东西
【发布时间】:2014-07-01 20:37:55
【问题描述】:

也许这首先是一个糟糕的设计,但我有一个抽象基类,它有一个方法validate

public abstract class abClass{
  string str1;
  string str2;

  public virtual bool validate(){...};
}

我知道每个派生类都有需要验证的属性,但我不想为每个抽象方法复制粘贴 str1str2 的验证。

我想确保未来的开发人员(包括我自己)会记得包含并填写验证方法。从我所见,没有办法给抽象方法一个定义,或者强制一个虚拟方法被覆盖。

到目前为止,我所听到的所有关于此的内容都是“您应该让开发人员选择他/她想要做的事情。”这对我来说还不够好,它违背了制衡,以帮助你不犯错误。我不介意他们是否主动选择没有任何实现代码,但如果他们忘记了,那么当派生字段未经过验证时,这将导致头疼。

对这个问题有什么建议吗?

【问题讨论】:

  • 使validate 成为抽象方法而不是虚拟方法?你可以重构出常见的逻辑。
  • 所以你想强制一个覆盖,而那个覆盖必须调用基类?我认为没有办法在语言中强制执行。那是您要/想要的吗?
  • @BradleyDotNET 我记得 Martin Fowler 讨论过这个......
  • 你担心的问题是真实存在的,并且源于“呼叫超级”anti-patterns

标签: c# validation virtual-functions abstract-methods


【解决方案1】:

您可以使用Template method 模式:

public bool Validate()
{
    // Call base class validation
    if (!ValidateCore())
        return false;

    // Call specific validation code (overridden by derived classes)
    return ValidateOverride();
}

private bool ValidateCore()
{
    // Validate str1 and str2
    // ...
}

protected abstract bool ValidateOverride();

这样,派生类必须重写ValidateOverride(因为它是抽象的),并且不能忘记调用ValidateCore,因为它是由基类中的非虚拟方法调用的。

【讨论】:

  • 请注意,您无需使用abstract 即可使用此模式。您可以将 ValidateOverride 定义为“NOOP”过程protected virtual bool ValidateOverride() {return true;}
  • @ScottChamberlain,是的,但是将其抽象化会强制继承者覆盖它,从而确保他们不会忘记这样做。
【解决方案2】:

Martin Fowler 在他的CallSuper 文章中讨论了这个确切的问题。

他基本上指出,在覆盖时要求类的子类调用基类方法是一种不好的做法或反模式:

调用 Super 是一种在 OO 框架中不时出现的轻微气味(或反模式,如果您愿意的话)。它的症状很容易被发现。您从超类继承以插入某个框架。文档说类似“做你自己的事情,只需子类化 process 方法。但重要的是要记住以调用超类开始你的方法”

他提出的解决方案是基类应该自己做需要做的事情,而让派生类只关心派生类的问题:

相反,API 应该为您记住内务调用。通常的做法是将句柄方法设为Template Method,如下所示:

//translation of java code to proper C#
public abstract class EventHandler ...
{
    public void Handle (BankingEvent e) 
    {  
        HouseKeeping(e); //this is required by the base class.
        DoHandle(e); //Here, control is delegated to the abstract method.
    }

    protected abstract void DoHandle(BankingEvent e);
}

public class TransferEventHandler: EventHandler
{
    protected override void DoHandle(BankingEvent e) 
    {
       initiateTransfer(e);
    }
}

【讨论】:

  • 非常好,这是解决我的问题的完美方案,并帮助我避免了反模式。通常我只得到一个或另一个。干得好,备份也很好。
【解决方案3】:

你可以这样做:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication6
{
    class Program
    {
        static void Main(string[] args)
        {
            Base good = new Good();

            Base bad = new Bad();

            good.Validate();

            bad.Validate(); // This blows up

            Console.ReadLine();
        }
    }

    public class Base
    {
        public virtual void Validate()
        {
            throw new NotImplementedException();
        }
    }

    public class Good : Base
    {
        public override void Validate()
        {
            Console.WriteLine("Looks good to me.");
        }
    }

    public class Bad : Base
    {

    }
}

...虽然如果由我决定,我只会定义一个带有 Validate() 方法的接口。

【讨论】:

  • 为什么不把它抽象化,而不是在基础实现中抛出异常呢?使其抽象化将允许在编译时检测错误,而不是在运行时。
  • @ThomasLevesque 是的,我同意那会好得多。但是,简单地使用接口也会产生相同的结果。
  • 结果不一样:抽象类允许你提供一些默认实现,而接口根本没有实现。我在回答中建议的解决方案无法通过界面实现。
  • 当我说相同的结果时,我指的是“编译时出错”。
猜你喜欢
  • 2011-11-21
  • 2011-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-28
  • 1970-01-01
  • 1970-01-01
  • 2017-10-18
相关资源
最近更新 更多