Не уверен, что я должен использовать интерфейс здесь, но что? - PullRequest
1 голос
/ 26 мая 2011

У меня есть несколько элементов управления * .ascx.Каждый из них представляет элемент управления Microsoft Chart, но разные типы диаграмм.Все эти диаграммы предоставляют несколько методов с одинаковой функциональностью, поскольку все они реализуют интерфейс 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.Если я передаю элемент управления 'Chart' в качестве параметра вышеупомянутой функции, каким будет решение для приведенной ниже функции?

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

Ответы [ 3 ]

1 голос
/ 27 мая 2011

Конечно, вы можете сделать несколько вещей. Самым простым является наследование классов и указатели на абстрактные функции (делегаты, события, виртуальные функции) для обработки специфических для класса вещей.

Пример поможет. В вашем 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);
    }
}

Надеюсь, это поможет!

1 голос
/ 27 мая 2011

Первое наблюдение, которое мы можем сделать, состоит в том, что если вы можете иметь все ваши классы диаграмм, наследуемые от BaseChartControl, то вам больше не нужен IChartControl вообще! Интерфейс строго необходим, только когда два разных класса реализуют интерфейс, но они не наследуются от общего базового класса, который может «содержать» общий API.

С другой стороны, интерфейс - это идеальный способ унифицировать общее поведение нескольких классов, основанных на общей базе, которую вы не можете контролировать. Это будет иметь место, если вы продолжите получать классы диаграммы непосредственно из UserControl.

Теперь, какой бы подход вы ни выбрали (интерфейс или нет интерфейса), у вас все еще есть проблема абстракции, которую вы использовали LoadData в качестве примера. Давайте работать над этим.

Абстракция - трудная задача, потому что вы пытаетесь отделить общие части и оставить в частях, которые делают классы разными. Выполнение этого без создания беспорядка - одна из главных проблем проектирования, с которой сталкивается любой, кто проектирует иерархию классов.

Но специально для LoadData единственная его часть, которая не является обычным поведением, - это ссылка на сам Chart1. На мой взгляд, у вас есть как минимум три варианта:

  • Включите Chart1 в базовый класс, и теперь он также является общим
  • Поставка Chart1 в качестве дополнительного параметра к LoadData
  • Измените LoadData, чтобы он возвращал MemoryStream, и пусть вызывающий абонент его сериализует

Дело в разделении общего и уникального поведения. Нужно потрудиться, чтобы решить, как сделать это чисто, и это делает его одновременно и вызовом, и вознаграждением, когда вы, наконец, выбираете лучший способ сделать это.

1 голос
/ 27 мая 2011

Если у вас есть несколько реализаций интерфейса, которые все реализуют один из его методов одинаковым образом, то вы можете использовать рефакторинг метода extract.Поместите дублированный код в статический метод и вызовите его из реализаций вашего интерфейса.

Если дублирование более распространено и охватывает весь интерфейс, то вам лучше написать класс-помощник для реализации этого интерфейса.Вы можете удалить дублированный код и делегировать экземпляр вспомогательного класса.

Эта последняя идея похожа на ту, что вы пробовали с наследованием, но вместо этого представляет собой форму композиции, делегирования.Это необходимо, потому что C # поддерживает только одиночное наследование для реализаций.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...