当课堂上的提问顺序很重要时,这是最佳实践
本文关键字:最佳 课堂 顺序 | 更新日期: 2023-09-27 18:12:06
我有一个类,它有两个重要的函数:
public class Foo {
//plenty of properties here
void DoSomeThing(){/*code to calculate results*/}
void SaveSomething(){/* code to save the results in DB*/}
}
SaveSomething()
使用DoSomeThing()
的计算结果。
问题是我们不能在DoSomeThing()
之前调用SaveSomething()
,否则如果发生这种情况,结果不是真实的结果。我的意思是调用的顺序很重要,这是维护代码的一个问题。(当新成员加入团队时)
有办法处理这个吗?
我认为有以下三种方法
- 在
DoSomeThing()
之前调用SaveSomething()
抛出异常 具有
bool
的DoSomeThing()
和SaveSomething()
代码更改为:bool resultsAreCalculated = false; void SaveSomething(){ if (!resultsAreCalculated) { DoSomeThing(); // the resultsAreCalculated = true; is set in DoSomeThing(); // can we throw some exception? } /* code to save the results in DB*/ }
实现它流畅像:
Foo x = new Foo(); x.DoSomeThing().SaveSomething();
在这种情况下,重要的是要保证这不会发生:
x.SaveSomething().DoSomeThing();
现在,我使用第二个方法。有更好的办法吗?
理想情况下,需要遵循一定的执行顺序的方法表示或暗示需要实现某种工作流程。
有一些设计模式支持执行类似工作流的线性执行顺序,例如模板方法模式或策略。
要采用模板方法方法,您的Foo类将有一个抽象基,定义执行Do()
和Save()
的顺序,类似于:
public abstract class FooBase
{
protected abstract void DoSomeThing();
protected abstract void SaveSomething();
public void DoAndSave()
{
//Enforce Execution order
DoSomeThing();
SaveSomething();
}
}
public class Foo : FooBase
{
protected override void DoSomeThing()
{
/*code to calculate results*/
}
protected override void SaveSomething()
{
/* code to save the results in DB*/
}
}
这样你的类消费者将只能访问DoAndSave()
,他们不会违反你想要的执行顺序。
还有另一种模式处理工作流/状态转换类型的情况。你可以参考命令链和状态模式。
回应你的评论:这遵循了相同的模板思想,您在模板中添加了另一个步骤,假设您想在保存之前验证结果,您可以将模板扩展为:
public abstract class FooBase
{
protected abstract void DoSomeThing();
protected abstract void SaveSomething();
protected abstract bool AreValidResults();
public void DoAndSave()
{
//Enforce Execution order
DoSomeThing();
if (AreValidResults())
SaveSomething();
}
}
当然,对于更复杂的工作流,我在原始答案的末尾提到了状态模式,您可以对从一个状态到另一个状态的转换条件进行更细粒度的控制。
帮助避免用户错误的一个选项是通过传递一个变量来明确它。通过这样做,它为用户提出一个标志,他们需要在调用savessomething(…)之前获得结果(即DoSomething())。
results = DoSomething(); // returns the results to be saved
SaveSomething(results);
这个怎么样?
interface Result {
void Save();
SomeData GetData();
}
class Foo {
Result DoSomething() { /* ... */ }
}
用法:
myFoo.DoSomething().Save();
//or something like:
var result = myFoo.DoSomething();
if (result.GetData().Importance > threshold) result.Save();
从外部的角度来看,这很有意义。生成Result
并提供保存方法(如果需要),而实现则完全不透明。我不必担心将其传递回正确的Foo
实例。事实上,我可以将结果传递给对象,而对象甚至不知道创建它的Foo
实例(实际上,创建者应该在创建时传递所有必要的信息以保存结果)。结果可能有一个方法告诉我,它是否已经保存,如果需要的话。等等......
这基本上只是SRP的应用,尽管主要是在接口上而不是在实现上。Foo
的接口提供了产生结果的方法,Result
抽象了操作结果的方法。
根据Levinaris的答案展开(如果我有rep,则加1),您可以在DoSomthing()
方法返回的结果对象上使用Save()
方法。所以你会得到这样的内容:
var obj = new Foo();
// Get results
var results = obj.DoSomething();
// Check validity, and user acceptance
if(this.AreValidResults(results) && this.UserAcceptsResults(results))
{
// Save the results
results.Save();
}
else
{
// Ditch the results
results.Dispose();
}
显然,这种方法要求返回的results
对象要么是处理保存/处置结果的泛型类型,但也包含泛型结果;或者它需要是特定结果类型可以继承的某种形式的基类。
我喜欢Anas Karkoukli的答案,但另一种选择是状态机。
public class Foo {
private enum State {
AwaitingDo,
AwaitingValidate,
AwaitingSave,
Saved
}
private State mState = State.AwaitingDo;
private void Do() {
// Do something
mState = State.AwaitingValidate;
}
private void Validate() {
// Do something
mState = State.AwaitingSave;
}
private void Save() {
// Do something
mState = State.Saved;
}
public void MoveToNextState() {
switch (mState) {
case State.AwaitingDo:
Do();
break;
case State.AwaitingValidation:
Validate();
break;
case State.AwaitingSave:
Save();
break;
case State.Saved:
throw new Exception("Nothing more to do.");
break;
}
}
}
这有点草率,但你明白我的意思。
Anas回答的问题是,所有的函数都是作为一个步骤执行的,这意味着您无法进入对象的中间阶段。状态机迫使开发人员遵循工作流,但是在工作流的每个阶段,他们都可以在进入下一个阶段之前检查对象的属性。
Steve mcconnell的优秀著作Code Complete用了整整一章来讨论这个问题。这是第二版的第14章。
如果语句的顺序很重要,那么对数据执行这种顺序是非常好的实践。所以与其
calculateResults();
saveResults();
(将结果存储在实例变量中)写
Results r = calculateResults();
saveResults(r);
在计算结果之前保存结果是非常困难的。
Do
和Save
方法对我来说似乎不是一个有序的对。您需要对它们排序,只是因为您没有从Do
方法返回计算的状态。如果您将Do
方法编写为将结果返回给客户端代码的方法,那么您可以重写Save
,以便它将结果作为参数接收。
好处:
- 你不需要再排序方法,因为
Save
方法不关心客户端如何获得参数。它只是接收它,就这样。 - 你可以更容易地单元测试
Do
方法,因为方法变得更少耦合。 - 如果需要编写复杂的保存逻辑或实现存储库模式,可以将
Save
方法移动到另一个类。