Метод перегрузки без изменения классов

#c# #inheritance #overloading #visitor-pattern #double-dispatch

#c# #наследование #перегрузка #посетитель-шаблон #двойная отправка

Вопрос:

У меня есть доступ к структуре классов, которую я не могу изменить, следующим образом:

 Graphics
  Circle
  Line
  etc.
  

Опять же, я не могу его изменить! Все они обладают индивидуальными свойствами, такими как Radius , FirstPoint LastPoint и т.д., А также некоторыми общими свойствами.

Я хочу создать метод, который принимает Graphics объект и, в зависимости от типа объекта, будет запускать ToJson метод:

 Graphics g = db.GetGraphic(123);
// Console.WriteLine(g.GetType()) prints "Circle"

// Should run some specific implementation for `Circle` type graphics, and
// have an overload for all types including Graphics
ToJson(g);
  

Сначала я думал, что смогу искусно перегрузить ToJson метод:

 ToJson(Graphics g) { ... }
ToJson(Circle g) { ... }
ToJson(Line g) { ... }
  

однако это, конечно, каждый раз приводит к общей ToJson(Graphics) перегрузке.

Я уверен, что мог бы сделать что-то вроде следующего:

 if (g is Circle) ...
if (g is Line) ...
if (g is Graphics) ...
  

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


Что я рассмотрел

Я подумал, есть ли какой-нибудь универсальный метод-оболочка, который я мог бы использовать для каждого объекта (например, new JsonGraphics(g).ToJson() ), но я не хочу самостоятельно выполнять какую-либо ручную проверку типов.

Я просмотрел шаблон double dispatch и visitor, но я не был уверен, что они соответствуют моим требованиям, поскольку они выглядят так, как будто я должен модифицировать эти классы (или, может быть, я просто не до конца их понял), и (довольно очевидно, однако) обобщения также в основном недоступны, поскольку они требуют, чтобы я заранее знал, что это за тип Graphics объекта.


Тогда два вопроса:

Есть ли лучший способ сделать это, кроме использования некоторого словаря или чего-то другого if (g is Type) подобного?

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

Комментарии:

1. Если бы вы могли изменять типы, вы бы добавили виртуальный / абстрактный метод экземпляра toJSON и переопределили по мере необходимости. «Механизм правил», в котором вы используете отражение, чтобы найти правильную реализацию кода toJSON, вероятно, является лучшим способом.

2. Так что да, словарь типа для делегирования / объекта, который реализует это, вероятно, то, что я бы выбрал сам.

3. Шаблон декоратора?

4. Как вы заметили, чтобы использовать шаблон visitor для двойной отправки, вам нужен класс «visited» для реализации метода, который вызывает класс «visitor» обратно в соответствующем методе. Я привожу здесь простой пример: ericlippert.com/2015/05/04/wizards-and-warriors-part-three Обычно вы используете шаблон visitor, когда у вас есть группа связанных объектов, у вас есть множество этапов анализа или преобразования, которые вам нужно выполнить с этими объектами, и точные детали каждой операции зависят от типа времени выполнения как посещаемых объектов, так и объектов visitor.

5. Спасибо за ваш вклад, Эрик, — я дам это прочитать! 🙂

Ответ №1:

К сожалению, я не думаю, что вы можете что-либо сделать, кроме проверки типа Graphics объекта во время выполнения, не имея возможности модифицировать базовый класс или иметь доступ к конкретному типу до того, как он превратится в универсальный Graphics тип.

Вы можете использовать оператор switch (начиная с C # 7.0), который немного чище, чем ваша if цепочка:

 switch (g)
{
    case Circle circle: ... break;
    case Line line: ... break;
    default: /* Oh no! */ break;
}
  

Лично я не вижу большого преимущества в использовании словаря по сравнению с подобной инструкцией switch — и то, и другое можно убрать в небольшой автономный метод (и таким образом уменьшить количество нарушений принципа открытия / закрытия), но switch будет значительно дешевле.

Вы также можете использовать dynamic , который заставляет среду выполнения выполнять позднюю привязку:

 dynamic d = g;
ToJson(d); // Picks the right ToJson overload corresponding to the runtime type of 'd'
  

… хотя dynamic требует довольно больших затрат во время выполнения и обычно считается плохим.

Комментарии:

1. switch используется ли какое-либо сопоставление с шаблоном? Я не знал, что операторы switch в C # могут это делать! 🙂

2. @Nick Да, начиная с C # 7.0

3. О боже, давно это было. Последний раз я использовал C #, когда он был 5.0, я чувствую себя старым

4. Справедливости ради, ничего новаторского не произошло с версии 5.0 (кроме Roslyn) — было много небольших улучшений. C # 8 снова будет новаторским, хотя