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

时间:2011-05-26 19:44:18

标签: c# interface refactoring

我有多个* .ascx控件。每个代表一个Microsoft Chart Control,但不同类型的图表。这些图表都暴露了几个具有相同功能的方法,因为它们都实现了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);
    }
}

HistoricalLineGraph和System.Web.UI.UserControl之间的中间人类没有对象'Chart1'的概念,因为它在HistoricalLineGraph中定义。

我错过了这个问题的明显解决方案吗?

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

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

3 个答案:

答案 0 :(得分:1)

如果您有多个接口实现,它们都以相同的方式实现其中一个方法,那么您可以使用提取方法重构。将重复的代码放在某个地方的静态方法中,然后从接口实现中调用它。

如果复制更普遍并跨越整个界面,那么最好编写一个辅助类来实现该接口。您可以删除重复的代码并委托给辅助类的实例。

后一种想法类似于您尝试继承的内容,而是一种组合,委托的形式。这是必需的,因为C#仅支持单继承实现。

答案 1 :(得分:1)

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

一个例子会有所帮助。在 BaseClass ...

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);
    }
}

现在,在您的子类中,您可以重载两个Do虚函数并修改它们以便它们反映孩子。您也可以订阅活动并在那里做特色活动。

例如,您发现Chart1对子类没有用处。所以你像这样重写DoLoadStream:

protected override void DoLoadStream(MemoryStream stream)
{
    // do nothing or do something else.  Base class's DoLoadStream never executes!
}

还有另一种选择,但我觉得它更适合类库 - 如果您决定将代码重构为控件全部依赖的单独类,您可以在方法调用中传递委托。

例如:

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);
    }
}

我希望这有帮助!

答案 2 :(得分:1)

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

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

现在,无论您选择哪种方法(界面还是没有界面),您仍然可以使用您使用LoadData作为示例的抽象问题。让我们继续努力。

抽象是一项艰巨的任务,因为你试图将公共部分分开并留在使这些类不同的部分中。这样做不会造成混乱是任何设计类层次结构的人面临的主要设计挑战之一。

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

  • 在基类中包含Chart1,现在也很常见
  • 提供Chart1作为LoadData
  • 的附加参数
  • 更改LoadData以便它返回MemoryStream并让调用者序列化

重点是共同和独特行为的分离。决定如何干净利落地完成这项工作需要付出努力,当你最终决定采用最佳方法时,这既是挑战又是有益的。