C# 我不确定我应该在这里使用接口,但是什么?

C# 我不确定我应该在这里使用接口,但是什么?,c#,interface,refactoring,C#,Interface,Refactoring,我有多个*.ascx控件。每一个都表示一个Microsoft图表控件,但表示不同类型的图表。这些图表都公开了几个具有相同功能的方法,因为它们都实现了IChartControl接口。我讨厌这样一个事实:在某些情况下,每个图表的实现都是相同的——代码是复制/粘贴的 我选择让他们实现一个IChartControl接口,因为我找不到一个合理复杂的解决方案来允许这些图表控件使用相同的功能。事实证明,继承一个基本的“Chart”类非常复杂,因为您无法真正继承控件后面的HTML标记 我想做的是: 其中一个类的

我有多个*.ascx控件。每一个都表示一个Microsoft图表控件,但表示不同类型的图表。这些图表都公开了几个具有相同功能的方法,因为它们都实现了IChartControl接口。我讨厌这样一个事实:在某些情况下,每个图表的实现都是相同的——代码是复制/粘贴的

我选择让他们实现一个IChartControl接口,因为我找不到一个合理复杂的解决方案来允许这些图表控件使用相同的功能。事实证明,继承一个基本的“Chart”类非常复杂,因为您无法真正继承控件后面的HTML标记

我想做的是:

其中一个类的签名如下:

public partial class HistoricalLineGraph : System.Web.UI.UserControl, IChartControl
我想创建一个从System.Web.UI.UserControl继承的新类。此类将实现IChartControl接口。通过这种方式,我可以为我想要定义的方法提供一个基本实现,但我遇到了一个障碍。请看下面的方法,我想将其抽象到一个更高的级别,以便代码由我的所有图表类继承:

public void LoadData(string data)
{
    if (!string.IsNullOrEmpty(data))
    {
        byte[] dataAsArray = System.Text.Encoding.UTF8.GetBytes(data);
        MemoryStream stream = new MemoryStream(dataAsArray);
        Chart1.Serializer.Load(stream);
    }
}
介于HistoricalInGraph和System.Web.UI.UserControl之间的中间人类将没有HistoricalInGraph中定义的对象“Chart1”的概念

这个问题有没有一个明显的解决方案,而我却没有

编辑:我希望能够对IChartControl界面中定义的属性执行一些操作。如果我将“图表”控件作为参数传递给上述函数,下面的解决方案是什么

public string Title 
{
    get { return Chart1.Titles[1].Visible ? Chart1.Titles[1].Text : Chart1.Titles[0].Text; }
    set { Chart1.Titles[0].Text = value; }
}

如果一个接口的多个实现都以相同的方式实现了它的一个方法,那么可以使用extract方法重构。将复制的代码放在某个地方的静态方法中,并从接口实现中调用它

如果复制更普遍并且跨越整个接口,那么最好编写一个帮助器类来实现该接口。您可以去掉重复的代码并委托给helper类的实例


后一种想法与您尝试的继承类似,但它是一种组合形式,即委托。这是必要的,因为C#只支持实现的单一继承。

当然,您可以做几件事。最简单的方法是类继承和抽象函数指针(委托、事件、虚拟函数)来处理特定于类的事情

举个例子会有所帮助。在您的基类中

public event Action<BaseClass, string> DataLoaded;

protected virual void DoLoadDataLoaded(string data)
{
    var e = DataLoaded;
    if(null != e)
        e(this, data);
}

protected virtual void DoLoadStream(MemoryStream stream)
{
    Chart1.Serializer.Load(stream);
}

public void LoadData(string data)
{
    if (!string.IsNullOrEmpty(data))
    {
        byte[] dataAsArray = System.Text.Encoding.UTF8.GetBytes(data);
        MemoryStream stream = new MemoryStream(dataAsArray);
        DoLoadStream(stream);
        DoLoadDataLoaded(data);
    }
}
还有另一种选择,但我发现它更适合类库——如果您决定将代码重构为控件都依赖的单独类,您可以在方法调用中传递委托

例如:

public void LoadData(string data, Action<BaseClass, string, MemoryStream> handler)
{
    if (!string.IsNullOrEmpty(data))
    {
        byte[] dataAsArray = System.Text.Encoding.UTF8.GetBytes(data);
        MemoryStream stream = new MemoryStream(dataAsArray);
        handler(this, data, stream); // do something with it!
        DoLoadStream(stream);  // can still keep/use these if you want
        DoLoadDataLoaded(data);
    }
}
公共void加载数据(字符串数据、操作处理程序)
{
如果(!string.IsNullOrEmpty(数据))
{
byte[]dataAsArray=System.Text.Encoding.UTF8.GetBytes(数据);
MemoryStream stream=新的MemoryStream(dataAsArray);
处理程序(这个、数据、流);//用它做点什么!
DoLoadStream(stream);//如果您愿意,仍然可以保留/使用这些
DoLoadDataLoaded(数据);
}
}

我希望这有帮助

我们可以做的第一个观察是,如果您可以让所有图表类继承自
BaseChartControl
,那么您就不再需要
IChartControl
!只有当两个不同的类实现了接口,但它们不是从可以“容纳”公共API的公共基类继承时,才严格需要接口

另一方面,接口是统一多个类的公共行为的完美方式,这些类派生自您无法控制的公共基础。如果继续让图表类直接从
UserControl
派生,就会出现这种情况

现在,无论您选择哪种方法(接口或无接口),您仍然存在抽象问题,您已经使用
LoadData
作为示例。让我们努力吧

抽象是一项困难的任务,因为您正试图分离出公共部分,并保留使类不同的部分。在不造成混乱的情况下这样做是任何设计类层次结构的人都面临的主要设计挑战之一

但特别是对于
LoadData
,其中唯一不常见的部分是对
Chart1
本身的引用。在我看来,你至少有三个选择:

  • 在基类中包含
    Chart1
    ,现在它也很常见
  • 提供
    Chart1
    作为
    LoadData
  • 更改
    LoadData
    ,使其返回
    MemoryStream
    ,并让调用者序列化它

重点是将普通行为和独特行为分开。决定如何干净利落地做到这一点需要付出努力,因此,当您最终决定最好的方法时,这既是一个挑战也是一个回报。

您是否可以传递第二个参数
IChartControl
并使用它来代替。然后,您的子类可以调用
LoadData(,this)
Humm,这看起来可能有用——也许我想得太多了。界面中公布的属性如何?我将在原始帖子中编辑属性。有没有类似于。。标记属性摘要?您可以在接口中定义属性,但不能将代码放在getter和setter中-至少我认为您不能。如果要这样做,就需要一个具体的基类。没有理由不同时执行这两种操作,而使用IChartControl作为传递类型的更基本接口。这是一个非常酷的示例。你知道一个好地方吗?我可以多读一点吗?我肯定我可以用谷歌搜索一些东西,但是
public void LoadData(string data, Action<BaseClass, string, MemoryStream> handler)
{
    if (!string.IsNullOrEmpty(data))
    {
        byte[] dataAsArray = System.Text.Encoding.UTF8.GetBytes(data);
        MemoryStream stream = new MemoryStream(dataAsArray);
        handler(this, data, stream); // do something with it!
        DoLoadStream(stream);  // can still keep/use these if you want
        DoLoadDataLoaded(data);
    }
}