敏捷软件开发_实例2<四>
上一章中对薪水支付案例的用例和类做了详细的阐述,在本篇会介绍薪水支付案例包的划分和数据库,UI的设计。
包的划分
一个错误包的划分
为什么这个包是错误的:
- 如果对classifications更改就要影响payrolldatabase更改,还会迫使transactions更改,tansactions重新发布和编译测试就是不负责的,transactions没有共享封闭性,每个类都有自己变化的敏感,所以发布的频率非常高,是不合理的。
调整一下:
将具体类和具体类打包,抽象类和抽象类打包,交互类单独打包。这已经是一个比较好打包设计了。
类的组件应该要符合共同重用原则,payrolldamain中的类没有形成最小的可重用单元,transaction类不必和组件中的其他类一起重用,可以把transaction迁移到transactionapplication类中
这样的划分太精细了,是否有这样的必要需要整体来看。
最终包的结构:
数据库的设计
emplogee是核心
完成这个设计需要进行重构:
-
提取出payrolldatabase接口,
public interface PayrollDatabase { void AddEmployee(Employee employee); Employee GetEmployee(int id); void DeleteEmployee(int id); void AddUnionMember(int id, Employee e); Employee GetUnionMember(int id); void RemoveUnionMember(int memberId); ArrayList GetAllEmployeeIds(); IList GetAllEmployees(); }
-
内存表实例:
public class InMemoryPayrollDatabase : PayrollDatabase { private static Hashtable employees = new Hashtable(); private static Hashtable unionMembers = new Hashtable(); public void AddEmployee(Employee employee) { employees[employee.EmpId] = employee; } // etc... public Employee GetEmployee(int id) { return employees[id] as Employee; } public void DeleteEmployee(int id) { employees.Remove(id); } public void AddUnionMember(int id, Employee e) { unionMembers[id] = e; } public Employee GetUnionMember(int id) { return unionMembers[id] as Employee; } public void RemoveUnionMember(int memberId) { unionMembers.Remove(memberId); } public ArrayList GetAllEmployeeIds() { return new ArrayList(employees.Keys); } public IList GetAllEmployees() { return new ArrayList(employees.Values); } public void Clear() { employees.Clear(); unionMembers.Clear(); } }
-
数据库
public class SqlPayrollDatabase : PayrollDatabase { private SqlConnection connection; public SqlPayrollDatabase() { connection = new SqlConnection("Initial Catalog=Payroll;Data Source=localhost;user id=sa;password=abc"); connection.Open(); } ~SqlPayrollDatabase() { connection.Close(); } public void AddEmployee(Employee employee) { //增加员工策略 SaveEmployeeOperation operation = new SaveEmployeeOperation(employee, connection); operation.Execute(); } public Employee GetEmployee(int id) { //数据库事务 LoadEmployeeOperation loadOperation = new LoadEmployeeOperation(id, connection); loadOperation.Execute(); return loadOperation.Employee; } public void DeleteEmployee(int id) { throw new NotImplementedException(); } public void AddUnionMember(int id, Employee e) { throw new NotImplementedException(); } public Employee GetUnionMember(int id) { throw new NotImplementedException(); } public void RemoveUnionMember(int memberId) { throw new NotImplementedException(); } public ArrayList GetAllEmployeeIds() { throw new NotImplementedException(); } public IList GetAllEmployees() { throw new NotImplementedException(); } }
-
如果插入雇佣记录成功,但是支付记录失败,为了解决这个问题而使用事务的方式。
public class SaveEmployeeOperation { private readonly Employee employee; private readonly SqlConnection connection; private string methodCode; private string classificationCode; private SqlCommand insertPaymentMethodCommand; private SqlCommand insertEmployeeCommand; private SqlCommand insertClassificationCommand; public SaveEmployeeOperation(Employee employee, SqlConnection connection) { this.employee = employee; this.connection = connection; } public void Execute() { PrepareToSavePaymentMethod(employee); PrepareToSaveClassification(employee); PrepareToSaveEmployee(employee); SqlTransaction transaction = connection.BeginTransaction("Save Employee"); try { ExecuteCommand(insertEmployeeCommand, transaction); ExecuteCommand(insertPaymentMethodCommand, transaction); ExecuteCommand(insertClassificationCommand, transaction); transaction.Commit(); } catch(Exception e) { transaction.Rollback(); throw e; } } private void ExecuteCommand(SqlCommand command, SqlTransaction transaction) { if(command != null) { command.Connection = connection; command.Transaction = transaction; command.ExecuteNonQuery(); } } private void PrepareToSaveEmployee(Employee employee) { string sql = "insert into Employee values (" + "@EmpId, @Name, @Address, @ScheduleType, " + "@PaymentMethodType, @PaymentClassificationType)"; insertEmployeeCommand = new SqlCommand(sql); this.insertEmployeeCommand.Parameters.Add("@EmpId", employee.EmpId); this.insertEmployeeCommand.Parameters.Add("@Name", employee.Name); this.insertEmployeeCommand.Parameters.Add("@Address", employee.Address); this.insertEmployeeCommand.Parameters.Add("@ScheduleType", ScheduleCode(employee.Schedule)); this.insertEmployeeCommand.Parameters.Add("@PaymentMethodType", methodCode); this.insertEmployeeCommand.Parameters.Add("@PaymentClassificationType", classificationCode); } private void PrepareToSavePaymentMethod(Employee employee) { PaymentMethod method = employee.Method; if(method is HoldMethod) methodCode = "hold"; else if(method is DirectDepositMethod) { methodCode = "directdeposit"; DirectDepositMethod ddMethod = method as DirectDepositMethod; insertPaymentMethodCommand = CreateInsertDirectDepositCommand(ddMethod, employee); } else if(method is MailMethod) { methodCode = "mail"; MailMethod mailMethod = method as MailMethod; insertPaymentMethodCommand = CreateInsertMailMethodCommand(mailMethod, employee); } else methodCode = "unknown"; } private SqlCommand CreateInsertDirectDepositCommand(DirectDepositMethod ddMethod, Employee employee) { string sql = "insert into DirectDepositAccount values (@Bank, @Account, @EmpId)"; SqlCommand command = new SqlCommand(sql); command.Parameters.Add("@Bank", ddMethod.Bank); command.Parameters.Add("@Account", ddMethod.AccountNumber); command.Parameters.Add("@EmpId", employee.EmpId); return command; } private SqlCommand CreateInsertMailMethodCommand(MailMethod mailMethod, Employee employee) { string sql = "insert into PaycheckAddress values (@Address, @EmpId)"; SqlCommand command = new SqlCommand(sql); command.Parameters.Add("@Address", mailMethod.Address); command.Parameters.Add("@EmpId", employee.EmpId); return command; } private void PrepareToSaveClassification(Employee employee) { PaymentClassification classification = employee.Classification; if(classification is HourlyClassification) { classificationCode = "hourly"; HourlyClassification hourlyClassification = classification as HourlyClassification; insertClassificationCommand = CreateInsertHourlyClassificationCommand(hourlyClassification, employee); } else if(classification is SalariedClassification) { classificationCode = "salary"; SalariedClassification salariedClassification = classification as SalariedClassification; insertClassificationCommand = CreateInsertSalariedClassificationCommand(salariedClassification, employee); } else if(classification is CommissionClassification) { classificationCode = "commission"; CommissionClassification commissionClassification = classification as CommissionClassification; insertClassificationCommand = CreateInsertCommissionClassificationCommand(commissionClassification, employee); } else classificationCode = "unknown"; } private SqlCommand CreateInsertHourlyClassificationCommand(HourlyClassification classification, Employee employee) { string sql = "insert into HourlyClassification values (@HourlyRate, @EmpId)"; SqlCommand command = new SqlCommand(sql); command.Parameters.Add("@HourlyRate", classification.HourlyRate); command.Parameters.Add("@EmpId", employee.EmpId); return command; } private SqlCommand CreateInsertSalariedClassificationCommand(SalariedClassification classification, Employee employee) { string sql = "insert into SalariedClassification values (@Salary, @EmpId)"; SqlCommand command = new SqlCommand(sql); command.Parameters.Add("@Salary", classification.Salary); command.Parameters.Add("@EmpId", employee.EmpId); return command; } private SqlCommand CreateInsertCommissionClassificationCommand(CommissionClassification classification, Employee employee) { string sql = "insert into CommissionedClassification values (@Salary, @Commission, @EmpId)"; SqlCommand command = new SqlCommand(sql); command.Parameters.Add("@Salary", classification.BaseRate); command.Parameters.Add("@Commission", classification.CommissionRate); command.Parameters.Add("@EmpId", employee.EmpId); return command; } private static string ScheduleCode(PaymentSchedule schedule) { if(schedule is MonthlySchedule) return "monthly"; if(schedule is WeeklySchedule) return "weekly"; if(schedule is BiWeeklySchedule) return "biweekly"; else return "unknown"; } }
界面设计
界面设计时最好使得业务行为和UI分离,这里使用model view presenter模式(MVP)
- model:实体层和数据库交互
- view:界面层
- presenter:业务处理层
MVP的作用是解耦界面、业务和实体的关系
- 在presenter中主动使用view,界面的形态都是由presenter去控制,就是在presenter中去注册view事件,当用户触发事件时,这个事件会通过view传递到presenter中,并通过presenter调用model数据方法,最后presenter 调用引用的view实例去改变界面的形态。
public class AddEmployeePresenter
{
private TransactionContainer transactionContainer;
private AddEmployeeView view;
private PayrollDatabase database;
private int empId;
private string name;
private string address;
private bool isHourly;
private double hourlyRate;
private bool isSalary;
private double salary;
private bool isCommission;
private double commissionSalary;
private double commission;
public AddEmployeePresenter(AddEmployeeView view,
TransactionContainer container,
PayrollDatabase database)
{
this.view = view;
this.transactionContainer = container;
this.database = database;
}
public int EmpId
{
get { return empId; }
set
{
empId = value;
UpdateView();
}
}
public string Name
{
get { return name; }
set
{
name = value;
UpdateView();
}
}
public string Address
{
get { return address; }
set
{
address = value;
UpdateView();
}
}
public bool IsHourly
{
get { return isHourly; }
set
{
isHourly = value;
UpdateView();
}
}
public double HourlyRate
{
get { return hourlyRate; }
set
{
hourlyRate = value;
UpdateView();
}
}
public bool IsSalary
{
get { return isSalary; }
set
{
isSalary = value;
UpdateView();
}
}
public double Salary
{
get { return salary; }
set
{
salary = value;
UpdateView();
}
}
public bool IsCommission
{
get { return isCommission; }
set
{
isCommission = value;
UpdateView();
}
}
public double CommissionSalary
{
get { return commissionSalary; }
set
{
commissionSalary = value;
UpdateView();
}
}
public double Commission
{
get { return commission; }
set
{
commission = value;
UpdateView();
}
}
private void UpdateView()
{
if(AllInformationIsCollected())
view.SubmitEnabled = true;
else
view.SubmitEnabled = false;
}
public bool AllInformationIsCollected()
{
bool result = true;
result &= empId > 0;
result &= name != null && name.Length > 0;
result &= address != null && address.Length > 0;
result &= isHourly || isSalary || isCommission;
if(isHourly)
result &= hourlyRate > 0;
else if(isSalary)
result &= salary > 0;
else if(isCommission)
{
result &= commission > 0;
result &= commissionSalary > 0;
}
return result;
}
public TransactionContainer TransactionContainer
{
get { return transactionContainer; }
}
public virtual void AddEmployee()
{
transactionContainer.Add(CreateTransaction());
}
public Transaction CreateTransaction()
{
if(isHourly)
return new AddHourlyEmployee(
empId, name, address, hourlyRate, database);
else if(isSalary)
return new AddSalariedEmployee(
empId, name, address, salary, database);
else
return new AddCommissionedEmployee(
empId, name, address, commissionSalary,
commission, database);
}
}
public interface ViewLoader
{
void LoadPayrollView();
void LoadAddEmployeeView(
TransactionContainer transactionContainer);
}
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;
}
///
/// 最新的form
///
public Form LastLoadedView
{
get { return lastLoadedView; }
}
}
public class PayrollMain
{
public static void Main(string[] args)
{
PayrollDatabase database =
new InMemoryPayrollDatabase();
WindowViewLoader viewLoader =
new WindowViewLoader(database);
viewLoader.LoadPayrollView();
Application.Run(viewLoader.LastLoadedView);
}
}
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();
}
}
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();
}
}
public class AddEmployeeWindow : Form, AddEmployeeView
{
public System.Windows.Forms.TextBox empIdTextBox;
private System.Windows.Forms.Label empIdLabel;
private System.Windows.Forms.Label nameLabel;
public System.Windows.Forms.TextBox nameTextBox;
private System.Windows.Forms.Label addressLabel;
public System.Windows.Forms.TextBox addressTextBox;
public System.Windows.Forms.RadioButton hourlyRadioButton;
public System.Windows.Forms.RadioButton salaryRadioButton;
public System.Windows.Forms.RadioButton commissionRadioButton;
private System.Windows.Forms.Label hourlyRateLabel;
public System.Windows.Forms.TextBox hourlyRateTextBox;
private System.Windows.Forms.Label salaryLabel;
public System.Windows.Forms.TextBox salaryTextBox;
private System.Windows.Forms.Label commissionSalaryLabel;
public System.Windows.Forms.TextBox commissionSalaryTextBox;
private System.Windows.Forms.Label commissionLabel;
public System.Windows.Forms.TextBox commissionTextBox;
private System.Windows.Forms.TextBox textBox2;
private System.Windows.Forms.Label label1;
private System.ComponentModel.Container components = null;
public System.Windows.Forms.Button submitButton;
private AddEmployeePresenter presenter;
public AddEmployeeWindow()
{
InitializeComponent();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
public AddEmployeePresenter Presenter
{
get { return presenter; }
set { presenter = value; }
}
private void hourlyRadioButton_CheckedChanged(
object sender, System.EventArgs e)
{
hourlyRateTextBox.Enabled = hourlyRadioButton.Checked;
presenter.IsHourly = hourlyRadioButton.Checked;
}
private void salaryRadioButton_CheckedChanged(
object sender, System.EventArgs e)
{
salaryTextBox.Enabled = salaryRadioButton.Checked;
presenter.IsSalary = salaryRadioButton.Checked;
}
private void commissionRadioButton_CheckedChanged(
object sender, System.EventArgs e)
{
commissionSalaryTextBox.Enabled =
commissionRadioButton.Checked;
commissionTextBox.Enabled =
commissionRadioButton.Checked;
presenter.IsCommission =
commissionRadioButton.Checked;
}
private void empIdTextBox_TextChanged(
object sender, System.EventArgs e)
{
presenter.EmpId = AsInt(empIdTextBox.Text);
}
private void nameTextBox_TextChanged(
object sender, System.EventArgs e)
{
presenter.Name = nameTextBox.Text;
}
private void addressTextBox_TextChanged(
object sender, System.EventArgs e)
{
presenter.Address = addressTextBox.Text;
}
private void hourlyRateTextBox_TextChanged(
object sender, System.EventArgs e)
{
presenter.HourlyRate = AsDouble(hourlyRateTextBox.Text);
}
private void salaryTextBox_TextChanged(
object sender, System.EventArgs e)
{
presenter.Salary = AsDouble(salaryTextBox.Text);
}
private void commissionSalaryTextBox_TextChanged(
object sender, System.EventArgs e)
{
presenter.CommissionSalary =
AsDouble(commissionSalaryTextBox.Text);
}
private void commissionTextBox_TextChanged(
object sender, System.EventArgs e)
{
presenter.Commission = AsDouble(commissionTextBox.Text);
}
private void addEmployeeButton_Click(
object sender, System.EventArgs e)
{
presenter.AddEmployee();
this.Close();
}
private double AsDouble(string text)
{
try
{
return Double.Parse(text);
}
catch (Exception)
{
return 0.0;
}
}
private int AsInt(string text)
{
try
{
return Int32.Parse(text);
}
catch (Exception)
{
return 0;
}
}
public bool SubmitEnabled
{
set { submitButton.Enabled = value; }
}
}
完毕