AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Sprachen und Entwicklungsumgebungen Object-Pascal / Delphi-Language MVC + Observer Pattern Konzept / Was haltet Ihr davon
Thema durchsuchen
Ansicht
Themen-Optionen

MVC + Observer Pattern Konzept / Was haltet Ihr davon

Ein Thema von FAM · begonnen am 5. Feb 2015 · letzter Beitrag vom 6. Feb 2015
Antwort Antwort
FAM

Registriert seit: 22. Dez 2014
47 Beiträge
 
Delphi XE Enterprise
 
#1

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon

  Alt 5. Feb 2015, 13:00
Grundlegend bei den Entwürfen ist aber, dass die Views nicht die ViewModels/Controller erzeugen, denn sonst geht der gesamte Vorteil der Testbarkeit sofort flöten und die Views übernehmen auf einmal wieder die Kontrolle.

danke für deine Antwort.

Was die Testbarkeit angeht, verstehe ich dich nicht ganz. Warum sollte diese flöten gehen...? ich habe doch mit diesem Konzept die BusinessLogik sauber von der GUI (View) getrennt.

oder nicht?
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#2

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon

  Alt 5. Feb 2015, 13:09
Grundlegend bei den Entwürfen ist aber, dass die Views nicht die ViewModels/Controller erzeugen, denn sonst geht der gesamte Vorteil der Testbarkeit sofort flöten und die Views übernehmen auf einmal wieder die Kontrolle.

danke für deine Antwort.

Was die Testbarkeit angeht, verstehe ich dich nicht ganz. Warum sollte diese flöten gehen...? ich habe doch mit diesem Konzept die BusinessLogik sauber von der GUI (View) getrennt.

oder nicht?
Wenn die View den Controller erstellt, dann eben nicht.

Mein MainViewModel kann ich testen ohne irgendeine View im Spiel zu haben.
Delphi-Quellcode:
procedure Test;
var
  LVM : TMainViewModel;
begin
  LVM := TMainViewModel.Create;
  try
    Assert( LVM.SomeAction.CanExecute );
    Assert( not Assigned( LVM.Activity ) );

    LVM.SomeAction.Execute;

    Assert( not LVM.SomeAction.CanExecute );
    Assert( Assigned( LVM.Activity ) );

    while not LVM.CanClose do
      Sleep(10);

    Assert( LVM.SomeAction.CanExecute );
    Assert( not Assigned( LVM.Activity ) );
  finally
    LVM.Free;
  end;
end;
Ich kann auch die gesamte Anwendung durchlaufen lassen ohne ein einziges View zu erzeugen, denn die View wird erzeugt, wenn es ein ViewModel gibt und nicht umgekehrt, das ViewModel wird erzeugt, wenn es eine View gibt.

Du willst aber eine Aktion per Event an die View geben, die dann eine neue View mit Controller erstellt und dann soll dieser Controller irgendwie eingebunden werden. Schwups ist die Abhängigkeit von der View wieder da und wir haben nichts gewonnen, nur mehr Schreibarbeit.
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)

Geändert von Sir Rufo ( 5. Feb 2015 um 13:11 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#3

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon

  Alt 5. Feb 2015, 14:23
Richtig spannend wird es, wenn man das erweitern möchte:

z.B. benötigen wir auch eine Aktivitätsanzeige mit einem Fortschritt. Nichts leichter als das
Delphi-Quellcode:
unit ViewModel.ActivityViewModel;

interface

uses
  MVVM.ViewModel.ViewModelBase;

type
  TActivityViewModel = class( TViewModelBase )
  private
    FInfo: string;
    procedure SetInfo( const Value: string );
  public
    property Info: string read FInfo write SetInfo;
  end;

  TProgressActivityViewModel = class( TActivityViewModel )
  private
    FProgress: Single;
    procedure SetProgress( const Value: Single );
  public
    property Progress: Single read FProgress write SetProgress;
  end;

implementation

{ TActivityViewModel }

procedure TActivityViewModel.SetInfo( const Value: string );
begin
  if FInfo <> Value
  then
    begin
      FInfo := Value;
      OnPropertyChanged( 'Info' );
    end;
end;

{ TProgressActivityViewModel }

procedure TProgressActivityViewModel.SetProgress( const Value: Single );
begin
  if FProgress <> Value
  then
    begin
      FProgress := Value;
      OnPropertyChanged( 'Progress' );
    end;
end;

end.
Schon haben wir eine Aktivität mit Fortschritt.

Dann mal in das MainViewModel und die Aktivitäten eingebaut (nur die Änderungen)
Delphi-Quellcode:
unit ViewModel.MainViewModel;

interface

uses
  System.SysUtils,
  System.Classes,
  System.Threading,

  de.itnets.Commands,
  de.itnets.References,

  MVVM.ViewModel.ViewModelBase,
  ViewModel.WorkspaceViewModel,
  ViewModel.ActivityViewModel;

type
  TMainViewModel = class( TWorkspaceViewModel )
  private
    FSomeProgressActionCommand: ICommand;
    FSomeRandomActionCommand: ICommand;
    function GetSomeProgressActionCommand: ICommand;
    function GetSomeRandomActionCommand: ICommand;
  public
    property SomeProgressActionCommand: ICommand read GetSomeProgressActionCommand;
    property SomeRandomActionCommand: ICommand read GetSomeRandomActionCommand;
 end;

implementation

{ TMainViewModel }

function TMainViewModel.GetSomeProgressActionCommand: ICommand;
begin
  if not Assigned( FSomeProgressActionCommand )
  then
    FSomeProgressActionCommand := TRelayCommand.Create(
      procedure
      var
        LActivity: AutoRef<TProgressActivityViewModel>;
      begin

        SetCanClose( False );

        // Aktivitätsanzeige setzen

        LActivity := TProgressActivityViewModel.Create;
        LActivity.Reference.Info := 'Performing SomeProgressAction';

        SetActivity( LActivity );

        // Task starten

        TTask.Run(
            procedure
          var
            LIdx: Integer;
          begin
            LActivity.Reference.Progress := 0;
            for LIdx := 1 to 10 do
              begin
                Sleep( 200 );
                LActivity.Reference.Progress := LIdx * 10;
              end;

            // Aktivitätsanzeige ausschalten
            TThread.Synchronize( nil,
                procedure
              begin
                SetActivity( nil );
                SetCanClose( True );
              end );
          end );
      end,
      function: Boolean
      begin
        Result := not FActivity.IsAssigned;
      end );
  Result := FSomeProgressActionCommand;
end;

function TMainViewModel.GetSomeRandomActionCommand: ICommand;
begin
  if not Assigned( FSomeRandomActionCommand )
  then
    FSomeRandomActionCommand := TRelayCommand.Create(
      procedure
      var
        LActivity: AutoRef<TActivityViewModel>;
        LProgressActivity: AutoRef<TProgressActivityViewModel>;
      begin

        SetCanClose( False );

        // Aktivitätsanzeige setzen

        LActivity := TActivityViewModel.Create;
        LProgressActivity := TProgressActivityViewModel.Create;

        SetActivity( LActivity );

        // Task starten

        TTask.Run(
            procedure
          var
            LIdx: Integer;
          begin
            TThread.Queue( nil,
                procedure
              begin
                LActivity.Reference.Info := 'Init data...';
                SetActivity( LActivity );
              end );

            Sleep( 1000 );

            TThread.Queue( nil,
              procedure
              begin
                LProgressActivity.Reference.Info := 'Reading data...';
                LProgressActivity.Reference.Progress := 0;
                SetActivity( LProgressActivity );
              end );
            for LIdx := 1 to 20 do
              begin
                Sleep( 150 );
                TThread.Queue( nil,
                  procedure
                  begin
                    LProgressActivity.Reference.Progress := LIdx * 5;
                  end );
              end;

            TThread.Queue( nil,
                procedure
              begin
                LActivity.Reference.Info := 'Cleanup system...';
                SetActivity( LActivity );
              end );

            Sleep( 1000 );

            // Aktivitätsanzeige ausschalten
            TThread.Synchronize( nil,
              procedure
              begin
                SetActivity( nil );
                SetCanClose( True );
              end );
          end );
      end,
      function: Boolean
      begin
        Result := not FActivity.IsAssigned;
      end );
  Result := FSomeRandomActionCommand;
end;

end.
Da wir TProgressActivityViewModel von TActivityViewModel abgleitet haben brauchen wir die View nicht ändern um lauffähig zu bleiben. Nur der Progress wird eben nicht angezeigt, aber die Funktionalität bleibt gewahrt.

Ok, dann bauen wir uns eine entsprechende View (Frame mit einer ProgressBar und einem TextFeld) und bringen das auf die MainView. Damit das dann auch genutzt wird benötigen wir diese Änderungen an der View (nur die geänderten Teile)
Delphi-Quellcode:
unit View.Form.MainView;

interface

uses
  de.itnets.Events,
  de.itnets.References,
  MVVM.ViewModel.ViewModelBase,
  ViewModel.MainViewModel,

  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
  View.Form.WorkspaceView, MVVM.View.FMX.Frame.Base,
  FMX.Objects, System.Actions, FMX.ActnList, FMX.Layouts,
  
  View.Frame.AcitivityView,
  View.Frame.ProgressAcitivityView;

type
  TMainView = class( TWorkspaceView )
    ActivityCurtain: TRectangle;
    ActivityView1: TActivityView;
    ToolBar1: TToolBar;
    SpeedButton1: TSpeedButton;
    ActionList1: TActionList;
    SomeActionAction: TAction;
    CloseAction: TAction;
    SpeedButton2: TSpeedButton;
    SomeProgressActionAction: TAction;
    SpeedButton3: TSpeedButton;
    ProgressActivityView1: TProgressActivityView;
    SomeRandomActionAction: TAction;
    SpeedButton4: TSpeedButton;
    procedure SomeActionActionExecute( Sender: TObject );
    procedure SomeActionActionUpdate( Sender: TObject );
    procedure CloseActionExecute( Sender: TObject );
    procedure CloseActionUpdate( Sender: TObject );
    procedure SomeProgressActionActionExecute( Sender: TObject );
    procedure SomeProgressActionActionUpdate( Sender: TObject );
    procedure SomeRandomActionActionExecute( Sender: TObject );
    procedure SomeRandomActionActionUpdate( Sender: TObject );
  private
    FMain: WeakRef<TMainViewModel>;
  protected
    procedure AttachToViewModel( AViewModel: TViewModelBase ); override;
    procedure DetachFromViewModel( AViewModel: TViewModelBase ); override;
    procedure ViewModelPropertyChanged( Sender: TObject; const e: TPropertyChangedArgs ); override;
  public

  end;

var
  MainView: TMainView;

implementation

{$R *.fmx}

uses
  System.StrUtils,
  ViewModel.ActivityViewModel;

{ TMainView }

procedure TMainView.SomeProgressActionActionExecute( Sender: TObject );
begin
  inherited;
  FMain.Reference.SomeProgressActionCommand.Execute;
end;

procedure TMainView.SomeProgressActionActionUpdate( Sender: TObject );
begin
  inherited;
  TAction( Sender ).Enabled := FMain.IsAssigned and FMain.Reference.SomeProgressActionCommand.CanExecute;
end;

procedure TMainView.SomeRandomActionActionExecute( Sender: TObject );
begin
  inherited;
  FMain.Reference.SomeRandomActionCommand.Execute;
end;

procedure TMainView.SomeRandomActionActionUpdate( Sender: TObject );
begin
  inherited;
  TAction( Sender ).Enabled := FMain.IsAssigned and FMain.Reference.SomeRandomActionCommand.CanExecute;
end;

procedure TMainView.ViewModelPropertyChanged( Sender: TObject; const e: TPropertyChangedArgs );
begin
  inherited;

  if FMain.IsAssigned
  then
    begin

      if e.Matches( ['Active', 'DisplayName'] )
      then
        begin
          Caption := FMain.Reference.DisplayName + ' (' + IfThen( FMain.Reference.Active, 'Active', 'Inactive' ) + ')';
        end;

      if e.Match( 'Activity' )
      then
        begin
          ActivityCurtain.BringToFront;
          ActivityCurtain.Visible := Assigned( FMain.Reference.Activity );

          if FMain.Reference.Activity is TProgressActivityViewModel
          then
            begin
              ActivityView1.Visible := False;
              ActivityView1.SetViewModel( nil );
              ProgressActivityView1.Visible := True;
              ProgressActivityView1.BringToFront;
              ProgressActivityView1.SetViewModel( FMain.Reference.Activity );
            end
          else if FMain.Reference.Activity is TActivityViewModel
          then
            begin
              ProgressActivityView1.Visible := False;
              ProgressActivityView1.SetViewModel( nil );
              ActivityView1.Visible := Assigned( FMain.Reference.Activity );
              ActivityView1.BringToFront;
              ActivityView1.SetViewModel( FMain.Reference.Activity );
            end
          else
            begin
              ActivityView1.Visible := False;
              ActivityView1.SetViewModel( nil );
              ProgressActivityView1.Visible := False;
              ProgressActivityView1.SetViewModel( nil );
            end;
        end;

    end

end;

end.
Im Anhang nur die Exe
Angehängte Dateien
Dateityp: zip SimpleForm_exe.zip (2,04 MB, 16x aufgerufen)
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)
  Mit Zitat antworten Zitat
Thomas_K

Registriert seit: 16. Apr 2006
71 Beiträge
 
Delphi XE8 Professional
 
#4

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon

  Alt 5. Feb 2015, 15:07
@Sir Rufo,

Ich habe das SimpleForm Beispiel versucht mit XE7 zu öffnen, doch dabei beschwert sich die IDE, das sie nicht alle Dateien finden kann, MVVM.View.FMX.Form.Base.pas, MVVM.View.FMX.Frame.Base.pas ausserdem kann meine Die folgende Units nicht auflösen de.itnets.Events, de.itnets.References, MVVM.Core, MVVM.ViewModel.ViewModelBase, de.itnets.Commands, …
Könntest du die restlichen Dateien die zum Kompilieren notwendig sind ebenfalls veröffentlichen?
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#5

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon

  Alt 5. Feb 2015, 15:20
@Thomas_K
Das habe ich auch geschrieben
Zitat von Sir Rufo:
Im Anhang habe ich den gesamten restlichen Source (exclusive den Basis-Units) und die ausführbare Exe angehängt
Wenn ich die hätte veröffentlichen wollen, hätte ich das gemacht. Aktuell sitzt da zuviel Arbeit drin, als dass ich die einfach so herausgeben möchte. Ob ich die jemals herausgeben werde habe ich auch nicht entschieden (mir noch keinen Kopf drüber gemacht).

Das Beispiel-Projekt habe ich auch nur veröffentlicht um einen direkten Vergleich zwischen dem vorgestellten Konzept und MVVM zu ermöglichen, zu zeigen, dass MVVM mit Delphi durchaus zuverlässig funktioniert und wie MVVM grundsätzlich funktioniert.
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)
  Mit Zitat antworten Zitat
BlackSeven

Registriert seit: 25. Sep 2004
79 Beiträge
 
Delphi XE7 Professional
 
#6

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon

  Alt 6. Feb 2015, 07:33
MVVM ist in DSharp meiner Erfahrung nach am besten implementiert.
Ich hoffe, dass Stefan dieses Jahr etwas mehr Zeit findet, den MVVM-Branch (Caliburn Micro) in DSharp weiterzuentwickeln.

https://bitbucket.org/sglienke/dsharp
  Mit Zitat antworten Zitat
FAM

Registriert seit: 22. Dez 2014
47 Beiträge
 
Delphi XE Enterprise
 
#7

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon

  Alt 6. Feb 2015, 08:08
Habe das ganze mit dem MVVP Konzept implementiert, das Problem ist jetzt allerdings meine Delphi Version (XE), dort gibt es noch keine Live-Bindings. Kennt jemand ein Workaround um das "zu simulieren"?
  Mit Zitat antworten Zitat
mkinzler
(Moderator)

Registriert seit: 9. Dez 2005
Ort: Heilbronn
39.877 Beiträge
 
Delphi 11 Alexandria
 
#8

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon

  Alt 6. Feb 2015, 08:16
Habe das ganze mit dem MVVP Konzept implementiert, das Problem ist jetzt allerdings meine Delphi Version (XE), dort gibt es noch keine Live-Bindings. Kennt jemand ein Workaround um das "zu simulieren"?
http://mitov.com/products/openwirelivebindings#overview
Markus Kinzler
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#9

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon

  Alt 6. Feb 2015, 08:28
Habe das ganze mit dem MVVP Konzept implementiert, das Problem ist jetzt allerdings meine Delphi Version (XE), dort gibt es noch keine Live-Bindings. Kennt jemand ein Workaround um das "zu simulieren"?
Ja, ich habe einmal an jedem ViewModel einen PropertyChanged Event hängen und kann damit entweder den BindSourceAdapter refreshen oder eben manuell den Wert übergeben. Wenn manuell gebunden, dann brauche ich aber auch ein manuelles Rückschreiben in das ViewModel, was aber eigentlich idR mit einem Event geht, wo dann von allen die Werte zurückgeschrieben werden.

Für Listen habe ich eine Observable Collection mit einem CollectionChanged Event. Dieser teilt mir genau mit, was sich in der Liste geändert hat. Damit kann ich dann jede andere Liste (Listendarstellung) mit möglichst wenig Stress (für mich und die Darstellung) synchron halten. Der Event liefert mir die Art der Änderung, den jeweiligen Index, wo sich etwas geändert hat und die jeweiligen Items (jeweils für OldItems und NewItems).
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)

Geändert von Sir Rufo ( 6. Feb 2015 um 08:31 Uhr)
  Mit Zitat antworten Zitat
Antwort Antwort

 

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 03:05 Uhr.
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz