敏捷软件开发_实例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;
}
/// <summary>
/// 最新的form
/// </summary>
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<四>的更多相关文章
- 敏捷软件开发_实例1<二>
敏捷软件开发_实例1 这本书的实例非常好,给了我非常多的启发.主要讲了两个实例,咖啡机和薪水支付实例,咖啡机实例比较简单并没有用什么设计模式,薪水支付实例用了很多设计模式,包括后面的打包等. 咖啡机实 ...
- 敏捷软件开发_设计原则<三>
敏捷软件开发_设计原则 单一职责原则(single responsibilities principle,SRP) 原理:一个类应该只有一个变化 分离职责:如果不耦合的职责那么很简单,如果两个职责耦合 ...
- 敏捷软件开发_UML<一>
敏捷软件开发_UML 所看书籍是:敏捷软件开发_原则.模式与实践_C#版(美)马丁著,这本书写的非常棒,感谢作者.该归纳总结的过程按照我读的顺序写. UML 在建造桥梁,零件,自动化设备之前需要建 ...
- 敏捷软件开发vs传统软件开发
摘要 本文介绍了传统软件开发(着重介绍了传统软件开发中常用的瀑布模型)和敏捷软件开发,以及敏捷开发和传统开发的对比. 一.传统软件开发 比较常用的几种传统软件开发方法:瀑布式开发.迭代式开发.螺旋开发 ...
- 敏捷软件开发:原则、模式与实践——第12章 ISP:接口隔离原则
第12章 ISP:接口隔离原则 不应该强迫客户程序依赖并未使用的方法. 这个原则用来处理“胖”接口所存在的缺点.如果类的接口不是内敛的,就表示该类具有“胖”接口.换句话说,类的“胖”接口可以分解成多组 ...
- 敏捷软件开发:原则、模式与实践——第10章 LSP:Liskov替换原则
第10章 LSP:Liskov替换原则 Liskov替换原则:子类型(subtype)必须能够替换掉它们的基类型(base type). 10.1 违反LSP的情形 10.1.1 简单例子 对L ...
- 敏捷软件开发 Agile software Development(转)
原文链接: http://www.cnblogs.com/kkun/archive/2011/07/06/2099253.html 敏捷软件开发 Agile software Development ...
- 《敏捷软件开发-原则、方法与实践》-Robert C. Martin读书笔记(转)
Review of Agile Software Development: Principles, Patterns, and Practices 本书主要包含4部分内容,这些内容对于今天的软件工程师 ...
- 敏捷软件开发:原则、模式与实践——第13章 写给C#程序员的UML概述
第13章 写给C#程序员的UML概述 UML包含3类主要的图示.静态图(static diagram)描述了类.对象.数据结构以及它们之间的关系,藉此表现出了软件元素间那些不变的逻辑结构.动态图(dy ...
随机推荐
- MySQL插入数据时报错Cause: java.sql.SQLException: #HY000的解决方法
数据库中有字段要求不能为空,但是insert插入的时候,改字段没有值
- PlayJava Day020
1.异常Exception补充: ①错误(Error)指的是致命性错误,一般无法处理 ②异常以类的形式封装 程序可以处理的异常对应的类是java.lang.Exception及其子类 运行时异常对应的 ...
- HTML识别后台传输或者js变量中字符串里的 '\n' 并成功换行显示
HTML识别 string 里的 '\n' 并成功换行显示 设置标签的的css属性 white-space: pre-line; <div style='white-space: pre-lin ...
- 7天教你精通变大神,学CAD关键还要掌握方法,纯干货新手要看
接触CAD初期是“痛苦”的,“煎熬”的,也是充满“成就”的. 痛苦是初学者怎么都不懂,需要学习的东西很多,整个过程是有些痛苦的. 煎熬也是每个求学阶段都会遇到的状态,眼睛会了,手不会,这个状态很难受. ...
- Easy User Manager System writeup
0x01 解题 思路 一个进程用自己的ip去申请拿到code然后进入verify页面,另外一个进程去申请8.8.8.8 步骤 1. 首先注册一个账号 然后用两个不同的浏览器进入Change页面.这里我 ...
- python小技巧 小知识
python小技巧 小知识 python系统变量(修改调用shell命令路径)或用户空间说明 20150418 python调用系统命令,报找不到.怎么办? 类似执行shell的: [ -f /etc ...
- Java读写Excel文件,利用POI
直接看工具类代码吧, package com.example.demo.util; import com.example.demo.entity.ExcelDataVO; import org.apa ...
- Mysql中事务ACID实现原理
引言 照例,我们先来一个场景~ 面试官:"知道事务的四大特性么?"你:"懂,ACID嘛,原子性(Atomicity).一致性(Consistency).隔离性(Isola ...
- fastdfs使用总结
参考:https://www.cnblogs.com/chiangchou/p/fastdfs.html 说明:这篇博客是为了记录我在安装和使用FastDFS分布式文件系统时遇到的问题和解决方法, ...
- Python画一棵漂亮的樱花树(不同种樱花+玫瑰+圣诞树喔)
不少用Python(大多是turtle库)绘制的树图,感觉很漂亮,我整理了一下,挑了一些我觉得不错的代码分享给大家(这些我都测试过,确实可以生成喔~)one 樱花树 动态生成樱花效果图(这个是动态的) ...