当前位置: 首页 > 科技观察

面向对象设计讨论:有状态还是无状态?这是一道难题_0

时间:2023-03-20 20:25:22 科技观察

相信大家都知道什么是面向对象编程。但有时我们需要花一些时间来决定将哪些属性分配给特定类。显然,如果类属性赋值错误,那么我们后续很可能会出现严重的问题。在这里,我们将讨论哪些类应该是有状态的,哪些应该是无状态的。对象的状态意味着什么在讨论有状态类和无状态类之前,我们应该首先对对象的状态有一个扎实的理解。状态,正如字典所说,是“某人或某物在特定时间点所处的特定状态”。当我们查看编程并考虑对象在特定时间点的状态时,相关类别缩小到对象在特定时间的属性或成员变量值。那么谁来决定对象的属性呢?答案是类。谁决定类中的属性和成员?答案是编写该类的程序员。谁是程序员?这是给所有正在阅读这篇文章的人。那么我们真的擅长决定每个类需要哪些属性吗?答案可能是否定的。至少我见过的很多印度程序员只是为了薪水加入编程行业,他们显然缺乏做出正确属性选择的能力。首先,这种知识是不能直接从学校学到的。具体来说,我们需要投入大量时间来积累经验,并用它来找出正确的选择——这与其说是一种技术,不如说是一门艺术。工程通常有严格的规则,但艺术没有。即使经过15年的编程,我仍然很难思考一个类需要哪些属性,甚至如何为类选择一个名称。那么是否可以通过规则来限制属性的具体要求呢?换句话说,对象状态中应该包含哪些属性?或者对象应该总是喜欢无状态?下面我们一起来看看吧。实体类/业务对象编程领域充斥着诸如实体类甚至业务对象之类的名称,旨在表示类的某些确定状态。如果我们以Employee类为例,它的作用就是包含一个员工的状态。那么具体的状态内容是什么呢?EmpID,Company,Designation,JoinedDate,etc...正如教科书所说,这个类应该是有状态的,这是毫无疑问的。但是我们应该如何进行薪酬计算呢?我们应该将CalculateSalary()方法添加到Employee类吗?是否应使用SalaryCalculator类,该类是否应包含Calculate()方法?如果有一个SalaryCalculator类:它是否应该包含BasicPay、DAHRA等属性?还是应该通过构造函数将Employee对象作为私有成员变量注入到SalaryCalculator中?或者SalaryCalculator是否应该显示Employee公共属性(Java中的Get&SetEmployee方法)?Helper/Manipulation/Modification类这些类负责执行特定任务。薪水计算器就是其中之一。这些类有多种命名方式来反映它们的行为,通过前缀或后缀来表达,例如:SomethingCalculator类,例如:SalaryCalculatorSomethingHelper类,例如:DBHelperSomethingController类,例如:DBControllerSomethingManager类SomethingExecutor类SomethingProvider类SomethingWorkerclassSomethingBuilderClassSomethingAdapterClassSomethingGeneratorClass人们可以通过不同的前缀或者序列来表达类的状态,这里不做过多讨论。我们可以为这些类添加状态吗?我建议大家以无状态的方式处理这些类。我们来看看具体原因。根据维基百科给出的面向对象程序设计中封装的定义,混合类有“……将数据和函数封装到一个组件中”的概念。这是否意味着所有操作对象的方法都应该封装到类中的实体中?我不这么认为。实体类应该使用有状态的访问方法,例如GetName()、SetName()、GetJoiningDate、GetSalary()等。但是,应排除CalculateSalary()。为什么?根据单一职责原则:“一个类应该只因单一原因而改变。”如果我们在Employee类中添加CalculateSalary()方法,那么这个类的变化有两个原因:Employee类状态变化:当一个新的属性被添加到Employee时,计算逻辑发生了变化,我们来梳理一下再说一遍,假设我们有2个类,Employee类和SalaryCalculator类,那么它们之间是如何相互连接的呢?实现的方法有很多种,一种是在GetSalary方法中创建一个SalaryCalculator类对象,然后调用Calculate()来设置Employee类的salary变量,在这种情况下,该类也会表现出实体类和辅助类的特征,我们称之为混合类,我个人不建议大家使用这种混合类。基本原理:“一旦你意识到你的类可能已经变成了一个mixin,考虑重构它。如果您发现您的班级不属于以上任何类别,请立即停止后续的编程工作。《Stateinauxiliary/operationalclassesstatefulauxiliaryclasses会带来什么问题?在给出答案之前,让我们先通过下面的例子了解一下SalaryCalculator类可以包含的state值的不同组合:场景1——基本值classSalaryCalculator{publicdoubleBasic{get;set;}publicdoubleDA{get;set;}publicstringDesignation{get;set;}publicdoubleCalculate(){//Calculateandreturn}}缺点这时候Basicsalary可能是“Accountant”,Designation可能是“Director””。在这种情况下,我们无法保证SalaryCalculator以任何强制方式独立运行。同样,如果在线程环境中执行,也会导致运行失败。场景2——对象是stateclassSalaryCalculator{publicEmployeeEmployee{get;set;}publicdoubleCalculate(){//Calculateandreturn}}缺点如果两个线程共享SalaryCalculator对象,每个线程对应不同的employee,整个执行顺序可能会导致下面的逻辑是错误的:线程1设置了employee1对象线程2设置了employee2对象线程1调用了calculate方法,获取了employee2的Salary可以看到可以通过构造函数注入Employee关联,让这个属性只读。接下来我们需要为每个Employee对象创建SalaryCalculator对象。因此,您永远不应该以这种方式设计辅助类。场景3-StatelessclassSalaryCalculator{publicdoubleCalculate(Employeeinput){//Calculateandreturn}}这是一个近乎完美的情况。但是需要考虑的是如何保证所有的方法不使用任何成员变量,那么如何保证它们属于无状态类。正如SOLID的第二个原则所说:“为扩展而开放,为修改而封闭”。这意味着什么?具体来说,我们在写一个类的时候,一定要保证它是完整完成的,即不对其进行后续的修改。但同时,它还应该具有通过子类化和覆盖进行扩展的能力。那么,我们的类最终应该如下所示:interfaceISalaryCalculator{doubleCalculate(Employeeinput);}classSimpleSalaryCalculator:ISalaryCalculator{publicvirtualdoubleCalculate(Employeeinput){returninput.Basic+input.HRA;}}classTaxAwareSalaryCalculator:SimpleSalaryCalculator{publicoverridedoubleCalculate(Employeeinput){returnbase.Calculate(input)-GetTax(input);}privatedoubleGetTax(Employeeinput){//ReturntaxthrownewNotImplementedException();}}之前反复强调过,编程要面向接口。在上面的代码片段中,为了篇幅,我省略了接口的实现方法。此外,计算逻辑应始终在受保护的函数内,以便继承类在必要时可以调用它。下面是Calculator类的正确消费方法:其中,Factory类负责封装决定使用哪个子类的逻辑。如上所述,它可以选择有状态或动态反射机制。更改此类的唯一原因是创建对象,因此我们没有违反单一职责原则。在使用混合类的情况下,您可以从Employee.Salary属性或Employee.GetSalary()调用计算逻辑,如下所示:;set;}publicdoubleHRA{get;set;}publicdoubleSalary{//NOTRECOMMENDEDget{returnSalaryCalculatorFactory.GetCalculator().Calculate(this);}}}总结“思考时不编程,编程时不思考。”这个原则给我们带来了足够的考虑空间来正确把握类的有状态和无状态的决策——有状态的时候显示哪个状态。实体类应该有状态。助手/操作类应该是无状态的。确保助手类是无状态的。如果你有一个mixin类,请确保它不违反单一职责原则。在编程之前花一些时间在类设计上。将班级设计结果提交给其他同事审查,并考虑他们的反馈。仔细选择类名。这些名称将帮助我们确定其状态。职位的命名没有固定的限制,这里有一些个人建议:实体类在名称中应体现对象的类型,eg:EmployeeAuxiliary/职位类名称应体现其作用。例如:SalaryCalculator、PaySlipGenerator等。千万不要在类名中使用动词,例如:CalculateSalary{}class原标题:Object-OrientedDesignDecisions:StatefulorStatelessClasses.com]