繁体   English   中英

课堂电话的顺序很重要时的最佳做法?

[英]best practice when order of calls in class are important?

我有一个课程有两个重要的功能:

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() ,否则结果不是真实的结果。 我的意思是调用顺序很重要,这是维护代码的一个问题。(当新的一个被添加到团队中时)。

有没有办法管理这个?

我想到如下3种方法

  1. 如果在DoSomeThing()之前调用异常,则在SaveSomething()抛出异常
  2. 拥有在DoSomeThing()SaveSomething()代码中设置的bool更改为:

     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*/ } 
  3. 实现它流畅如:

     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();
    }

}

当然,对于更精细的工作流程,我在最初答案的最后提到了状态模式,您可以更精细地控制从一个状态到另一个状态的转换条件。

帮助避免用户错误的一个选项是通过传递变量来明确它。 通过这样做,它为用户提出了一个标志,他们需要在调用SaveSomething(...)之前获得结果(即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摘要意味着操纵结果。

DoSave方法对我来说似乎不是一个有序的对。 您只需要对它们进行排序,因为您没有从Do方法返回计算状态。 如果将Do方法编写为将结果返回给客户端代码的方法,则可以重写Save ,以便将结果作为参数接收。

优点:

  1. 您不再需要订购方法,因为Save方法不关心客户端如何获取参数。 它只是收到它就是它。
  2. 您可以更轻松地单元测试Do方法,因为方法变得更少耦合。
  3. 如果您需要编写复杂的保存逻辑或实现存储库模式,则可以将Save方法移动到另一个类。

扩大在Levinaris的回答(+1,如果我有REP),你可以或者有一个Save()的结果Method对象从返回DoSomthing()方法。 所以你会得到这样的东西:

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 McConnel的优秀书籍Code Complete花了整整一章来讨论这个问题。 这是第二版的第14章。

如果语句的顺序很重要,那么强制执行数据排序是一种很好的做法。 所以而不是

calculateResults();
saveResults();

(将结果存储在实例变量中)写入

Results r = calculateResults();
saveResults(r);

因此,在计算结果之前尝试保存结果要困难得多。 有一个明确的迹象表明预期的订单。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM