Рефакторинг кода с расширениями универсального метода - PullRequest
0 голосов
/ 13 июня 2011

Я пытаюсь реорганизовать следующий код:

public static void SaveSplitbar(RadSplitBar splitBar)
{
    Logger.InfoFormat("Saving splitBar {0}", splitBar.ID);
    RadSplitBarSetting splitbarSettings = splitBar.GetSettings();
    SerializableDictionary<string, RadSplitBarSetting> splitBarStates = GetStates<SerializableDictionary<string, RadSplitBarSetting>>();

    bool splitbarIsKnown = splitBarStates.ContainsKey(splitbarSettings.ID);

    if (splitbarIsKnown)
    {
        Logger.Debug("SplitBar is known. Overwriting data.");
        splitBarStates[splitbarSettings.ID] = splitbarSettings;
    }
    else
    {
        Logger.Debug("SplitBar is unknown. Saving data.");
        splitBarStates.Add(splitbarSettings.ID, splitbarSettings);
    }

    RadControlManager.SetStates<SerializableDictionary<string, RadSplitBarSetting>>(splitBarStates);
}

Логика кода используется повторно для сохранения различных других объектов. Я пытаюсь сделать этот код более общим, чтобы у меня был один метод Save (), а не SaveX (), SaveY () и SaveZ (), выполняющие одну и ту же работу над разными объектами.

Однако у меня есть небольшая загвоздка. Хотя все объекты, передаваемые в Save, имеют метод «GetSettings ()», многие из них имеют этот метод, определенный в методе расширения:

//RadControlExtensions.cs Extension Methods
public static RadSplitBarSetting GetSettings(this RadSplitBar splitbar)
{
    RadSplitBarSetting radSplitbarSetting = new RadSplitBarSetting(splitbar.ID, splitbar.Parent.ID, splitbar.CollapseMode, splitbar.AdjacentPanesNames);
    return radSplitbarSetting;
}

Таким образом, когда я попытался реорганизовать SaveSplitbar, я понял, что мне нужно будет включить тип моего параметра. Когда я начал идти по этому пути, я подумал: «Ну, это глупо, если мне нужно определить тип объекта, я просто должен написать несколько раз Save в качестве методов расширения для каждого объекта».

Хотя это и есть "ОК", я недоволен им. Логика сохранения каждого типа управления идентична, и я бы предпочел, чтобы в моем классе расширений не было логики копирования / вставки.

Моей следующей мыслью было: «Хорошо, если какой-либо параметр, переданный в Save, гарантированно имеет метод GetSettings - возможно, есть способ получить интерфейс IRadControlExtensions, который предоставляет этот метод. Я не смог определить ни один такой способ, как каждый метод GetSettings имеет свою сигнатуру - не представляется возможным объявить об этом только один раз в интерфейсе.

Так вот где я. Как я должен подходить к этой проблеме?

РЕДАКТИРОВАТЬ: Другой метод сохранения

public static void SavePane(CormantRadPane pane)
{
    Logger.InfoFormat("Saving pane {0}", pane.ID);
    RadPaneSetting paneSettings = pane.GetSettings();
    SerializableDictionary<string, RadPaneSetting> paneStates = GetStates<SerializableDictionary<string, RadPaneSetting>>();

    bool paneIsKnown = paneStates.ContainsKey(paneSettings.ID);

    if (paneIsKnown)
    {
        Logger.Debug("Pane is known. Overwriting data.");
        paneStates[paneSettings.ID] = paneSettings;
    }
    else
    {
        Logger.Debug("Pane is unknown. Saving data.");
        paneStates.Add(paneSettings.ID, paneSettings);
    }

    RadControlManager.SetStates<SerializableDictionary<string, RadPaneSetting>>(paneStates);
}

РЕДАКТИРОВАТЬ 2: Возможно, лучший вопрос: «Когда пора прекратить расширение класса и начать создавать новый класс, который наследует от класса, который в настоящее время расширяется?»

Ответы [ 2 ]

2 голосов
/ 13 июня 2011

Ключ не должен быть общим в том, что вы хотите сохранить ... вам не важно, какой у него тип.Вам просто нужен этот экземпляр, чтобы предоставить вам какой-то объект настроек (вас интересует тип настроек!)

public static void Save<TSetting>(IGetSetting<TSetting> saveMe)
  where TSetting : IHasID
{
  TSetting setting = saveMe.GetSetting();
  SerializableDictionary<string, TSetting> states =
    GetStates<SerializableDictionary<string, TSetting>>();

  bool isKnown = states.ContainsKey(setting.ID);
  if (isKnown)
  {
    states[setting.ID] = setting;
  }
  else
  {
    states.Add(setting.ID, setting);
  }
  RadControlManager.SetStates<SerializableDictionary<string, TSetting>>(states);
}


interface IGetSetting<TSetting>
{
  TSetting GetSetting();
}

interface IHasID
{
  string ID {get;}
}


class RadSplitBar : IGetSetting<RadSplitBarSetting>

class RadSplitBarSetting : IHasID

Если у вас нет RadSplitBar для применения интерфейса IGetSettingрассмотрите способ заставить вызывающего абонента сказать, что нужно:

public static void Save<TSetting>(Func<TSetting> howToGetSetting)
{
  TSetting setting = howToGetSetting();
  //... rest as above.

}

Вызывается

SaveExtensions.Save( radSplitBar.GetSettings );
2 голосов
/ 13 июня 2011

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

 public class CormantRadPane : IWidget
 {
  ...
 } 

 public class RadSplitBar : IWidget
 {
  ...
 } 

где IWidget выглядит как

public interface IWidget
{
    int ID;
}

и каждый класс настроек с чем-то вроде ISetting

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

  Type contreteWidgetType = widget.GetType(); 

Помните, что методы расширения на самом деле являются обманом компилятора. При использовании ключевого слова this компилятор автоматически генерирует ExtensionAttribute для методов расширения. Взятый из некоторого прикольного кода из Jon Skeet здесь , вы можете отсканировать сборку на предмет правильного метода расширения и вызвать его.

Джон сказал ..

Ищите классы, украшенные ExtensionAttribute, а затем методы в этом классе, которые также украшены ExtensionAttribute. Затем проверьте тип первого параметра и убедитесь, что он соответствует интересующему вас типу.

    static IEnumerable<MethodInfo> GetExtensionMethods(Assembly assembly,
        Type extendedType)
    {
        var query = from type in assembly.GetTypes()
                    where type.IsSealed && !type.IsGenericType && !type.IsNested
                    from method in type.GetMethods(BindingFlags.Static
                        | BindingFlags.Public | BindingFlags.NonPublic)
                    where method.IsDefined(typeof(ExtensionAttribute), false)
                    where method.GetParameters()[0].ParameterType == extendedType
                    select method;
        return query;
    }

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

...