敏捷软件开发_实例2<四>

敏捷软件开发_实例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; }
	}
}

完毕

你可能感兴趣的:(敏捷软件开发_实例2<四>)