#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 снова будет новаторским, хотя