有状态类仍是无状态类?

转自:http://developer.51cto.com/art/201603/507198.htm程序员

相信你们都清楚何谓面向对象编程。不过有时候咱们还须要花点时间决定为特定类赋予怎样的属性。很明显,若是类属性分配有误,那么咱们极可能遇到严重的后续问题。在这里咱们将共同探讨哪些类应该具有状态,而哪些类应为无状态。编程

对象的状态意味着什么ide

在咱们讨论有状态类与无状态类以前,首先应该对对象的状态拥有深刻理解。正如字典中所言,状态是指“某人或某物在特定时间点下所处之特定情况。”函数

当咱们着眼于编程并考量对象在特定时间点下的状态时,相关范畴就缩小到了给定时间中对象的属性或者成员变量值。那么对象的属性由谁决定?答案是类。谁又来决定类中的属性与成员?答案是编写该类的程序员。谁又是程序员?就是各位正在阅读本篇文章的朋友们。那么咱们是否真的精于决断每一个类各自须要怎样的属性?ui

答案恐怕是否认的。至少我见过的很多印度程序员就仅仅为了薪酬而加入编程行业,他们明显缺乏作出正确属性选择的能力。首先,这类知识没办法从学校里直接学到。具体来说,咱们须要投入大量时间来积累经验,并借此摸索出正确选择——这更像是一种艺术而非技术。工程技术每每拥有严格的规则,但艺术却没有。即便是经历了十五年的编程从业时光,我在考虑某个类须要怎样的属性甚至如何为该类选择名称时,仍然须要费一番心思。this

那么咱们可否经过规则限定属性的具体需求?换言之,对象状态当中应当包含哪些属性?或者说,对象是否应当永远优先选择无状态?下面一块儿来看。spa

实体类/业务对象线程

编程领域充斥着大量诸如实体类乃至业务对象等的名称,旨在体现类的某种明确状态。若是咱们选择Employee类做为示例,那么其做用就是包含某位员工的状态。那么具体状态内容是什么?EmpID、Company、Designation、JoinedDate等等……正如教材上所言,这种类应当为有状态,毫无疑问。设计

但咱们应该如何进行薪酬计算?code

咱们是否该在Employee类中添加CalculateSalary() 方法?

是否应该使用SalaryCalculator 类,该类又是否应当包含Calculate()方法?

若是存在SalaryCalculator类:

  • 其是否应该包含诸如BasicPay、DA HRA等属性?
  • 或者Employee对象是否应看成为私有成员变量经过构造方法注入至SalaryCalculator?
  • 或者SalaryCalculator是否应当显示Employee公共属性(Java中的 Get&Set Employee 方法)?

辅助/操做/修改类

这些类负责执行特定任务。SalaryCalculator就属于其中之一。这些类拥有多种命名方式,用于体现其行为并经过前缀或者后缀进行表达,例如:

  • SomethingCalculator 类,例如: SalaryCalculator
  • SomethingHelper 类,例如: DBHelper
  • SomethingController类,例如: DBController
  • SomethingManager类
  • SomethingExecutor类
  • SomethingProvider类
  • SomethingWorker类
  • SomethingBuilder类
  • SomethingAdapter类
  • SomethingGenerator类

人们能够经过不一样前缀或后续表达类状态,在这里咱们就不过多讨论了。

咱们可否向这些类中添加一项状态? 我建议你们以无状态方式处理这些类。下面来看具体理由。

混合类

根据维基百科给出的面向对象编程内的封装定义,其概念为“……将数据与函数打包成单一组件。”这是否意味着所有用于操做该对象的方法都应该被打包进实体类当中?我认为不是。实体类应当使用有状态访问方法,例如GetName()、SetName()、GetJoiningDate以及GetSalary() 等等。不过 CalculateSalary()应被排除在外。为何?

根据单一责任原则:“一个类应当只出于单一理由进行变动。”若是咱们将 CalculateSalary()方法添加到Employee类当中,那么该类则因为如下两种理由而发生变动:

Employee类状态变动:当新属性被添加到Employee当中时。

计算逻辑中出现变动。

下面让咱们再明确地整理一遍。假设咱们拥有2个类。Employee类与SalaryCalculator类。那么两者该如何彼此对接?实现方式多种多样。其一为在GetSalary方法中建立一个SalaryCalculator类对象,并调用Calculate()以设置Employee类的薪酬变量。在这种状况下,该类将一样表现为实体类与辅助类的特性,咱们将其称为混合类。我我的不建议你们使用这种混合类。

基本原则:“一旦你们发现本身的类可能已经转化为混合类,请考虑对其进行重构。若是你们发现本身的类不属于以上任何一种类别,请立刻中止后续编程工做。”

辅助/操做类中的状态

有状态的辅助类会带来哪些问题?在给出答案以前,让咱们首先经过如下示例了解SalaryCalculator类可以包含的不一样状态值组合:

场景一——基本值

class SalaryCalculator 
 
 { 
 
     public double Basic { get; set; } 
 
     public double DA { get; set; } 
 
     public string Designation { get; set; } 
 
     public double Calculate() 
 
     { 
 
         //Calculate and return 
 
     } 
 
 } 

缺点

这时Basic薪酬有可能为“Accountant”则Designation可能为“Director”,两者彻底不能匹配。在这种状况下,咱们没法经过任何强制性方式确保SalaryCalculator独立运做。

一样的,若是其执行于线程环境下,亦会致使运行失败。

场景二——对象即状态

class SalaryCalculator 
 
{ 
 
    public Employee Employee { get; set; } 
 
    public double Calculate() 
 
    { 
 
        //Calculate and return 
 
    } 
 
} 

缺点

若是两个线程共享SalaryCalculator对象,而每一个线程对应不一样的员工,那么整个执行顺序有可能致使如下逻辑错误:

  • 线程1设置employee1对象
  • 线程2设置employee2对象
  • 线程1调用Calculate 方法并为employee2获取Salary

能够看到其中Employee关联性可经过构造方法进行注入,并使得该属性为只读。接下来咱们须要为每一个Employee对象建立SalaryCalculator 对象。所以,***不要经过这种方式设计辅助类。

场景三——无状态

class SalaryCalculator 
 
{ 
 
    public double Calculate(Employee input) 
 
    { 
 
        //Calculate and return 
 
    } 
 
} 

这是一种近乎***的状况。不过须要考虑的是,如何所有方法都不使用任何成员变量,那么咱们该如何保证其属于无状态类。

正如SOLID第二原则所言:“开放扩展,封闭修改。”什么意思?具体来说,当咱们编写一个类时,必须保证其完全完成,即不要再对其进行后续修改。但与此同时,其也应具有经过子类与覆盖实现扩展的能力。那么,咱们的类最终应该以下所示:

interface ISalaryCalculator 
 
{ 
 
    double Calculate(Employee input); 
 
} 
 
class SimpleSalaryCalculator:ISalaryCalculator 
 
{ 
 
    public virtual double Calculate(Employee input) 
 
    { 
 
        return input.Basic + input.HRA; 
 
    } 
 
} 
 
class TaxAwareSalaryCalculator : SimpleSalaryCalculator 
 
{ 
 
    public override double Calculate(Employee input) 
 
    { 
 
        return base.Calculate(input)-GetTax(input); 
 
    } 
 
    private double GetTax(Employee input) 
 
    { 
 
        //Return tax 
 
        throw new NotImplementedException(); 
 
    } 
 
} 

正如我以前所反复强调,编程应该面向接口进行。在以上代码片断当中,我出于篇幅的考虑而略去了接口实现方法。另外,计算逻辑应当始终处于受保护函数以内,从而保证继承类可以在必要时对其进行调用。

如下为Calculator类的正确消费方式:

class SalaryCalculatorFactory 
 
{ 
 
    internal static ISalaryCalculator GetCalculator() 
 
    { 
 
        // Dynamic logic to create the ISalaryCalculator object 
 
        return new SimpleSalaryCalculator(); 
 
    } 
 
} 
 
class PaySlipGenerator 
 
{ 
 
    void Generate() 
 
    { 
 
        Employee emp = new Employee() { }; 
 
        double salary =SalaryCalculatorFactory.GetCalculator().Calculate(emp); 
 
    } 
 
} 

其中Factory类负责封装决定使用哪一个子类的逻辑。其既可如上所述选择有状态,亦可选择动态反映机制。对该类进行变动的唯一理由就是建立对象,所以咱们并无违背“单一责任原则”。

在使用混合类的状况下,你们可能从Employee.Salary 属性或者Employee.GetSalary() 处调用计算逻辑,以下所示:

class Employee 
 
{ 
 
    public string Name { get; set; } 
 
    public int EmpId { get; set; } 
 
    public double Basic { get; set; } 
 
    public double HRA { get; set; } 
 
    public double Salary 
 
    { 
 
        //NOT RECOMMENDED  
 
        get{return SalaryCalculatorFactory.GetCalculator().Calculate(this);} 
 
    } 
 
} 

 

总结

“思考时不编程,编程时不思考。”这项原则让为咱们带来充足的考量空间,从而正确把握类的有状态与无状态决定——以及在有状态时让其显示哪一种状态。

实体类应该有状态。

辅助/操做类应当无状态。

确保辅助类无状态。

若是存在混合类,确保其不会违背单一责任原则。

在编程以前花点时间进行类设计。把类设计成果交给其余同事审查,并考量其反馈意见。

认真选择类名称。这些名称将帮助咱们决定其状态。命名工做并无固定限制,如下是我我的的一些建议:

  • 实体类应当在名称中体现对象类型,例如: Employee
  • 辅助/工做类名称应当反映出其做用。例如: SalaryCalculator、PaySlipGenerator等
  • 永远不要在类名称中使用动词,例如: CalculateSalary{}类
相关文章
相关标签/搜索