Можно ли прилично сделать Visitor в C#?

kruzer25

Есть вариант - сделать через жопу в лоб:

interface IThing {
}

class Milk : IThing {

public readonly float Volume;

}

abstract class Creature : IThing {

abstract public readonly float Weight;

}

class Cow : Creature {

public readonly float Weight;
public readonly Color Color;

}

class Chameleon : Creature {

public readonly float Weight;

}

class Laptop : IThing {

public readonly float Weight;
public readonly Color Color;
public readonly int Price;

}

class Visitor1 {

public static void Visit(IThing thing) {

if(thing is Milk) {
Console.WriteLine(thing.Volume + " liters of milk");
} else if(thing is Cow) {
Console.WriteLine(thing.Weight + " kgs of a raw beef");
} else if(thing is Chameleon) {
Console.WriteLine(thing.Weight + " kgs of a raw chameleon's meat");
} else if(thing is Laptop) {
Console.WriteLine(thing.Weight + " kgs of a scrap metal in half with plastic");
} else {
throw new ApplicationException("I do not know about this thing!");
}
}

}

class Visitor2 {

public static void Visit(IThing thing) {

if(thing is Milk) {
Console.WriteLine(thing.Volume + " liters of milk");
} else if(thing is Creature) {
Console.WriteLine(thing.Weight + " kgs of a raw meat");
} else if(thing is Laptop) {
Console.WriteLine(thing.Weight + " kgs of a scrap metal in half with plastic");
} else {
throw new ApplicationException("I do not know about this thing!");
}
}

}

class Visitor3 {

public static void Visit(IThing thing) {

if(thing is Milk) {
Console.WriteLine("White milk");
} else if(thing is Cow) {
Console.WriteLine(thing.Color + " cow");
} else if(thing is Chameleon) {
Console.WriteLine("Chameleon");
} else if(thing is Laptop) {
Console.WriteLine(thing.Color + " laptop");
} else {
throw new ApplicationException("I do not know about this thing!");
}
}

}

class Program {

private static IThing GetThing {
return new Cow;
}

public static void Main {
Visitor1.Visit(this.GetThing;
Visitor2.Visit(this.GetThing;
Visitor3.Visit(this.GetThing;
}

}

Можно сделать даже более анально - завести кучу методов вроде IsCow IsLaptop итд.
Можно написать нормальный код, но он не будет работать:
class Visitor2 {

public static void Visit(IThing thing) {
throw new ApplicationException("I do not know about this thing!");
}

public static void Visit(Milk thing) {
Console.WriteLine(thing.Volume + " liters of milk");
}

public static void Visit(Creature thing) {
Console.WriteLine(thing.Weight + " kgs of a raw meat");
}

public static void Visit(Laptop thing) {
Console.WriteLine(thing.Weight + " kgs of a scrap metal in half with plastic");
}

}

Но если немного модифицировать - заработает:

interface IThingVisitor {

public void Visit(Milk thing);
public void Visit(Cow thing);
public void Visit(Chameleon thing);
public void Visit(Laptop thing);

}

class Visitor2 : IVisitor {

public void Visit(Milk thing) {
Console.WriteLine(thing.Volume + " liters of milk");
}

public void Visit(Creature thing) {
Console.WriteLine(thing.Weight + " kgs of a raw meat");
}

public void Visit(Cow thing) {
this.VisitCreature)thing);
}

public void Visit(Chameleon thing) {
this.VisitCreature)thing);
}

public void Visit(Laptop thing) {
Console.WriteLine(thing.Weight + " kgs of a scrap metal in half with plastic");
}

}

interface IThing {

public void Accept(Visitor visitor);

}

class Milk : IThing {

public void Accept(Visitor visitor) {
visitor.Visit(this);
}

}

class Program {

private static IThing GetThing {
return new Cow;
}

public static void Main {
this.GetThing.Accept(new Visitor1;
this.GetThing.Accept(new Visitor2;
this.GetThing.Accept(new Visitor3;
}

}

Минусы - придётся писать в конкретных Visitor-ах методы на каждый конкретный тип Thing; в каждом классе, унаследованном от Thing, надо писать один и тот же код (а если забудем - то будут очень трудноуловимые баги):

public override void Accept(Visitor visitor) {
visitor.Visit(this);
}

Вот в плюсах, говорят, за счёт того, что шаблоны можно переопределять для конкретных типов - эта задача решается очень легко и красиво.
А можно ли в шарпе предпоследний пример привести в рабочее состояние, чтобы не надо было в каждом классе Thing писать один и тот же метод, и чтобы можно было в Visitor-ах писать методы для обработки только каких-то абстрактных типов?
То есть, как-нибудь вроде

Visitor1.Visit**super**)this.GetThing;

, где (**super**) в рантайме приводит объект к максимально конкретному типу (и, соответственно, конкретный Visit выбирается тоже в рантайме)?

kokoc88

Dasar

визитор обычно делается наоборот.

interface IThingWithVisit1
{
string Description {get;}
}

class Visitor1 {

public static void Visit(IThing thing)
{
var visit1 = thing as IThingWithVisit1;
if (visit1 != null)
Console.WriteLine(visit1.Description);
else {
throw new ApplicationException(
string.Format("I do not know about this thing '{0}'!", thing.GetType.FullName;
}
}

}

partial class Milk: IThingWithVisit1
{
string IThingWithVisit1.Description {get {return this.Volume + " liters of milk";}}
}

partial class Cow:IThingWithVisit1
{
string IThingWithVisit1.Description {get {return this.Weight + " kgs of a raw beef";}}
}

kruzer25

Ты ничего не понял.
Вывод на экран строчек тут - только для примера, чтобы понятно было, что имеется в виду.
В реальной жизни там вместо вывода строчек может быть какая-то обработка, меняющая внутреннее состояние visitor-а, а объекты на вход приходят совершенно разные, для которых нужна совершенно разная обработка. Эту обработку в принципе нельзя выносить в объекты Thing, она должна оставаться в Visitor-ах; вопрос в том, как лучше сделать, чтобы для каждого типа Thing была своя обработка.
Partial-классы - хорошая идея в некоторых случаях, но не в этом.

Dasar

Вывод на экран строчек тут - только для примера, чтобы понятно было, что имеется в виду.
без разницы, да хоть винт форматирует, или снаряд на луну закидывает.
меняющая внутреннее состояние visitor-а
значит в метод объекта надо передавать сам visitor.
Эту обработку в принципе нельзя выносить в объекты Thing, она должна оставаться в Visitor-ах;
вот это спорное утверждение.
если обработка разная для разных типов, то ее имеет смысл переносить в сам тип, если к самому типу эта обработка имеет косвенное отношение, то удобно эту обработку оформлять через partial классы.

Dasar

в общем случае это будет выглядеть так

interface IThingWithVisit1
{
void Visit(Visitor1 visitor);
}

class Visitor1 {

public void Visit(IThing thing)
{
var visit1 = thing as IThingWithVisit1;
if (visit1 != null)
visit1.Visit(this);
else {
throw new ApplicationException(
string.Format("I do not know about this thing '{0}'!", thing.GetType.FullName;
}
}

}

partial class Milk: IThingWithVisit1
{
void IThingWithVisit1.Visit(Visitor1 visitor){Console.WriteLine( this.Volume + " liters of milk");}
}

partial class MadCow:IThingWithVisit1
{
void IThingWithVisit1.Visit(Visitor1 visitor){Hdd.Format;}
}

partial class Worker:IThingWithVisit1
{
void IThingWithVisit1.Visit(Visitor1 visitor){visitor.ChangeState;}
}

kruzer25

без разницы, да хоть винт форматирует, или снаряд на луну закидывает.
значит в метод объекта надо передавать сам visitor
Я не хочу, чтобы всякие там коровы форматировали мой винчестер.
А для того, чтобы они могли менять состояние visitor-а - мне надо его поля состояния делать публичными.
если обработка разная для разных типов, то ее имеет смысл переносить в сам тип, если к самому типу эта обработка имеет косвенное отношение, то удобно эту обработку оформлять через partial классы.
Обработка имеет крайне косвенное отношение к типу (корова не знает, что её зарубят на мясо, а её молоко чаще разливают в картонные пакеты, чем в пластиковые). Кроме того, у коровы нет доступа к заводу, где происходит фасовка молока по пакетам, и ты не должен хотеть научить её работать на этом заводе даже через partial-классы - её всё равно туда не пустят.

kruzer25

в общем случае это будет выглядеть так
Я понял, как это будет выглядеть.
Твоё решение не оставляет никакой изоляции; нам придётся разрешить менять внутреннее состояние visitor-а снаружи (откуда угодно, а не только из MadCow - мы ведь никак не можем разграничить доступ); не на заводе будут знать, как расчленять коров и куриц, а коровы будут знать, как им надо расчленять себя на российских, а как - на американских заводах.

katrin2201

вот это спорное утверждение.
это утверждение пенартура правильное, слишком категоричное, но правильное.
в твоем случае модельные классы должны "знать" о всех реализованных визиторах, более того содержать их код.
это называется умным словом tight coupling и есть highly discouraged anti-pattern.

Dasar

это называется умным словом tight coupling и есть highly discouraged anti-pattern.
если в задаче есть n*k - различных поведений, то как это не обзывай анти-паттернами, но другого решения не будет.
какая разница: что визиторы знают про все объекты, что объекты знают про все визиторы?
ps
это будет анти-патерном, только если задачу можно так переформулировать, что n*k - замениться на n+k

katrin2201

какая разница: что визиторы знают про все объекты, что объекты знают про все визиторы?
большая

Dasar

разница очень большая
в чем именно?
что чаще будет добавляться виды объектов, или визиторы?
обычно чаще добавляются объекты, и получается, что при таких частых добавлениях, надо будет залезть в код каждого визитора и добавить обработку этого объекта.
при чем все это будет делаться на страшных и тормозных лестницах if-ов.

Dasar

большая
т.е ты пишешь, и предлагаешь писать вот такой код?:

public static void Visit(IThing thing) {

if(thing is Milk) {
Console.WriteLine("White milk");
} else if(thing is Cow) {
Console.WriteLine(thing.Color + " cow");
} else if(thing is Chameleon) {
Console.WriteLine("Chameleon");
} else if(thing is Laptop) {
Console.WriteLine(thing.Color + " laptop");
} else {
throw new ApplicationException("I do not know about this thing!");
}

kruzer25

при чем все это будет делаться на страшных и тормозных лестницах if-ов.
Ты не прочитал мой пост целиком?
Там в конце есть работающее решение без всяких ифов.

kruzer25

какая разница: что визиторы знают про все объекты, что объекты знают про все визиторы?
Какая разница - на заводе по производству фарша и на заводе по производству филе знают про то, что есть курицы и коровы, или курицы и коровы знают про то, что есть завод по производству фарша и завод по производству филе?

kokoc88

визитор обычно делается наоборот.
Визитор обычно делается наоборот.

interface IUserVisitor
{
string HelloPenartur2(Penatrur2 user);
string Hello( user);
string Hello( user);
}

interface IUser
{
string SayHelloTo(IUserVisitor visitor);
}

class Penatrur2 : IUser
{
public string SayHelloTo(IUserVisitor visitor) { return visitor.HelloPenartur2(this); }
}

class : IUser
{
public string SayHelloTo(IUserVisitor visitor) { return visitor.Hello(this); }
}

class : IUser
{
public string SayHelloTo(IUserVisitor visitor) { return visitor.Hello(this); }
}

class IntellectCountVisitor : IUserVisitor
{
public string HelloPenartur2(Penatrur2 user) { return int.MinValue.ToString; }

public string Hello( user) { return 10.ToString; }

public string Hello( user) {return 10.ToString; }
}

class ReadGoFVisitor : IUserVisitor
{
public string HelloPenartur2(Penatrur2 user) { return "no"; }

public string Hello( user) { return "yes"; }

public string Hello( user) { return "no"; }
}

class Program
{
static void Main(string[] args)
{
IntellectCountVisitor icv = new IntellectCountVisitor;
ReadGoFVisitor rgv = new ReadGoFVisitor;
IUser[] users = new IUser[3] { new new new Penatrur2 };
foreach (IUser u in users)
{
Console.WriteLine(u.GetType.ToString + " intellect @ " + u.SayHelloTo(icv;
Console.WriteLine(u.GetType.ToString + " has read GoF " + u.SayHelloTo(rgv;
}
}
}

Применение: 1. развязка типов, 2. часто меняются операции над некоторыми объектами.

kruzer25

public string HelloPenartur2(Penatrur2 user) { return int.MinValue.ToString; }
     public string Hello( user) { return 10.ToString; }
Похоже, что всё-таки наоборот.
Потому что (ты ведь не читал первый пост?) твоё решение в точности совпадает с тем шаблонным, которое в конце моего первого поста (за исключением того, что у меня методы Hello для разных пользователей называются одинаково).

kokoc88

Потому что (ты ведь не читал первый пост?) твоё решение в точности совпадает с моим (за исключением того, что у меня методы Hello для разных пользователей называются одинаково).
Я прочитал твой пост и ответил на него. А сейчас я отвечаю -ю.

kruzer25

Я прочитал твой пост и ответил на него. А сейчас я отвечаю -ю.
И почему же ты в своём ответе -ю так высоко оценил свои интеллектуальные способности, и так низко - мои?
Кстати, твой пример, наоборот, показывает, что лучше эту информацию вынести в классы пользователей (например, объект пользователя лучше знает, читал ли он GoF, чем Visitor). Впрочем, в твоём примере вообще можно обойтись без visitor-ов.

kokoc88

обычно чаще добавляются объекты, и получается, что при таких частых добавлениях, надо будет залезть в код каждого визитора и добавить обработку этого объекта.
Ага, только Visitor прекрасно ложится на дерево объектов, расслаивая его по уровням. Допустим от сервера может придти ответ, ответ - это ошибка или ок, ок - это команда1 или команда2. Получается
...response.AcceptResponse(this)
OnError throw...
OnOk(OkResponse r) r.AcceptOk(this)..
OnCommand1(Command1 c)
OnCommand2(Command2 c)
Таким образом для групп объектов может быть общая обработка, что в итоге удобнее, если паттерн не переворачивать.

katrin2201

обычно чаще добавляются объекты

т.е ты пишешь, и предлагаешь писать вот такой код?:
см. код майка

Dasar

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

Dasar

Таким образом для групп объектов может быть общая обработка, что в итоге удобнее если паттерн не переворачивать
так это можно и через наследование объектов сделать, особенно если mixin-ы использовать.

kruzer25

кода столько же, но
он лежит в нужном месте.
Смотри пример с ответами от сервера. Ответ - ошибка или успех; он может содержать в себе (в зависимости от конкретного типа) какие-то дополнительные данные; в конкретном visitor-е мы знаем, как обрабатывать некоторые из ответов. А ты предлагаешь перенести эту обработку в сами ответы.
Кроме того, повторю - у ответов может просто не хватить прав на совершение нужных действий. В том же примере с сумасшедшей коровой - у нас в пакете с обработчиками и остальным ядерным функционалом есть граната; если приходит соответствующая команда - Visitor эту гранату взрывает. Ты предлагаешь сделать гранату доступной для всех желающих снаружи этого внутреннего защищённого пространства имён с проверенным безопасным кодом?

kokoc88

и что мы выиграли?
кода столько же, но при добавление юзеров надо бегать по куче файлов.
видов юзеров обычно много больше, чем визиторов, соответственно операция добавления юзера чаще.
Твой код очень слабо похож на паттерн. Я вообще понять не могу, что там за ерунда и зачем там приведение типов.
При добавлении объектов в этом паттерне надо либо бегать по визиторам, либо работать с группами, либо иметь обработку по дефолту. Цитирую "добавление новых элеменетов затруднено". Паттерн не для удобного добавления большого количества объектов с одной операцией, а для добавления операций над объектами. В первом случае вообще используют виртуальный метод.

katrin2201

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

kruzer25

у тебя в голове каша
МБ он просто не выспался?
Совершенно не ожидал от него такую хрень услышать.

kokoc88

так это можно и через наследование объектов сделать, особенно если mixin-ы использовать.
Слабо понял эту твою фразу. Но я тебе просто описал, почему при добавлении новых элементов я не буду бегать по куче файлов. Ещё раз:
- Одна операция и много объектов - виртуальный метод
- Много операций и много объектов - визитор
- Одна операция и иерархия объектов - визитор или виртуальный метод

kruzer25

Я вообще понять не могу, что там за ерунда и зачем там приведение типов.
Приведение типов - типа для того, чтобы у нас в visitor не засунули объект, который этот конкретный тип visitor-а не поддерживает, и для того, чтобы на засовывание туда абстрактного объекта не выругался компилятор.

Dasar

у тебя в голове каша
может просто кому-то лень думать.

kokoc88

может просто кому-то лень думать.
Я тоже что-то не вижу силы твоих аргументов... Мало что понял из твоих выкладок и кода. :crazy:

Dasar

Я тоже что-то не вижу силы твоих аргументов... Мало что понял из твоих выкладок и кода.
что такое расщепление и зачем оно делается - понятно?
что такое переворот двойной передачи и зачем оно делается - понятно?

Dasar

...response.AcceptResponse(this)
OnError throw...
OnOk(OkResponse r) r.AcceptOk(this)..
OnCommand1(Command1 c)
OnCommand2(Command2 c)
о чем сказать хочешь - понятно, но приведи, плиз, полный код (в том числе и с обработкой групп) чтобы проще было обсуждать.

kokoc88

что такое расщепление и зачем оно делается - понятно?
что такое переворот двойной передачи и зачем оно делается - понятно?
Да, или нет. Нет или да.
Этот пост только сильнее меня запутывает. Ещё раз, начиная с момента "визитор предназначен для определения разных операций над множеством объектов, в нём трудно добавлять новые объекты". И попроще.

Dasar

Ещё раз, начиная с момента "визитор предназначен для определения разных операций над множеством объектов, в нём трудно добавлять новые объекты".
операции бывают разные.
есть операции, которые слабо относятся к объекту (могут быть реализованы более менее независимо от объекта) - тогда используется твой вариант
но есть операции, которые сильно завязаны на состояние объекта - например, Open/Close, Draw/Move/Rotate,
тогда будет скорее использоваться перевернутый вариант

interface IThing
{
void Open;
void Close;

void Draw;
void Move;
void Rotate;
}
class OpenVisitor:IVisitor
{
public void Visit(IThing thing)
{
thing.Open;
}
}
class DrawVisitor:IVisitor
{
public void Visit(IThing thing)
{
thing.Draw;
}
}

часто бывает, что опреация имеет смысл не для всех объектов, а только для некоторых, тогда удобно использовать расщепление

interface IThing
{
}
interface IThingWithOpenClose
{
void Open;
void Close;
}
interface IThingWithDraw
{
void Draw;
}

class OpenVisitor:IVisitor
{
public void Visit(IThing thing)
{
if (thing is IThingWithOpenClose)
IThingWithOpenClose)thing).Open;
}
}
class DrawVisitor:IVisitor
{
public void Visit(IThing thing)
{
if (thing is IThingWithDraw)
IThingWithDraw)thing).Draw;
}
}

или для обратного варианта
interface IUserVisitor_Penartur2
{
string HelloPenartur2(Penatrur2 user);
}
interface IUserVisitor_
{
string Hello( user);
}
interface IUserVisitor_
{
string Hello( user);
}

kokoc88

interface IThing
{
    void Open;
    void Close;
   
    void Draw;
    void Move;
    void Rotate;
}
class OpenVisitor:IVisitor
{
    public void Visit(IThing thing)
    {
     thing.Open;
    }
}
class DrawVisitor:IVisitor
{
    public void Visit(IThing thing)
    {
     thing.Draw;
    }
}
В этом коде не ясно, что за задача стоит, зачем здесь Visitor и почему он так называется. Почему я не пишу сразу new Context; foreach(thing...) thing.Draw(context без вызовов двух виртуальных функций?
часто бывает, что опреация имеет смысл не для всех объектов, а только для некоторых, тогда удобно использовать расщепление
Опять же, не понимаю... Я получил какой-то объект или группу объектов. Я сначала их разделю на те, что умеют сохраняться и те, что умеют отрисовываться. А затем вызову сохранение или отрисовку. Всё, две функции, отсутствие reflection.
class ThingWithDraw : IThing {}
class ThingWithOpen : IThing {}
void VisitDraw(..)
void VisitOpen(...)

kokoc88

Я сначала их разделю на те, что умеют сохраняться и те, что умеют отрисовываться.
Ну да, да, надо было разделить на три... И ещё упомянуть о том, что объекты могут принадлежать вообще разной иерархии типов. Тогда вообще не ясно, на кой чёрт там визитор.

Dasar

В этом коде не ясно, что за задача стоит, зачем здесь Visitor и почему он так называется. Почему я не пишу сразу new Context; foreach(thing...) thing.Draw(context без вызовов двух виртуальных функций?
допустим что-нибудь банальное, последовательность обходов набирается динамически (например, редактируется пользователем)
var scenario1 = new IVisitor[]{new OpenVisitor new DrawVisitor new CloseVisitor};
var scenario2 = new IVisitor[]{new MoveVisitor(12, 3 new RotateVisitor(90 new MoveVisitor(3,7 new DrawVisitor};

katrin2201

Опять ничего не ясно. В таком виде это больше походит на паттерн Command.
Не бывает универсальных паттернов на все случаи жизни. У каждого - достаточно строгий узкий круг применимости. Попытка их расширить приводит к той самой каше.

kokoc88

допустим что-нибудь банальное, последовательность обходов набирается динамически (например, редактируется пользователем)
var scenario1 = new IVisitor[]{new OpenVisitor new DrawVisitor new CloseVisitor};
var scenario2 = new IVisitor[]{new MoveVisitor(12, 3 new RotateVisitor(90 new MoveVisitor(3,7 new DrawVisitor};
Это паттерн Strategy. Или Command, если отправляется одному объекту.

rosali

визитор обычно делается наоборот.

-1 тебе ;) visitor пишется именно так как написал. этот подход используется когда структура данных более менее постоянна, а основное развитие идет написанием новых способов обработки. например данные - это AST программы на каком-нибудь языке программирования, то есть имеются классы там не знаю DeclareClass, DeclareMethod, Statements, Assing, Call, Operator, ... их много, но новые будут появляться не часто. а вот способов обработки "открытое" множество, сначала надо написать интерпретатор, потом компилятор, потом профайлер, дебаггер, визуализатор, ... если ты по каждой новой задаче будешь писать новые методы в классы самого AST (а не дай бог еще и добавлять туда данные, необходимые для этой обработки) то довольно быстро получится помойка. а вот подход через visitor-ы позволяет разные алгоритмы обработки разнести и отделить их от собственно данных. теперь когда возникает новая задача, мы не новые методы дописываем, а новый класс, полностью инкапсулируя в нем все тонкости этого конкретного алгоритма. так то (С)
только я так и не понял, какие у -а с применением всего этого проблемы? :ooo:
> в каждом классе, унаследованном от Thing, надо писать один и тот же код (а если забудем - то будут очень трудноуловимые баги)
я еще раз повторяю, предполагается, что новые классы-данные возникают редко, в основном пишутся новые классы-visitor-ы. если это не так, значит паттерн не очень подходит. да и что это за мифические "трудноуловимые баги" непонятно, забыл метод интерфейса определить, получил ошибку компиляции, чего тут трудноуловимого то?

6yrop

+1, вот пример из MSDN-а для прохода по Ling.Expression Implement an Expression Tree Visitor

kruzer25

только я так и не понял, какие у -а с применением всего этого проблемы?
> в каждом классе, унаследованном от Thing, надо писать один и тот же код (а если забудем - то будут очень трудноуловимые баги)
я еще раз повторяю, предполагается, что новые классы-данные возникают редко, в основном пишутся новые классы-visitor-ы. если это не так, значит паттерн не очень подходит.
Необязательно новые классы данных возникают редко. Главное - что из соображений изоляции, обработка должна быть в специальном классе-обработчике, а не в классе данных.
да и что это за мифические "трудноуловимые баги" непонятно, забыл метод интерфейса определить, получил ошибку компиляции, чего тут трудноуловимого то?
См. мой пример из первого поста.
Взяли мы и забыли для Cow (которое, напомню, потомок Creature) определить метод Accept. И что? Какая будет ошибка компиляции?
Никакой ошибки компиляции не будет, и вообще никаких ошибок не будет, просто Cow мы всегда будем обрабатывать как Creature, даже если написан отдельный обработчик именно для Cow. Это вполне может оказаться трудноуловимым багом (когда у тебя где-то совсем в другом месте из-за этого окажутся кривые данные, и ты будешь часами ломать голову по поводу того, как же они так испортились).

klyv

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

kruzer25

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

klyv

даркгреевский вариант с частичными классами лучше.
да, есть такое. жаль, что это (Visitor красивше, чем в примере или LINQ или твоём) возможно только при перекомпиляции исходников.

Dasar

Это паттерн Strategy. Или Command, если отправляется одному объекту.
как минимум - это Strategy + Visitor.
to mike и , если вы так формально классифицируете паттерны, то могу вот так пример переформулировать:

interface IThing
{
}
interface IThingWithILGenerate
{
void Generate(ILGenerator generator);
}
interface IThingWithCsGenerate
{
void Generate(System.CodeDom.Compiler.IndentedTextWriter writer);
}

class Visitor<TThingFace, TInfo>:IVIsitor
{
public Visitor(Executor<TThingFace,TInfo> executor)
{
this.Executor = executor;
}
Executor<TThingFace,TInfo> executor;
void Visit(IThing thing)
{
if (thing is TThingFace)
executorTThingFace)thing, info);
}
...
}

var generator = isILGenerateElseCsGenerate
? (IVisitor)new Visitor< IThingWithILGenerate>thing,generator) => thing.Generate(thing, generator) :
(IVisitor)new Visitor<IThingWithCsGenerate>thing, writer) => thing.Generate(thing, writer);

generator.VisitAll;

Dasar

да, есть такое. жаль, что это (Visitor красивше, чем в примере или LINQ или твоём) возможно только при перекомпиляции исходников.
в общем случае, в обоих вариантах требуется перекомпиляция.
мой вариант требует перекомпиляции всегда, майковский вариант требует перекомпиляции при изменении набора элементов

kokoc88

как минимум - это Strategy + Visitor.
to mike и , если вы так формально классифицируете паттерны, то могу вот так пример переформулировать:
Я опять не вижу там Visitor - двойная Strategy: какой тип мы хотим обработать в списке и способ обработки этого типа. Операции в этом случае как писались в классах элементов, так и продолжают там писаться, а смысл паттерна в том, чтобы отделить операцию от элемента.
Возможно, в разной литературе по-разному толкуют содержимое паттерна, но в моём понимании он полностью избавляет от проверки принадлежности экземпляра объекта какому-то типу и обязательно содержит два виртуальных вызова, по крайней мере в C++/Java/C# (так же этот приём носит умное название).
Оставить комментарий
Имя или ник:
Комментарий: