An in depth understanding of the JavaScript series (43) : state patterns for design patterns

  • 2020-05-10 17:38:00
  • OfStack

introduce

The state mode (State) allows an object to change its behavior when its internal state changes, and the object appears to modify its class.

The body of the

Like, for example, we in the download things at ordinary times, usually there will be several status, such as readiness (ReadyState), download status (DownloadingState), hold (DownloadPausedState), state (DownloadedState) the download is complete, failure state (DownloadFailedState), that is to say, only can do the current status in each state can do, and do other state can do.

Because the State pattern describes how the download (Download) behaves differently in each state. The key idea of this 1 pattern is to introduce an abstract class called State (or a function in JS) to represent the download state, and the State function (as a prototype) to declare some public interfaces for each subclass of state (the inheritance function). Each of its inheritance functions implements behavior related to a particular state, such as DownloadingState and DownloadedState, which implement the behavior of being downloaded and being downloaded, respectively. These behaviors can be maintained by Download.

Let's implement 1 by first defining the State function as a prototype for the other base functions:


var State = function () { }; State.prototype.download = function () {
    throw new Error(" The method must be overloaded !");
}; State.prototype.pause = function () {
    throw new Error(" The method must be overloaded !");
}; State.prototype.fail = function () {
    throw new Error(" The method must be overloaded !");
}; State.prototype.finish = function () {
    throw new Error(" The method must be overloaded !");
};

We defined four method interfaces for the State prototype, corresponding to download (download), pause (pause), fail (fail), and end (finish) so that subfunctions could be rewritten.

Before writing a subfunction, let's write an ReadyState function so that we can pass the state to the first download state:


var ReadyState = function (oDownload) {
    State.apply(this);
    this.oDownload = oDownload;
}; ReadyState.prototype = new State(); ReadyState.prototype.download = function () {
    this.oDownload.setState(this.oDownload.getDownloadingState());
    // Ready After that, you can start downloading, so it's set Download The state fetch method in the function
 console.log("Start Download!");
}; ReadyState.prototype.pause = function () {
    throw new Error(" You can't pause the download before it starts !");
}; ReadyState.prototype.fail = function () {
    throw new Error(" The file hasn't been downloaded yet. How can you say it failed !");
}; ReadyState.prototype.finish = function () {
    throw new Error(" The file hasn't been downloaded yet, and it certainly can't be finished !");
};

The function accepts an instance of the Download maintenance function as a parameter, the Download function is used to control state changes and retrievals (similar to the central controller for external calls), and ReadyState overrides the prototype's download methods to start the download. Let's move on to the main functions of the Download function:


var Download = function () {
    this.oState = new ReadyState(this);
}; Download.prototype.setState = function (oState) {
    this.oState = oState;
}; // Externally exposed 4 Public methods for external invocation Download.prototype.download = function () {
    this.oState.download();
}; Download.prototype.pause = function () {
    this.oState.pause();
}; Download.prototype.fail = function () {
    this.oState.fail();
}; Download.prototype.finish = function () {
    this.oState.finish();
}; // Get the various states and pass in the current state this object
Download.prototype.getReadyState = function () {
    return new ReadyState(this);
}; Download.prototype.getDownloadingState = function () {
    return new DownloadingState(this);
}; Download.prototype.getDownloadPausedState = function () {
    return new DownloadPausedState(this);
}; Download.prototype.getDownloadedState = function () {
    return new DownloadedState(this);
}; Download.prototype.getDownloadedFailedState = function () {
    return new DownloadFailedState(this);
};

Download function prototype provides eight method, four is for download status operation behavior, the other four are used to get the current state of four different, these four methods are receiving this as parameters, namely pass Download instance itself as a parameter to the state of the process the request object (ReadyState behind and to the inheritance of function), which makes the state object can access oDownlaod than necessary.

Next, go ahead and define four state-dependent functions:


var DownloadingState = function (oDownload) {
    State.apply(this);
    this.oDownload = oDownload;
}; DownloadingState.prototype = new State(); DownloadingState.prototype.download = function () {
    throw new Error(" The file is already being downloaded !");
}; DownloadingState.prototype.pause = function () { this.oDownload.setState(this.oDownload.getDownloadPausedState());
    console.log(" Pause to download !");
}; DownloadingState.prototype.fail = function () { this.oDownload.setState(this.oDownload.getDownloadedFailedState());
    console.log(" Download failed !");
}; DownloadingState.prototype.finish = function () {
    this.oDownload.setState(this.oDownload.getDownloadedState());
    console.log(" The download !");
};

The main notice of DownloadingState is that files that are already being downloaded cannot be downloaded again, and other states can continue.


var DownloadPausedState = function (oDownload) {
    State.apply(this);
    this.oDownload = oDownload;
}; DownloadPausedState.prototype = new State(); DownloadPausedState.prototype.download = function () {
    this.oDownload.setState(this.oDownload.getDownloadingState());
    console.log(" Continue to download !");
}; DownloadPausedState.prototype.pause = function () {
    throw new Error(" It's already been suspended, so why stop it !");
}; DownloadPausedState.prototype.fail = function () { this.oDownload.setState(this.oDownload.getDownloadedFailedState());
    console.log(" Download failed !");
}; DownloadPausedState.prototype.finish = function () {
    this.oDownload.setState(this.oDownload.getDownloadedState());
    console.log(" The download !");
};

Note in the DownloadPausedState function that downloads that have been suspended cannot be suspended again.

var DownloadedState = function (oDownload) {
    State.apply(this);
    this.oDownload = oDownload;
}; DownloadedState.prototype = new State(); DownloadedState.prototype.download = function () {
    this.oDownload.setState(this.oDownload.getDownloadingState());
    console.log(" To download !");
}; DownloadedState.prototype.pause = function () {
    throw new Error(" Okay, that's it. Pause what? ");
}; DownloadedState.prototype.fail = function () {
    throw new Error(" Download all successful, how can fail? ");
}; DownloadedState.prototype.finish = function () {
    throw new Error(" Download successful, can't be successful again !");
};

DownloadedState function, similarly, after successful download, you can't set finish anymore, you can only set the download state again.


var DownloadFailedState = function (oDownload) {
    State.apply(this);
    this.oDownload = oDownload;
}; DownloadFailedState.prototype = new State(); DownloadFailedState.prototype.download = function () {
    this.oDownload.setState(this.oDownload.getDownloadingState());
    console.log(" Try downloading again !");
}; DownloadFailedState.prototype.pause = function () {
    throw new Error(" Failed downloads cannot be paused !");
}; DownloadFailedState.prototype.fail = function () {
    throw new Error(" All failed, why also failed !");
}; DownloadFailedState.prototype.finish = function () {
    throw new Error(" A failed download will certainly not succeed !");
};

Similarly, the failure state of DownloadFailedState function cannot fail again, but it can be tried again after finished.

Call the test code, it is very simple, let's demonstrate in HTML, the first is to ask for jquery, and then there are three buttons: start download, pause, redownload. (note that firebug is used in Firefox to view the results, as the console.log method is used).


<html>
<head>
    <link type="text/css" rel="stylesheet" href="http://www.cnblogs.com/css/style.css" />
    <title>State Pattern</title>
    <script type="text/javascript" src="/jquery.js"></script>
    <script type="text/javascript" src="Download.js"></script>
    <script type="text/javascript" src="states/State.js"></script>
    <script type="text/javascript" src="states/DownloadFailedState.js"></script>
    <script type="text/javascript" src="states/DownloadPausedState.js"></script>
    <script type="text/javascript" src="states/DownloadedState.js"></script>
    <script type="text/javascript" src="states/DownloadingState.js"></script>
    <script type="text/javascript" src="states/ReadyState.js"></script>
</head>
<body>
    <input type="button" value=" Start the download " id="download_button" />
    <input type="button" value=" suspended " id="pause_button" />
    <input type="button" value=" To download " id="resume_button" />
    <script type="text/javascript">
        var oDownload = new Download();
        $("#download_button").click(function () {
            oDownload.download();
        });         $("#pause_button").click(function () {
            oDownload.pause();
        });         $("#resume_button").click(function () {
            oDownload.download();
        });
    </script>
</body>
</html>

conclusion

The usage scenarios of the state mode are also particularly clear, as follows:

1.1 the behavior of an object depends on its state, and it must change its behavior at runtime based on its state.
2.1 operations contain a large number of branch statements that depend on the state of the object. The state is usually a representation of one or more enumerated constants.


Related articles: