2009-09-14 14 views
14

मैं अपने दिमाग को सी #/विनफॉर्म ऐप में उपयोग किए गए एमवीपी पैटर्न के चारों ओर लपेटने की कोशिश कर रहा हूं। इसलिए मैंने सभी विवरणों को पूरा करने के लिए एप्लिकेशन की तरह एक सरल "नोटपैड" बनाया। मेरा लक्ष्य कुछ ऐसा बनाना है जो क्लासिक विंडोज व्यवहार को खुले, सहेजने, नए के साथ-साथ शीर्षक पट्टी में सहेजी गई फ़ाइल के नाम को प्रतिबिंबित करता है। साथ ही, जब सहेजे गए परिवर्तन नहीं होते हैं, तो शीर्षक पट्टी में * शामिल होना चाहिए।क्रिटिक मेरे सरल एमवीपी विनफॉर्म ऐप

इसलिए मैंने एक दृश्य & एक प्रस्तुतकर्ता बनाया जो एप्लिकेशन की दृढ़ता स्थिति का प्रबंधन करता है। एक सुधार जिसे मैंने माना है, टेक्स्ट हैंडलिंग कोड तोड़ रहा है ताकि दृश्य/प्रस्तुतकर्ता वास्तव में एक एकल उद्देश्य इकाई हो।

यहाँ

alt text

मैं नीचे प्रासंगिक फ़ाइलों के सभी शामिल कर रहा हूँ में संदर्भ के लिए एक स्क्रीन शॉट है ...। मुझे इस बारे में फीडबैक में दिलचस्पी है कि मैंने इसे सही तरीके से किया है या यदि सुधार करने के तरीके हैं।

NoteModel.cs:

public class NoteModel : INotifyPropertyChanged 
{ 
    public string Filename { get; set; } 
    public bool IsDirty { get; set; } 
    string _sText; 
    public readonly string DefaultName = "Untitled.txt"; 

    public string TheText 
    { 
     get { return _sText; } 
     set 
     { 
      _sText = value; 
      PropertyHasChanged("TheText"); 
     } 
    } 

    public NoteModel() 
    { 
     Filename = DefaultName; 
    } 

    public void Save(string sFilename) 
    { 
     FileInfo fi = new FileInfo(sFilename); 

     TextWriter tw = new StreamWriter(fi.FullName); 
     tw.Write(TheText); 
     tw.Close(); 

     Filename = fi.FullName; 
     IsDirty = false; 
    } 

    public void Open(string sFilename) 
    { 
     FileInfo fi = new FileInfo(sFilename); 

     TextReader tr = new StreamReader(fi.FullName); 
     TheText = tr.ReadToEnd(); 
     tr.Close(); 

     Filename = fi.FullName; 
     IsDirty = false; 
    } 

    private void PropertyHasChanged(string sPropName) 
    { 
     IsDirty = true; 
     PropertyChanged.Invoke(this, new PropertyChangedEventArgs(sPropName)); 
    } 


    #region INotifyPropertyChanged Members 

    public event PropertyChangedEventHandler PropertyChanged; 

    #endregion 
} 

Form2.cs:

public partial class Form2 : Form, IPersistenceStateView 
{ 
    PersistenceStatePresenter _peristencePresenter; 

    public Form2() 
    { 
     InitializeComponent(); 
    } 

    #region IPersistenceStateView Members 

    public string TheText 
    { 
     get { return this.textBox1.Text; } 
     set { textBox1.Text = value; } 
    } 

    public void UpdateFormTitle(string sTitle) 
    { 
     this.Text = sTitle; 
    } 

    public string AskUserForSaveFilename() 
    { 
     SaveFileDialog dlg = new SaveFileDialog(); 
     DialogResult result = dlg.ShowDialog(); 
     if (result == DialogResult.Cancel) 
      return null; 
     else 
      return dlg.FileName; 
    } 

    public string AskUserForOpenFilename() 
    { 
     OpenFileDialog dlg = new OpenFileDialog(); 
     DialogResult result = dlg.ShowDialog(); 
     if (result == DialogResult.Cancel) 
      return null; 
     else 
      return dlg.FileName; 
    } 

    public bool AskUserOkDiscardChanges() 
    { 
     DialogResult result = MessageBox.Show("You have unsaved changes. Do you want to continue without saving your changes?", "Disregard changes?", MessageBoxButtons.YesNo); 

     if (result == DialogResult.Yes) 
      return true; 
     else 
      return false; 
    } 

    public void NotifyUser(string sMessage) 
    { 
     MessageBox.Show(sMessage); 
    } 

    public void CloseView() 
    { 
     this.Dispose(); 
    } 

    public void ClearView() 
    { 
     this.textBox1.Text = String.Empty; 
    } 

    #endregion 

    private void btnSave_Click(object sender, EventArgs e) 
    { 
     _peristencePresenter.Save(); 
    } 

    private void btnOpen_Click(object sender, EventArgs e) 
    { 
     _peristencePresenter.Open(); 
    } 

    private void btnNew_Click(object sender, EventArgs e) 
    { 
     _peristencePresenter.CleanSlate(); 
    } 

    private void Form2_Load(object sender, EventArgs e) 
    { 
     _peristencePresenter = new PersistenceStatePresenter(this); 
    } 

    private void Form2_FormClosing(object sender, FormClosingEventArgs e) 
    { 
     _peristencePresenter.Close(); 
     e.Cancel = true; // let the presenter handle the decision 
    } 

    private void textBox1_TextChanged(object sender, EventArgs e) 
    { 
     _peristencePresenter.TextModified(); 
    } 
} 

IPersistenceStateView.cs

public interface IPersistenceStateView 
{ 
    string TheText { get; set; } 

    void UpdateFormTitle(string sTitle); 
    string AskUserForSaveFilename(); 
    string AskUserForOpenFilename(); 
    bool AskUserOkDiscardChanges(); 
    void NotifyUser(string sMessage); 
    void CloseView(); 
    void ClearView(); 
} 

PersistenceStatePresenter.cs

public class PersistenceStatePresenter 
{ 
    IPersistenceStateView _view; 
    NoteModel _model; 

    public PersistenceStatePresenter(IPersistenceStateView view) 
    { 
     _view = view; 

     InitializeModel(); 
     InitializeView(); 
    } 

    private void InitializeModel() 
    { 
     _model = new NoteModel(); // could also be passed in as an argument. 
     _model.PropertyChanged += new PropertyChangedEventHandler(_model_PropertyChanged); 
    } 

    private void InitializeView() 
    { 
     UpdateFormTitle(); 
    } 

    private void _model_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) 
    { 
     if (e.PropertyName == "TheText") 
      _view.TheText = _model.TheText; 

     UpdateFormTitle(); 
    } 

    private void UpdateFormTitle() 
    { 
     string sTitle = _model.Filename; 
     if (_model.IsDirty) 
      sTitle += "*"; 

     _view.UpdateFormTitle(sTitle); 
    } 

    public void Save() 
    { 
     string sFilename; 

     if (_model.Filename == _model.DefaultName || _model.Filename == null) 
     { 
      sFilename = _view.AskUserForSaveFilename(); 
      if (sFilename == null) 
       return; // user canceled the save request. 
     } 
     else 
      sFilename = _model.Filename; 

     try 
     { 
      _model.Save(sFilename); 
     } 
     catch (Exception ex) 
     { 
      _view.NotifyUser("Could not save your file."); 
     } 

     UpdateFormTitle(); 
    } 

    public void TextModified() 
    { 
     _model.TheText = _view.TheText; 
    } 

    public void Open() 
    { 
     CleanSlate(); 

     string sFilename = _view.AskUserForOpenFilename(); 

     if (sFilename == null) 
      return; 

     _model.Open(sFilename); 
     _model.IsDirty = false; 
     UpdateFormTitle(); 
    } 

    public void Close() 
    { 
     bool bCanClose = true; 

     if (_model.IsDirty) 
      bCanClose = _view.AskUserOkDiscardChanges(); 

     if (bCanClose) 
     { 
      _view.CloseView(); 
     } 
    } 

    public void CleanSlate() 
    { 
     bool bCanClear = true; 

     if (_model.IsDirty) 
      bCanClear = _view.AskUserOkDiscardChanges(); 

     if (bCanClear) 
     { 
      _view.ClearView(); 
      InitializeModel(); 
      InitializeView(); 
     } 
    } 
} 
+6

यह प्रश्न अब विषय पर नहीं है, भले ही इसे पोस्ट किया गया हो तो यह ठीक रहेगा। इन दिनों के प्रश्न _Code Review_ पर बेहतर होंगे। – halfer

उत्तर

5

एक परिपूर्ण एमवीपी निष्क्रिय दृश्य पैटर्न के करीब आने का एकमात्र तरीका WinForms संवाद का उपयोग करने के बजाय संवाद के लिए अपने स्वयं के एमवीपी triads लिखना होगा। फिर आप दृश्य निर्माण तर्क को प्रस्तुतकर्ता से दृश्य में ले जा सकते हैं।

यह एमवीपी ट्रायड्स के बीच संचार के विषय में आता है, यह विषय आमतौर पर इस पैटर्न की जांच करते समय चमकदार होता है। जो मैंने पाया है वह मेरे प्रस्तुतियों पर ट्रायड्स को जोड़ना है।

public class PersistenceStatePresenter 
{ 
    ... 
    public Save 
    { 
     string sFilename; 

     if (_model.Filename == _model.DefaultName || _model.Filename == null) 
     { 
      var openDialogPresenter = new OpenDialogPresenter(); 
      openDialogPresenter.Show(); 
      if(!openDialogPresenter.Cancel) 
      { 
       return; // user canceled the save request. 
      } 
      else 
       sFilename = openDialogPresenter.FileName; 

     ... 

Show() विधि, ज़ाहिर है, एक अवर्णित OpenDialogView, जो उपयोगकर्ताओं को इनपुट स्वीकार करते हैं और यह OpenDialogPresenter के पास भेजने के हैं दिखाने के लिए ज़िम्मेदार है। किसी भी मामले में, यह स्पष्ट होना शुरू हो जाना चाहिए कि एक प्रस्तुतकर्ता एक विस्तृत मध्यस्थ है। विभिन्न परिस्थितियों में, आप एक बिचौलिया बाहर refactor करने के लिए परीक्षा हो सकती है, लेकिन यहाँ अपने को जानबूझकर है: परीक्षण करने के लिए

  • देख सकते हैं और के बीच सीधा निर्भरता से बचें देखने के लिए, जहां यह कठिन है से बाहर

    • रखें तर्क मॉडल

    कभी-कभी मैंने एमवीपी ट्रायड संचार के लिए उपयोग किए गए मॉडल को भी देखा है।इसका लाभ प्रेजेंटर को एक दूसरे को जानने की जरूरत नहीं है। यह आम तौर पर मॉडल में एक राज्य स्थापित करके पूरा किया जाता है, जो एक घटना को ट्रिगर करता है, जो कि एक अन्य प्रस्तुति तब सुनता है। एक दिलचस्प विचार। एक मैंने व्यक्तिगत रूप से उपयोग नहीं किया है।

  • +0

    प्रतिक्रिया के लिए धन्यवाद। आपने OpenDialogPresenter के साथ var का उपयोग क्यों किया? क्या आपके पास त्रिभुज संचार से संबंधित कोई लिंक हैं। मुझे लगता है कि मेरा वर्तमान दृष्टिकोण आदर्श प्रस्तुतियों में कार्यों का कारण बनने के लिए मॉडल के साथ मॉडल में झुका रहा है। क्या यह एक बुरा विचार है? –

    +0

    मैं डिफ़ॉल्ट रूप से var का उपयोग करता हूं जब तक कोई वैध कारण न हो, केवल व्यक्तिगत वरीयता। मैंने एमवीपी ट्रायड संचार से निपटने वाले कुछ लिंक के साथ अपना जवाब अपडेट किया। –

    2

    सबकुछ अच्छा दिखता है, केवल एक ही संभावित स्तर है, मैं फ़ाइल को सहेजने के लिए तर्क को दूर करने के लिए आगे बढ़ता हूं और प्रदाताओं द्वारा इसे संभाला जाता है ताकि बाद में आप डेटाबेस, ईमेल, क्लाउड स्टोरेज जैसे वैकल्पिक बचत विधियों में आसानी से फ्लेक्स कर सकें।

    आईएमओ कभी भी आप फ़ाइल सिस्टम को छूने के साथ सौदा करते हैं, यह हमेशा एक स्तर को दूर करने के लिए बेहतर होता है, यह भी मजाक कर रहा है और बहुत आसान परीक्षण कर रहा है।

    +0

    हां, ज़ाहिर है। इस चरण में इसे सरल रखने की कोशिश कर रहा है। –

    1

    एक बात मैं करना पसंद प्रत्यक्ष से छुटकारा पाने जाता है:

    यहाँ तकनीक दूसरों त्रय संचार से निपटने के लिए इस्तेमाल किया है से कुछ के साथ कुछ लिंक है प्रस्तुतकर्ता संचार को देखें। इसका कारण यह है कि दृश्य यूआई स्तर पर है और प्रस्तुतकर्ता व्यवसाय परत पर है। मुझे अपनी परतों को एक दूसरे के अंतर्निहित ज्ञान के लिए पसंद नहीं है, और मैं जितना संभव हो सके प्रत्यक्ष संचार को सीमित करना चाहता हूं। आम तौर पर, मेरा मॉडल एकमात्र चीज है जो परतों से आगे निकलती है। इस प्रकार प्रस्तुतकर्ता इंटरफेस के माध्यम से दृश्य को जोड़ता है, लेकिन दृश्य प्रस्तुतकर्ता के खिलाफ बहुत सी सीधी कार्रवाई नहीं करता है। मुझे प्रेजेंटर को प्रतिक्रिया के आधार पर मेरे विचार को सुनने और कुशलतापूर्वक उपयोग करने में सक्षम होना पसंद है, लेकिन मैं अपने विचार को अपने प्रस्तुतकर्ता के ज्ञान को सीमित करना भी पसंद करता हूं।

    मैं अपने IPersistenceStateView करने के लिए कुछ घटनाओं जोड़ेंगे:

     
    event EventHandler Save; 
    event EventHandler Open; 
    // etc. 
    

    तब मेरे प्रस्तुतकर्ता उन घटनाओं को सुनने के लिए है:

     
    public PersistenceStatePresenter(IPersistenceStateView view) 
    { 
        _view = view; 
    
        _view.Save += (sender, e) => this.Save(); 
        _view.Open += (sender, e) => this.Open(); 
        // etc. 
    
        InitializeModel(); 
        InitializeView(); 
    } 
    

    तब बटन क्लिक घटनाओं आग के लिए दृश्य कार्यान्वयन को बदलने ।

    यह प्रस्तुतकर्ता को कठपुतली के समान कार्य करता है, दृश्य पर प्रतिक्रिया करता है और अपने तार खींचता है; उसमें, प्रस्तुतकर्ता के तरीकों पर सीधी कॉल को हटा देना। आपको अभी भी प्रस्तुतकर्ता को दृश्य में तुरंत चालू करना होगा, लेकिन यह केवल उस प्रत्यक्ष कार्य के बारे में है जो आप करेंगे।

    +0

    मुझे यह सुझाव भी पसंद है। –

    +0

    @Travis: उस दृष्टिकोण के साथ समस्या, यदि कोई है, तो यह है कि दृश्य का नियंत्रण अब प्रस्तुतकर्ता द्वारा ही किया जाने की गारंटी नहीं है, क्योंकि आपको ईवेंट को सार्वजनिक करने की आवश्यकता है। –

    +0

    @ जोहान: मुझे नहीं लगता कि यह समस्या बिल्कुल है। यह दृश्य को पूरी तरह से स्वतंत्र, स्वयं निहित और इसे नियंत्रित करने के बारे में अनजान बनाता है। मुझे लगता है कि एमवीपी पैटर्न का लाभ उठाने के दौरान, आप लचीलेपन को जोड़ते हैं, जिससे आप अलग-अलग संदर्भों में दृश्य का उपयोग कर सकते हैं। –

    संबंधित मुद्दे