开发者

Enforcing API constraints using OOP

I have an encoder class. The encoder can be in three states: freshly created, encoding or finished. There are three methods named startEncoding(), appendFrame() and finishEnco开发者_如何学编程ding() that can each only be called when the encoder is in the relevant state (it does not make sense to insert frames until you start encoding or after you finish). I am wondering about how these constraints can be enforced.

Currently I track the state internally and assert the right state at the beginning of the methods:

void appendFrame() {
    assert(state == STATE_ENCODING);
    …
}

It’s a low-hassle, working solution. But it does not seem like an optimal one to me, since the resulting API is easy to misuse (like forgetting to start the encoder before appending frames) and the asserts are a bit dumb (extra code, if not anything else).

I was wondering that maybe I could split the class into three smaller classes corresponding to the three states. Then the API would be obvious, since each class would only contain the supported methods. But this solution obviously feels like sweating it too much, and I’m not sure how to handle state switching like going from a running encoder to a finished one.

Can you think of another solution that would be better than checking the state manually in the methods? Is there a pattern for such use case?


Another solution is to check transitions, either internally or externally to methods.

You have a state machine, with a set of states, and a set of transitions between. You could allow a caller to ask a priori whether a particular transition is allowed. One use of this is to enable controls in a user interface.

Regardless, it's still useful to check internally. Exceptions might be better than assertions for invalid transitions.

This approach becomes helpful if you support multiple devices or algorithms, with similar but different rules for allowable transitions.


You could enforce state transitions by creating dependencies on previous states. Make the methods that do the actual work protected and wrap the calls in a way that will enforce how they may be invoked. For example:

class foo {
public:
        static foo* start() { foo* f = new foo; f->doStart(); return f;}
        static void doit(foo* f) { f->doDoit(); }
        static void finish(foo* f) { f->doFinish(); delete f; }

protected:
        void doStart() { std::cout << "doStart()\n"; }
        void doDoit() { std::cout << "doDoit()\n"; }
        void doFinish() { std::cout << "doFinish()\n"; }
};

int main()
{
        foo* f = foo::start();
        foo::doit(f);
        foo::finish(f);
        return 0;
}

The method doit() cant be invoked until start() returns successfully. In your example you don't specify if appendFrame() must be called at least once before finish() but if that is the case you could create an additional dependency there as well.


Usually an object has a "life cycle" with 3 main methods (categories), one for the constructor or initialization of the object, one for main operation of the object, and one for the destructor or finalization of the object.

If a class / object has more methods, usually the fit in one of the 3 categories I mention.

If you want to design an O.O. A.P.I., one of the constraints, you may want to apply, is that there are those 3 methods / methods categories public to your A.P.I. users.

You are using states or transistions in you example. I don't think you need to subclass, because its a very simple class. Usually, when a state machine class is used, you may require a public function that returns which its the current status of the object.

(C++ style example, may have modify to your progr. lang.)

class MyStatusMachineClass {
protected:
  int _CurrentStatus = 0;
public:
  int currentStatus();   // <-- main operation category method

  void startEncoding();  // <-- not a constructor, but works like one
  void appendFrame();    // <-- main operation category method
  void finishEncoding(); // <-- not a destructor, but works like one
} // end class

Your methods that are constrained by the status, must call this function before perform its operation. You may also want to add what to do in case a error is generated.

Specially, if the A.P.I. user wants to call startEncoding(), and the object is not in the status it should be.

There are several ways to handle errors, like exceptions, but in case of A.P.I., I recommend that when an error is found, the program is not interrupted, but an internal field variable stores a code for the error, an a public function returns that value

(C++ style example, may have modify to your progr. lang.)

class MyStatusMachineClass {
protected:
  int _CurrentStatus = 0;
  int _LastErrorCode = 0; // <-- "0" means "no error"
public:
  int currentStatus();   // <-- main operation category method
  int LastErrorCode();

  void startEncoding();  // <-- not a constructor, but works like one
  void appendFrame();    // <-- main operation category method
  void finishEncoding(); // <-- not a destructor, but works like one
} // end class

void main()
{
  MyStatusMachineClass* myStatusMachineObject = new MyStatusMachineClass();
    myStatusMachineObject->appendFrame();
    if (myStatusMachineObject->LastErrorCode()) {
      cout << "Error: Cannot append frame in current status.";
    }
  delete myStatusMachineObject();
}

Cheers.


Seems like "State pattern" to me.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜