Альтернатива шаблону "посетитель"?

Вы бы сделали это в родительской ViewModel.

Например, если ваша страница (назовите ее PageViewModel) имела два вида (ViewModelA и ViewModelB), у вас будет свойство на PageViewModel с именем CurrentView, и это будет определять, какой вид является видимый. Когда PageViewModel.CurrentView установлен на экземпляр ViewModelA, тогда DataTemplate ViewA используется для рисования контента. Когда он установлен на экземпляр ViewModelB, отображается DataTemplate ViewB.


    



    



    

Было бы идеально вызывать команду переключения представлений из родительского представления (в данном случае DataTemplate для PageViewModel), однако, если вы хотите переключить представления из ViewModelA / B, вы можете либо подключить поднимите событие вручную, когда объекты будут созданы (CurrentView.ChangeViewCommand = this.ChangeViewCommand), или загляните в систему обмена сообщениями. MVVM Light имеет простой Messenger класс, который, как я обнаружил, довольно прост в использовании, или Prism имеет более продвинутый EventAggregator

. Если вы хотите переключить виды для одной и той же ViewModel, я бы порекомендовал свойство Mode. который используется, чтобы определить, какой вид использовать. Например:


    



    



    
        
            
        
    

РЕДАКТИРОВАТЬ

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

51
задан Dave Schweisguth 13 February 2016 в 13:59
поделиться

4 ответа

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

class AbstractShape
{
    IXmlWriter _xmlWriter = null;
    IShapeDrawer _shapeDrawer = null;

    public AbstractShape(IXmlWriter xmlWriter, 
                IShapeDrawer drawer)
    {
        _xmlWriter = xmlWriter;
        _shapeDrawer = drawer;
    }

    //...
    public void WriteToXml(IStream stream)
    {
        _xmlWriter.Write(this, stream);

    }

    public void Draw()
    {
        _drawer.Draw(this);
    }

    // any operation could easily be injected and executed 
    // on this object at run-time
    public void Execute(IGeneralStrategy generalOperation)
    {
        generalOperation.Execute(this);
    }
}

Более подробная информация содержится в этом связанном обсуждении:

Если объект записывает себя в файл, или должен ли другой объект воздействовать на него для выполнения ввода-вывода?

15
ответ дан 7 November 2019 в 10:24
поделиться

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

Вам необходимо отделить функциональность посетителя от интерфейса фигуры.

Я бы предложил креационистский подход через абстрактная фабрика для создания заменяющих реализаций для функций посетителя.

public interface IShape {
  // .. common shape interfaces
}

//
// This is an interface of a factory product that performs 'work' on the shape.
//
public interface IShapeWorker {
     void process(IShape shape);
}

//
// This is the abstract factory that caters for all implementations of
// shape.
//
public interface IShapeWorkerFactory {
    IShapeWorker build(IShape shape);
    ...
}

//
// In order to assemble a correct worker we need to create
// and implementation of the factory that links the Class of
// shape to an IShapeWorker implementation.
// To do this we implement an abstract class that implements IShapeWorkerFactory
//
public AbsractWorkerFactory implements IShapeWorkerFactory {

    protected Hashtable map_ = null;

    protected AbstractWorkerFactory() {
          map_ = new Hashtable();
          CreateWorkerMappings();
    }

    protected void AddMapping(Class c, IShapeWorker worker) {
           map_.put(c, worker);
    }

    //
    // Implement this method to add IShape implementations to IShapeWorker
    // implementations.
    //
    protected abstract void CreateWorkerMappings();

    public IShapeWorker build(IShape shape) {
         return (IShapeWorker)map_.get(shape.getClass())
    }
}

//
// An implementation that draws circles on graphics
//
public GraphicsCircleWorker implements IShapeWorker {

     Graphics graphics_ = null;

     public GraphicsCircleWorker(Graphics g) {
        graphics_ = g;
     }

     public void process(IShape s) {
       Circle circle = (Circle)s;
       if( circle != null) {
          // do something with it.
          graphics_.doSomething();
       }
     }

}

//
// To replace the previous graphics visitor you create
// a GraphicsWorkderFactory that implements AbstractShapeFactory 
// Adding mappings for those implementations of IShape that you are interested in.
//
public class GraphicsWorkerFactory implements AbstractShapeFactory {

   Graphics graphics_ = null;
   public GraphicsWorkerFactory(Graphics g) {
      graphics_ = g;
   }

   protected void CreateWorkerMappings() {
      AddMapping(Circle.class, new GraphicCircleWorker(graphics_)); 
   }
}


//
// Now in your code you could do the following.
//
IShapeWorkerFactory factory = SelectAppropriateFactory();

//
// for each IShape in the heirarchy
//
for(IShape shape : shapeTreeFlattened) {
    IShapeWorker worker = factory.build(shape);
    if(worker != null)
       worker.process(shape);
}

Это по-прежнему означает, что вам нужно написать конкретные реализации для работы с новыми версиями ' shape ', но поскольку он полностью отделен от интерфейса shape, вы можете модифицировать это решение, не нарушая исходный интерфейс и программное обеспечение, которое с ним взаимодействует. Он действует как своего рода строительные леса вокруг реализаций IShape.

2
ответ дан 7 November 2019 в 10:24
поделиться

Существует «Шаблон посетителя по умолчанию», в котором вы выполняете шаблон посетителя как обычно, но затем определяете абстрактный класс, который реализует ваш класс IShapeVisitor , делегируя все абстрактный метод с сигнатурой visitDefault (IShape) .

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

Когда вы добавляете новый подкласс IShape , вы исправляете абстрактный класс для делегирования его методу visitDefault , и каждый посетитель, указавший поведение по умолчанию, получает это поведение для нового IShape .

Вариант этого варианта, если ваши классы IShape естественным образом попадают в иерархию, состоит в том, чтобы сделать абстрактный класс делегировать с помощью нескольких различных методов; например, DefaultAnimalVisitor может делать:

public abstract class DefaultAnimalVisitor implements IAnimalVisitor {
  // The concrete animal classes we have so far: Lion, Tiger, Bear, Snake
  public void visitLion(Lion l)   { visitFeline(l); }
  public void visitTiger(Tiger t) { visitFeline(t); }
  public void visitBear(Bear b)   { visitMammal(b); }
  public void visitSnake(Snake s) { visitDefault(s); }

  // Up the class hierarchy
  public void visitFeline(Feline f) { visitMammal(f); }
  public void visitMammal(Mammal m) { visitDefault(m); }

  public abstract void visitDefault(Animal a);
}

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

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

Другой вариант, если ваши классы IShape естественным образом попадают в иерархию, состоит в том, чтобы делегировать абстрактный класс с помощью нескольких различных методов; например, DefaultAnimalVisitor может делать:

public abstract class DefaultAnimalVisitor implements IAnimalVisitor {
  // The concrete animal classes we have so far: Lion, Tiger, Bear, Snake
  public void visitLion(Lion l)   { visitFeline(l); }
  public void visitTiger(Tiger t) { visitFeline(t); }
  public void visitBear(Bear b)   { visitMammal(b); }
  public void visitSnake(Snake s) { visitDefault(s); }

  // Up the class hierarchy
  public void visitFeline(Feline f) { visitMammal(f); }
  public void visitMammal(Mammal m) { visitDefault(m); }

  public abstract void visitDefault(Animal a);
}

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

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

Другой вариант, если ваши классы IShape естественным образом попадают в иерархию, состоит в том, чтобы делегировать абстрактный класс с помощью нескольких различных методов; например, DefaultAnimalVisitor может делать:

public abstract class DefaultAnimalVisitor implements IAnimalVisitor {
  // The concrete animal classes we have so far: Lion, Tiger, Bear, Snake
  public void visitLion(Lion l)   { visitFeline(l); }
  public void visitTiger(Tiger t) { visitFeline(t); }
  public void visitBear(Bear b)   { visitMammal(b); }
  public void visitSnake(Snake s) { visitDefault(s); }

  // Up the class hierarchy
  public void visitFeline(Feline f) { visitMammal(f); }
  public void visitMammal(Mammal m) { visitDefault(m); }

  public abstract void visitDefault(Animal a);
}

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

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

public abstract class DefaultAnimalVisitor implements IAnimalVisitor {
  // The concrete animal classes we have so far: Lion, Tiger, Bear, Snake
  public void visitLion(Lion l)   { visitFeline(l); }
  public void visitTiger(Tiger t) { visitFeline(t); }
  public void visitBear(Bear b)   { visitMammal(b); }
  public void visitSnake(Snake s) { visitDefault(s); }

  // Up the class hierarchy
  public void visitFeline(Feline f) { visitMammal(f); }
  public void visitMammal(Mammal m) { visitDefault(m); }

  public abstract void visitDefault(Animal a);
}

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

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

public abstract class DefaultAnimalVisitor implements IAnimalVisitor {
  // The concrete animal classes we have so far: Lion, Tiger, Bear, Snake
  public void visitLion(Lion l)   { visitFeline(l); }
  public void visitTiger(Tiger t) { visitFeline(t); }
  public void visitBear(Bear b)   { visitMammal(b); }
  public void visitSnake(Snake s) { visitDefault(s); }

  // Up the class hierarchy
  public void visitFeline(Feline f) { visitMammal(f); }
  public void visitMammal(Mammal m) { visitDefault(m); }

  public abstract void visitDefault(Animal a);
}

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

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

13
ответ дан 7 November 2019 в 10:24
поделиться

Я поддерживаю программное обеспечение CAD / CAM для металлорежущего станка. Так что у меня есть некоторый опыт в решении этих проблем.

Когда мы впервые преобразовали наше программное обеспечение (оно было впервые выпущено в 1985 году!) На объектно-ориентированный дизайн, я сделал именно то, что вам не нравится. В объектах и ​​интерфейсах были Draw, WriteToFile и т. Д. Обнаружение и чтение шаблонов проектирования на полпути преобразования очень помогло, но все еще оставалось много неприятных запахов кода.

В конце концов я понял, что ни один из этих типов операций на самом деле не подходит забота об объекте. Но скорее различные подсистемы, которые должны были выполнять различные операции. Я справился с этим с помощью того, что сейчас называется командным объектом Passive View , и четко определенного интерфейса между уровнями программного обеспечения. Интерфейс. Эти формы представляют собой вещательную оболочку, передающую события на уровень пользовательского интерфейса.

  • Уровень пользовательского интерфейса, который принимает события и управляет формами через интерфейс формы.
  • Уровень пользовательского интерфейса будет выполнять команды, которые все реализуют интерфейс команд
  • Объект пользовательского интерфейса имеют собственные интерфейсы, с которыми команда может взаимодействовать.
  • Команды получают необходимую информацию, обрабатывают ее, манипулируют моделью, а затем отправляют отчеты объектам пользовательского интерфейса, которые затем делают все необходимое с формами.
  • Наконец, модели, которые содержат различные объекты нашей системы. Подобно программам форм, траекториям резки, столу для резки и металлическим листам.
  • Рисование обрабатывается на уровне пользовательского интерфейса. У нас разное программное обеспечение для разных машин. Итак, все наше программное обеспечение использует одну и ту же модель и многократно использует одни и те же команды. Они по-разному справляются с рисованием. Например, стол для резки для фрезерного станка отличается от станка, использующего плазменный резак, несмотря на то, что оба они по сути представляют собой гигантский плоский стол XY. Это потому, что, как и автомобили, эти две машины построены достаточно по-разному, так что есть визуальная разница для клиента.

    Что касается форм, то мы делаем следующее

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

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

    Каждая программа фигур имеет несколько представлений, реализующих интерфейс IShapeView. Через интерфейс IShapeView программа формы может сообщить общей форме формы, которая у нас есть, как настроить себя, чтобы показать параметры этой формы. Общая форма формы реализует интерфейс IShapeForm и регистрируется с помощью объекта ShapeScreen. Объект ShapeScreen регистрируется в нашем объекте приложения. Представления форм используют любой экран форм, который регистрируется в приложении.

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

    Команды, управляющие формами, попадают в одну из двух категорий. Они либо манипулируют траекторией резания, либо изменяют параметры формы. Чтобы управлять параметрами формы, мы либо бросаем их обратно в экран ввода формы, либо показываем минимальный диалог. Пересчитайте форму и отобразите ее в том же месте.

    Для траектории резки мы объединили каждую операцию в отдельный командный объект. Например, у нас есть объекты команд

    ResizePath Нам также нужно время от времени получать доступ к параметрам через минимальный диалог, а не через наш экран ввода полной формы. Отсюда и множество представлений.

    Команды, управляющие формами, попадают в одну из двух категорий. Они либо манипулируют траекторией резания, либо изменяют параметры формы. Чтобы управлять параметрами формы, мы либо бросаем их обратно в экран ввода формы, либо показываем минимальный диалог. Пересчитайте форму и отобразите ее в том же месте.

    Для траектории резки мы объединили каждую операцию в отдельный командный объект. Например, у нас есть объекты команд

    ResizePath Нам также нужно время от времени получать доступ к параметрам через минимальный диалог, а не через наш экран ввода полной формы. Отсюда и множество представлений.

    Команды, управляющие формами, попадают в одну из двух категорий. Они либо манипулируют траекторией резания, либо изменяют параметры формы. Чтобы управлять параметрами формы, мы либо бросаем их обратно в экран ввода формы, либо показываем минимальный диалог. Пересчитайте форму и отобразите ее в том же месте.

    Для траектории резки мы объединили каждую операцию в отдельный командный объект. Например, у нас есть объекты команд

    ResizePath Они либо манипулируют траекторией резания, либо изменяют параметры формы. Чтобы управлять параметрами формы, мы либо бросаем их обратно в экран ввода формы, либо показываем минимальный диалог. Пересчитайте форму и отобразите ее в том же месте.

    Для траектории резки мы объединили каждую операцию в отдельный командный объект. Например, у нас есть объекты команд

    ResizePath Они либо манипулируют траекторией резания, либо изменяют параметры формы. Чтобы управлять параметрами формы, мы либо бросаем их обратно в экран ввода формы, либо показываем минимальный диалог. Пересчитайте форму и отобразите ее в том же месте.

    Для траектории резки мы объединили каждую операцию в отдельный командный объект. Например, у нас есть объекты команд

    ResizePath RotatePath MovePath SplitPath и т. д.

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

    Например,

       CuttingTableScreen.KeyRoute.Add vbShift+vbKeyF1, New MirrorPath
    

    или

       CuttingTableScreen.Toolbar("Edit Path").AddButton Application.Icons("MirrorPath"),"Mirror Path", New MirrorPath
    

    В обоих случаях объект Command MirrorPath связывается с желаемым элементом пользовательского интерфейса. В методе execute MirrorPath есть весь код, необходимый для отражения пути на определенной оси. Вероятно, команда будет иметь собственный диалог или использовать один из элементов пользовательского интерфейса, чтобы спросить пользователя, какую ось отразить. Ничто из этого не делает посетителя или не добавляет метод к пути.

    Вы обнаружите, что многое можно сделать, объединив действия в команды. Однако я предупреждаю, что это не черно-белая ситуация. Вы по-прежнему обнаружите, что некоторые вещи работают лучше как методы исходного объекта. На собственном опыте я обнаружил, что, возможно, 80% того, что я делал в методах, можно было перенести в команду. Последние 20% просто лучше работают с объектом.

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

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

    Последние 20% просто лучше работают с объектом.

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

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

    Последние 20% просто лучше работают с объектом.

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

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

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

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

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

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

    6
    ответ дан 7 November 2019 в 10:24
    поделиться
    Другие вопросы по тегам:

    Похожие вопросы: