Not sure I should be using an Interface here, but what?
I have multiple *.ascx controls. Each one represents a Microsoft Chart Control, but different types of charts. These charts all expose several methods with identical functionality because they all implement an IChartControl interface. I hate the fact that there are cases where each chart's implementation is identical -- the code is copy/pasted.
I chose to have them implement an IChartControl interface because I could not find a solution of reasonable complexity that开发者_运维百科 would allow these chart controls to utilize the same functions. Inheriting a base 'Chart' class proved exceedingly complicated in that you can't really inherit the HTML markup behind the controls.
What I would like to do is the following:
The signature for one of the classes is this:
public partial class HistoricalLineGraph : System.Web.UI.UserControl, IChartControl
I would like to create a new class which inherits from System.Web.UI.UserControl. This class would implement the IChartControl interface. In this way I could provide a base implementation for the methods I want to define, but I run into a snag. Look at the following method that I would like to abstract out to a higher level so that the code is inherited by all of my charting classes:
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);
}
}
A middle-man class between HistoricalLineGraph and System.Web.UI.UserControl will have no concept of the object 'Chart1' as it is defined in HistoricalLineGraph.
Is there an obvious solution to this problem that I am missing?
EDIT: I would like to be able to do something with properties defined in the IChartControl interface. If I am passing a 'Chart' control as a parameter to the above function, what would a solution be to the below?
public string Title
{
get { return Chart1.Titles[1].Visible ? Chart1.Titles[1].Text : Chart1.Titles[0].Text; }
set { Chart1.Titles[0].Text = value; }
}
The first observation we can make is that if you can have all your chart classes inherit from BaseChartControl
, then you no longer need IChartControl
at all! An interface is only strictly needed when two different classes implement the interface but they don't inherit from a common base class that can "house" the common API.
On the other hand, an interface is a perfect way to unify the common behaviors of several classes that derive from a common base that you have no control over. This would be the case if you continue to have your chart classes derive directly from UserControl
.
Now, whichever approach you choose (interface or no interface), you still have your abstraction problem that you've used LoadData
as an example of. Let's work on that.
Abstraction is a difficult task because you are trying to separate out the common parts and leave in the parts that make the classes different. Doing this without creating a mess is one of the major design challenges that anyone who is designing a class hierarchy faces.
But specifically for LoadData
, the only part of it that isn't common behavior is the reference to Chart1
itself. As I see it you have at least three choices:
- Include
Chart1
in the base class and now it is common too - Supply
Chart1
as an additional parameter toLoadData
- Change
LoadData
so that it returns aMemoryStream
and let the caller serialize it
The point is the separation of common and unique behavior. It takes work to decide how to do this cleanly and that makes it both a challenge and rewarding when you finally decide on the best way to do it.
If you have multiple implementations of an interface that all implement one of its methods in identical fashion then you can use the extract method refactoring. Put the duplicated code into a static method somewhere and call it from your interface implementations.
If the duplication is more pervasive and spans the entire interface then you would be better off writing a helper class to implement that interface. You can strip out the duplicated code and delegate to an instance of the helper class.
This latter idea is similar to what you tried with inheritance, but instead is a form of composition, delegation. This is needed because C# only supports single inheritance for implementations.
You can of course do several things. The easiest is class inheritance and abstract function pointers (delegates, events, virtual functions) to handle class specific things.
An example would help. In your 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);
}
}
Now in your child class you can overload both of the Do virtual functions and modify them so that they reflect the child. You can also subscribe to the event and do specialty stuff there as well.
For example, you find that Chart1 has no use for a child class. So you override DoLoadStream like so:
protected override void DoLoadStream(MemoryStream stream)
{
// do nothing or do something else. Base class's DoLoadStream never executes!
}
There is another option, but I find it better suited for class libraries - and in case you decide to instead refactor your code out as a separate class that the controls all rely on, you can pass delegates in method calls.
For example:
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);
}
}
I hope this helps!
精彩评论