MVP - dataGridView

0

Witam
Zacząłem zabawę z MVP i mam pewną wątpliwość. Zrobiłem prosty program do obracania wektorów. Wygląd jest przedtawiony poniżej:
user image

Model & Presenter:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace VectorRotation
{
    public struct SPoint
    {
        public double X { get; set; }
        public double Y { get; set; }
    }

    public interface IView
    {
        List<SPoint> InputVectorCoordinates {get;}
        double Angle { get; }
        List<SPoint> OutputVectorCoordinates { set; }
        event EventHandler<EventArgs> CalculateTask;
    }
    #region Presenter
    public class TPresenter
    {
        private readonly IView _view;
        public TPresenter(IView view)
        {
            this._view = view;
            this._view.CalculateTask += this.CalculateT;
        }
        private void CalculateT(object sender, EventArgs e)//button click event
        {
            this.TransformCoordinates();
        }
        private void TransformCoordinates()
        {
            List<SPoint> inputList = this._view.InputVectorCoordinates;
            if (inputList.Count == 0)
                return;
            
            double angle = this._view.Angle;
            if (angle == double.NaN)
                return;

            TCalculations calcs = new TCalculations();
            List<SPoint> resultList = calcs.TransformVector(angle, inputList);

            this._view.OutputVectorCoordinates = resultList;

        }

        

    }
    #endregion

    #region Model
    public class TCalculations //model class
    {

        public List<SPoint> TransformVector(double pAngle, List<SPoint> pCoordinates)
        {
            double x, y;
            double xp,yp;
            SPoint point = new SPoint() ;
            List<SPoint> TransformedCoord = new List<SPoint>();

            for (int i = 0; i <= pCoordinates.Count - 1; i++)
            {
                x = pCoordinates[i].X;
                y = pCoordinates[i].Y;
                xp = x * Math.Cos(pAngle) - y * Math.Sin(pAngle);
                yp = x * Math.Sin(pAngle) + y * Math.Cos(pAngle);
                point.X = xp;
                point.Y = yp;
                TransformedCoord.Add(point);
            }
            return TransformedCoord;
        }

    }
    #endregion
}
 

View:

 namespace VectorRotation
{
    public partial class Form1 : Form, IView
    {
        private TPresenter presenter;

        #region Properties
        public List<SPoint> InputVectorCoordinates 
        {
            get
            {
                List<SPoint> tempList = new List<SPoint>();
                SPoint point = new SPoint();
                double x, y;
                for (int i =0;i<=this.dgvInputData.Rows.Count-2;i++)
                {
                    try
                    {
                        x = double.Parse(this.dgvInputData.Rows[i].Cells[0].Value.ToString());
                        y = double.Parse(this.dgvInputData.Rows[i].Cells[1].Value.ToString());
                        point.X = x;
                        point.Y = y;
                        tempList.Add(point);
                    }
                    catch
                    {
                        this.ThrowErrorMessage("Wprowadzaj wyłącznie liczby.");
                    }
                }
                return tempList;
            }
        }
        public double Angle
        {
            get
            {
                double a = double.NaN;
                try
                {
                    a = double.Parse(this.txtRotationAngle.Text) * Math.PI / 180;//converted to radians
                }
                catch
                {
                    this.ThrowErrorMessage("Wprowadzaj wyłącznie liczby");
                }
                return a;
            }
        }
        public List<SPoint> OutputVectorCoordinates
        {
            set
            {
                List<SPoint> tempList = value;
                this.dgvOutputData.Rows.Clear();
                for (int i = 0; i <= tempList.Count - 1; i++)
                {
                    this.dgvOutputData.Rows.Add();
                    this.dgvOutputData.Rows[i].Cells[0].Value = tempList[i].X;
                    this.dgvOutputData.Rows[i].Cells[1].Value = tempList[i].Y;
                }
            }
        }
        #endregion

        #region Events
        public event EventHandler<EventArgs> CalculateTask;
        #endregion

        private void ThrowErrorMessage(string pMsg)
        {
            MessageBox.Show(pMsg, "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        public Form1()
        {
            InitializeComponent();
        }

        private void btnCalculations_Click(object sender, EventArgs e)
        {
            if (this.CalculateTask != null)
            {
                this.CalculateTask(this, EventArgs.Empty);
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            this.presenter = new TPresenter(this);
        }
    }
}

Poprzez właściwość

List<double> InputVectorCoordinates {get;} 

pobieram dane z DataGridView. Mam pewną wątpliwość.

  1. Czy transformacja danych z DataGridView do listy powinna odbywać się w View (tak jak zrobiłem) czy w Presenter?
  2. Czy sprawdzenie danych (czy są liczbami) powinno odbywać się w View czy w Presenter?

Dziękuje za sugestie
Pozdrawiam

1

Prezenter pełni rolę pośrednika pomiędzy widokiem. Natomiast widok odpowiada za obsługę interakcji użytkownika z aplikacją i reagowanie na zdarzenia od użytkownika. A więc te dwie rzeczy raczej w prezenterze powinny funkcjonować. Ale nie mam do końca pewności. Może niech się wypowiedzą bardziej doświadczeni:)

2

Abstrahując już od notacji węgierskiej (SPoint, TPresenter, 'pMsg), definiowania zmiennych nie tam, gdzie są potrzebne oraz używania Parse zamiast TryParse, ten kod trudno uznać za MVP. Po co w prezenterze jakieś zdarzenie, które jest podpinane do przycisku? Prezenter powinien trzymać się z dala od wszystkich przycisków i zdarzeń, to w Form1 powinieneś wywoływać metody prezentera w reakcji na zdarzenia przycisku.

Co do pytań - to zależy. Ja bym to raczej zrobił w widoku, bo wolę gdy widok sam potrafi dostarczyć już prawidłowe dane odpowiednich typów. Z drugiej strony, te operacje można uznać za część logiki prezentacji, i przenieść je do prezentera.

0
somekind napisał(a):

Abstrahując już od notacji węgierskiej (SPoint, TPresenter, 'pMsg), definiowania zmiennych nie tam, gdzie są potrzebne oraz używania Parse zamiast TryParse, ten kod trudno uznać za MVP. Po co w prezenterze jakieś zdarzenie, które jest podpinane do przycisku? Prezenter powinien trzymać się z dala od wszystkich przycisków i zdarzeń, to w Form1 powinieneś wywoływać metody prezentera w reakcji na zdarzenia przycisku.

Mógłbyś wyjaśnić o co chodzi z "definiowaniem zmiennych nie tam, gdzie są potrzebne"?
Po dłuższym zastanowieniu też mi się nie podoba ten "event" w Presenterze. Bazowałem na poniższym tutorialu:
http://www.dreamincode.net/forums/topic/342849-introducing-mvp-model-view-presenter-pattern-winforms/

somekind napisał(a):

Co do pytań - to zależy. Ja bym to raczej zrobił w widoku, bo wolę gdy widok sam potrafi dostarczyć już prawidłowe dane odpowiednich typów. Z drugiej strony, te operacje można uznać za część logiki prezentacji, i przenieść je do prezentera.

Coraz bardziej się przekonuje, że w programowaniu jest jak w każdej innej branży. Niby są jakies wzorce, normy, standardy a i tak każdy robi po swojemu:)
Mimo wszystko, jakby ktoś mógł polecić jakiś dobry poradnik jak pisać dobry kod to byłbym wdzięczny.
Dzieki.

1
mch0588 napisał(a):

Mógłbyś wyjaśnić o co chodzi z "definiowaniem zmiennych nie tam, gdzie są potrzebne"?

Zmienne point, x i y deklarujesz przed pętlą zamiast w niej.

Po dłuższym zastanowieniu też mi się nie podoba ten "event" w Presenterze. Bazowałem na poniższym tutorialu:
http://www.dreamincode.net/forums/topic/342849-introducing-mvp-model-view-presenter-pattern-winforms/

Ten tutorial jest słaby.
O to chodzi w MVP, żeby dało się podmienić technologię GUI bez zmiany kodu w Prezenterach. Coś takiego jak eventy są częścią charakterystyczną dla technologii GUI, więc obsługiwanie ich w Prezenterach to po prostu wyciek abstrakcji. Widoki powinny pozwalać Prezenterom na pobieranie i ustawianie danych, oraz wywoływać metody Prezenterów.

somekind napisał(a):

Coraz bardziej się przekonuje, że w programowaniu jest jak w każdej innej branży. Niby są jakies wzorce, normy, standardy a i tak każdy robi po swojemu:)

Pisanie wszystkiego po swojemu zazwyczaj kończy się tragedią. Są wzorce i zasady, tylko po prostu czasem jest kilka prawidłowych rozwiązań, w zależności od celu, który chcemy osiągnąć. Plik z dysku też możesz wczytać jako kolekcję linii tekstu albo tablicę bajtów, żadne z tych rozwiązań nie jest lepsze, po prostu każde ma inny cel.

0
somekind napisał(a):
mch0588 napisał(a):

Mógłbyś wyjaśnić o co chodzi z "definiowaniem zmiennych nie tam, gdzie są potrzebne"?

Zmienne point, x i y deklarujesz przed pętlą zamiast w niej.

Właśnie sprawdziłem na stackoverflow i masz racje:) nigdy bym nie wpadł żeby definiować zmienne w pętli (zawsze myślałem ze to spowalnia).
Jeszcze raz dziekuje za cenne uwagi.
Pozdrawiam

1 użytkowników online, w tym zalogowanych: 0, gości: 1