- The Interface
- Implementation
- Building a Window
- The Payroll Window
- The Unveiling
- Conclusion
- Bibliography
The Payroll Window
In building the Payroll view, shown in Figure 38-4, we'll use the same MODEL VIEW PRESENTER pattern used in the Add Employee view.
Figure 38-4 Design of the Payroll view
Listings 38-7 and 38-8 show all the code in this part of the design. Altogether, the development of this view is very similar to that of the Add Employee view. For that reason, we will not pay it much attention. Of particular note, however, is the ViewLoader hierarchy.
Sooner or later in developing this window, we'll get around to implementing an EventHandler for the Add Employee MenuItem. This EventHandler will call the AddEmployeeActionInvoked method on PayrollPresenter. At this point, the AddEmployeeWindow needs to pop up. Is PayrollPresenter supposed to instantiate AddEmployeeWindow? So far, we have done well to decouple the UI from the application. Were it to instantiate the AddEmployeeWindow, PayrollPresenter would be violating DIP. Yet someone must create AddEmployeeWindow.
Factory pattern to the rescue! This is the exact problem that FACTORY was designed to solve. ViewLoader, and its derivatives, are in fact an implementation of the FACTORY pattern. It declares two methods: LoadPayrollView and LoadAddEmployeeView. WindowsViewLoader implements these methods to create Windows Forms and display them. The MockViewLoader, which can easily replace the WindowsViewLoader, makes testing much easier.
With the ViewLoader in place, PayrollPresenter need not depend on any Windows form classes. It simply makes a call to the LoadAddEmployeeView on its reference to ViewLoader. If the need ever arises, we can change the whole user interface for Payroll by swapping the ViewLoader implementation. No code needs to change. That's power! That's OCP!
Listing 38-7. PayrollPresenterTest.cs
using System; using NUnit.Framework; using Payroll; namespace PayrollUI { [TestFixture] public class PayrollPresenterTest { private MockPayrollView view; private PayrollPresenter presenter; private PayrollDatabase database; private MockViewLoader viewLoader; [SetUp] public void SetUp() { view = new MockPayrollView(); database = new InMemoryPayrollDatabase(); viewLoader = new MockViewLoader(); presenter = new PayrollPresenter(database, viewLoader); presenter.View = view; } [Test] public void Creation() { Assert.AreSame(view, presenter.View); Assert.AreSame(database, presenter.Database); Assert.IsNotNull(presenter.TransactionContainer); } [Test] public void AddAction() { TransactionContainer container = presenter.TransactionContainer; Transaction transaction = new MockTransaction(); container.Add(transaction); string expected = transaction.ToString() + Environment.NewLine; Assert.AreEqual(expected, view.transactionsText); } [Test] public void AddEmployeeAction() { presenter.AddEmployeeActionInvoked(); Assert.IsTrue(viewLoader.addEmployeeViewWasLoaded); } [Test] public void RunTransactions() { MockTransaction transaction = new MockTransaction(); presenter.TransactionContainer.Add(transaction); Employee employee = new Employee(123, "John", "123 Baker St."); database.AddEmployee(employee); presenter.RunTransactions(); Assert.IsTrue(transaction.wasExecuted); Assert.AreEqual("", view.transactionsText); string expectedEmployeeTest = employee.ToString() + Environment.NewLine; Assert.AreEqual(expectedEmployeeTest, view.employeesText); } } }
Listing 38-8. PayrollPresenter.cs
using System; using System.Text; using Payroll; namespace PayrollUI { public class PayrollPresenter { private PayrollView view; private readonly PayrollDatabase database; private readonly ViewLoader viewLoader; private TransactionContainer transactionContainer; public PayrollPresenter(PayrollDatabase database, ViewLoader viewLoader) { this.view = view; this.database = database; this.viewLoader = viewLoader; TransactionContainer.AddAction addAction = new TransactionContainer.AddAction(TransactionAdded); transactionContainer = new TransactionContainer(addAction); } public PayrollView View { get { return view; } set { view = value; } } public TransactionContainer TransactionContainer { get { return transactionContainer; } } public void TransactionAdded() { UpdateTransactionsTextBox(); } private void UpdateTransactionsTextBox() { StringBuilder builder = new StringBuilder(); foreach(Transaction transaction in transactionContainer.Transactions) { builder.Append(transaction.ToString()); builder.Append(Environment.NewLine); } view.TransactionsText = builder.ToString(); } public PayrollDatabase Database { get { return database; } } public virtual void AddEmployeeActionInvoked() { viewLoader.LoadAddEmployeeView(transactionContainer); } public virtual void RunTransactions() { foreach(Transaction transaction in transactionContainer.Transactions) transaction.Execute(); transactionContainer.Clear(); UpdateTransactionsTextBox(); UpdateEmployeesTextBox(); } private void UpdateEmployeesTextBox() { StringBuilder builder = new StringBuilder(); foreach(Employee employee in database.GetAllEmployees()) { builder.Append(employee.ToString()); builder.Append(Environment.NewLine); } view.EmployeesText = builder.ToString(); } } }
Listing 38-9. PayrollView.cs
namespace PayrollUI { public interface PayrollView { string TransactionsText { set; } string EmployeesText { set; } PayrollPresenter Presenter { set; } } }
Listing 38-10. MockPayrollView.cs
namespace PayrollUI { public class MockPayrollView : PayrollView { public string transactionsText; public string employeesText; public PayrollPresenter presenter; public string TransactionsText { set { transactionsText = value; } } public string EmployeesText { set { employeesText = value; } } public PayrollPresenter Presenter { set { presenter = value; } } } }
Listing 38-11. ViewLoader.cs
namespace PayrollUI { public interface ViewLoader { void LoadPayrollView(); void LoadAddEmployeeView( TransactionContainer transactionContainer); } }
Listing 38-12. MockViewLoader.cs
namespace PayrollUI { public class MockViewLoader : ViewLoader { public bool addEmployeeViewWasLoaded; private bool payrollViewWasLoaded; public void LoadPayrollView() { payrollViewWasLoaded = true; } public void LoadAddEmployeeView( TransactionContainer transactionContainer) { addEmployeeViewWasLoaded = true; } } }
Listing 38-13. WindowViewLoaderTest.cs
using System.Windows.Forms; using NUnit.Framework; using Payroll; namespace PayrollUI { [TestFixture] public class WindowViewLoaderTest { private PayrollDatabase database; private WindowViewLoader viewLoader; [SetUp] public void SetUp() { database = new InMemoryPayrollDatabase(); viewLoader = new WindowViewLoader(database); } [Test] public void LoadPayrollView() { viewLoader.LoadPayrollView(); Form form = viewLoader.LastLoadedView; Assert.IsTrue(form is PayrollWindow); Assert.IsTrue(form.Visible); PayrollWindow payrollWindow = form as PayrollWindow; PayrollPresenter presenter = payrollWindow.Presenter; Assert.IsNotNull(presenter); Assert.AreSame(form, presenter.View); } [Test] public void LoadAddEmployeeView() { viewLoader.LoadAddEmployeeView( new TransactionContainer(null)); Form form = viewLoader.LastLoadedView; Assert.IsTrue(form is AddEmployeeWindow); Assert.IsTrue(form.Visible); AddEmployeeWindow addEmployeeWindow = form as AddEmployeeWindow; Assert.IsNotNull(addEmployeeWindow.Presenter); } } }
Listing 38-14. WindowViewLoader.cs
using System.Windows.Forms; using Payroll; namespace PayrollUI { public class WindowViewLoader : ViewLoader { private readonly PayrollDatabase database; private Form lastLoadedView; public WindowViewLoader(PayrollDatabase database) { this.database = database; } public void LoadPayrollView() { PayrollWindow view = new PayrollWindow(); PayrollPresenter presenter = new PayrollPresenter(database, this); view.Presenter = presenter; presenter.View = view; LoadView(view); } public void LoadAddEmployeeView( TransactionContainer transactionContainer) { AddEmployeeWindow view = new AddEmployeeWindow(); AddEmployeePresenter presenter = new AddEmployeePresenter(view, transactionContainer, database); view.Presenter = presenter; LoadView(view); } private void LoadView(Form view) { view.Show(); lastLoadedView = view; } public Form LastLoadedView { get { return lastLoadedView; } } } }
Listing 38-15. PayrollWindowTest.cs
using NUnit.Framework; namespace PayrollUI { [TestFixture] public class PayrollWindowTest { private PayrollWindow window; private MockPayrollPresenter presenter; [SetUp] public void SetUp() { window = new PayrollWindow(); presenter = new MockPayrollPresenter(); window.Presenter = this.presenter; window.Show(); } [TearDown] public void TearDown() { window.Dispose(); } [Test] public void TransactionsText() { window.TransactionsText = "abc 123"; Assert.AreEqual("abc 123", window.transactionsTextBox.Text); } [Test] public void EmployeesText() { window.EmployeesText = "some employee"; Assert.AreEqual("some employee", window.employeesTextBox.Text); } [Test] public void AddEmployeeAction() { window.addEmployeeMenuItem.PerformClick(); Assert.IsTrue(presenter.addEmployeeActionInvoked); } [Test] public void RunTransactions() { window.runButton.PerformClick(); Assert.IsTrue(presenter.runTransactionCalled); } } }
Listing 38-16. PayrollWinow.cs
namespace PayrollUI { public class PayrollWindow : System.Windows.Forms.Form, PayrollView { private System.Windows.Forms.MainMenu mainMenu1; private System.Windows.Forms.Label label1; private System.Windows.Forms.Label employeeLabel; public System.Windows.Forms.TextBox employeesTextBox; public System.Windows.Forms.TextBox transactionsTextBox; public System.Windows.Forms.Button runButton; private System.ComponentModel.Container components = null; private System.Windows.Forms.MenuItem actionMenuItem; public System.Windows.Forms.MenuItem addEmployeeMenuItem; private PayrollPresenter presenter; public PayrollWindow() { InitializeComponent(); } protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows Form Designer generated code //snip #endregion private void addEmployeeMenuItem_Click( object sender, System.EventArgs e) { presenter.AddEmployeeActionInvoked(); } private void runButton_Click( object sender, System.EventArgs e) { presenter.RunTransactions(); } public string TransactionsText { set { transactionsTextBox.Text = value; } } public string EmployeesText { set { employeesTextBox.Text = value; } } public PayrollPresenter Presenter { get { return presenter; } set { presenter = value; } } } }
Listing 38-17. TransactionContainerTest.cs
using System.Collections; using NUnit.Framework; using Payroll; namespace PayrollUI { [TestFixture] public class TransactionContainerTest { private TransactionContainer container; private bool addActionCalled; private Transaction transaction; [SetUp] public void SetUp() { TransactionContainer.AddAction action = new TransactionContainer.AddAction(SillyAddAction); container = new TransactionContainer(action); transaction = new MockTransaction(); } [Test] public void Construction() { Assert.AreEqual(0, container.Transactions.Count); } [Test] public void AddingTransaction() { container.Add(transaction); IList transactions = container.Transactions; Assert.AreEqual(1, transactions.Count); Assert.AreSame(transaction, transactions[0]); } [Test] public void AddingTransactionTriggersDelegate() { container.Add(transaction); Assert.IsTrue(addActionCalled); } private void SillyAddAction() { addActionCalled = true; } } }
Listing 38-18. TransactionContainer.cs
using Payroll; namespace PayrollUI { public class TransactionContainer { public delegate void AddAction(); private IList transactions = new ArrayList(); private AddAction addAction; public TransactionContainer(AddAction action) { addAction = action; } public IList Transactions { get { return transactions; } } public void Add(Transaction transaction) { transactions.Add(transaction); if(addAction != null) addAction(); } public void Clear() { transactions.Clear(); } } }