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

    敏捷软件开发_实例1 这本书的实例非常好,给了我非常多的启发.主要讲了两个实例,咖啡机和薪水支付实例,咖啡机实例比较简单并没有用什么设计模式,薪水支付实例用了很多设计模式,包括后面的打包等. 咖啡机实 ...

  2. 敏捷软件开发_设计原则<三>

    敏捷软件开发_设计原则 单一职责原则(single responsibilities principle,SRP) 原理:一个类应该只有一个变化 分离职责:如果不耦合的职责那么很简单,如果两个职责耦合 ...

  3. 敏捷软件开发_UML<一>

    敏捷软件开发_UML  所看书籍是:敏捷软件开发_原则.模式与实践_C#版(美)马丁著,这本书写的非常棒,感谢作者.该归纳总结的过程按照我读的顺序写. UML  在建造桥梁,零件,自动化设备之前需要建 ...

  4. 敏捷软件开发vs传统软件开发

    摘要 本文介绍了传统软件开发(着重介绍了传统软件开发中常用的瀑布模型)和敏捷软件开发,以及敏捷开发和传统开发的对比. 一.传统软件开发 比较常用的几种传统软件开发方法:瀑布式开发.迭代式开发.螺旋开发 ...

  5. 敏捷软件开发:原则、模式与实践——第12章 ISP:接口隔离原则

    第12章 ISP:接口隔离原则 不应该强迫客户程序依赖并未使用的方法. 这个原则用来处理“胖”接口所存在的缺点.如果类的接口不是内敛的,就表示该类具有“胖”接口.换句话说,类的“胖”接口可以分解成多组 ...

  6. 敏捷软件开发:原则、模式与实践——第10章 LSP:Liskov替换原则

    第10章 LSP:Liskov替换原则    Liskov替换原则:子类型(subtype)必须能够替换掉它们的基类型(base type). 10.1 违反LSP的情形 10.1.1 简单例子 对L ...

  7. 敏捷软件开发 Agile software Development(转)

    原文链接: http://www.cnblogs.com/kkun/archive/2011/07/06/2099253.html 敏捷软件开发 Agile software Development ...

  8. 《敏捷软件开发-原则、方法与实践》-Robert C. Martin读书笔记(转)

    Review of Agile Software Development: Principles, Patterns, and Practices 本书主要包含4部分内容,这些内容对于今天的软件工程师 ...

  9. 敏捷软件开发:原则、模式与实践——第13章 写给C#程序员的UML概述

    第13章 写给C#程序员的UML概述 UML包含3类主要的图示.静态图(static diagram)描述了类.对象.数据结构以及它们之间的关系,藉此表现出了软件元素间那些不变的逻辑结构.动态图(dy ...

随机推荐

  1. 双系统卸载ubuntu

    转载自https://www.jianshu.com/p/30795695be95 如果开机是这样子的,就是ubuntu引导windows(专业名词gpt) ubuntu引导windows 那么先进w ...

  2. MVC过滤器:过滤器执行顺序

    如果某个Action过滤器运用了多种过滤器,那么过滤器的执行顺序是如何呢? 规则一:不同类型的过滤器有一个先后顺序 即执行顺序是:授权过滤器->动作过滤器->结果过滤器->异常过滤器 ...

  3. Java操作数据库——在JDBC里使用事务

    Java操作数据库——在JDBC里使用事务 摘要:本文主要学习了如何在JDBC里使用事务. 使用Connection的事务控制方法 当JDBC程序向数据库获得一个Connection对象时,默认情况下 ...

  4. SpringCloud(九):springcloud——链路追踪springcloud-sleuth

    Spring-Cloud-Sleuth是Spring Cloud的组成部分之一,为SpringCloud应用实现了一种分布式追踪解决方案,其兼容了Zipkin, HTrace和log-based追踪, ...

  5. JS基础语法---内置对象

    js学习中三种对象: 内置对象----js系统自带的对象 自定义对象---自己定义的构造函数创建的对象 浏览器对象---BOM的时候讲 内置对象: Math Date String Array Obj ...

  6. [转]UiPath实践经验总结(二)

    本文转自:https://www.cnblogs.com/ybyebo/p/10086473.html 1.       UI操作容易受到各种意外的干扰,因此应该缩短UI操作阶段的总体时间.而为了缩短 ...

  7. Linux下使用 github+hexo 搭建个人博客06-next主题接入数据统计

    之前说了 next 主题的优化和接入评论系统.让我们完成了自己所需的页面风格和排版,也可让访问用户在每篇博文评论,完成博主和访问用户的交互. 本章我们继续讲解其他重要功能. 既然是一个网站,那么我们就 ...

  8. sakura设置桌面壁纸

    下下载steam上的Wallpaper Engine 先将sakura.html下载为html文件. 再从文件打开 就保存了 再加载保存,就一直是了

  9. git集成idea

    1.Git Gui的使用 2.Ssh key 介绍及使用 3.Idea配置使用并使用git Git Gui的使用 Ssh key 介绍及使用 Ssh key介绍 我理解的就是每台电脑上会产生出一个ss ...

  10. SQL Server 移位运算符

    参考链接:http://dataeducation.com/bitmask-handling-part-4-left-shift-and-right-shift/ SQL Server 官网的连接:h ...