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

完毕

猜你喜欢

转载自www.cnblogs.com/lovexinyi/p/9765859.html