Consider I'm writing a mobile app with user-login feature, using a framework which can be simplified like that:
class UserData{
static token="";
static name="";
static balance=0;
}
class Main{
constructor(){
this.loginPage=new LoginPage();
this.welcomePage=new WelcomePage();
.
.
.
if(notLogin){
this.loginPage.show();
}
}
}
login page to modify UserData
class LoginPage{
onLoginResponse(response){
UserData.token=response.token;
UserData.name=response.name;
UserData.balance=response.balance;
}
}
some other page:
class WelcomePage{
showAboutDialog(){
new AboutDialog().show();
}
}
a general dialog may be used by other page:
class AboutDialog{
}
One day, AboutDialog needs UserData.token to submit some information to server, so I add UserData.token to AboutDialog:
class AboutDialog{
onSubmit(){
HttpUtils.get("(some url)/?token="+UserData.token");
}
}
But according to Why is Global State so Evil?, I know I should not use global state, so I modify the code to use dependency injection:
class UserData{
constructor(){
this.token="";
this.name="";
this.balance=0;
}
}
class Main{
constructor(){
this.userData=new UserData();
this.loginPage=new LoginPage(this.userData);
this.welcomePage=new WelcomePage(this.userData);
.
.
.
}
}
class LoginPage{
constructor(userData){
this.userData=userData;
}
onLoginResponse(response){
this.userData.token=response.token;
this.userData.name=response.name;
this.userData.balance=response.balance;
}
}
class AboutDialog{
constructor(userData){
this.userData=userData;
}
onSubmit(){
HttpUtils.get("(some url)/?token="+this.userData.token");
}
}
the problem comes when modifying WelcomePage:
class WelcomePage{
constructor(userData){
this.userData=userData;
}
showAboutDialog(){
new AboutDialog(this.userData).show();
}
}
With global state, WelcomePage doesn't need to modify and even doesn't depend on UserData, but the one without global state (or say the "dependency injection version") needs to do so. Isn't the dependency injection one violate "open-closed principle"? Isn't disallowing global state results in more coupling here?
Nevertheless, in this case, the route from Main to AboutDialog is relatively simple: Main->WelcomePage->AboutPage, what if there is a class needs more intermediate to pass UserData for it (eg: Main->SomePage->SomeSubPage->SomeDialog->SomeService...)?
Also I found the "dependency injection" one is harder to maintain for other reasons:
It contains more code (eg: extra constructor and class property to pass data)
When a class doesn't need UserData, I may forget to remove those constructor and class property from the intermediate(eg:WelcomePage), result in modifying UserData at intermediate accidentally, which suffers from the same problem with global state that someone may modify the data accidently
And I think the other reasons to disallow global state is not applicable for me here:
concurrency problem: this app should have no multithreading codes (at far as I known)
performance: assigning a data field shouldn't cost to much time
So my question is, is this case a valid reason to allow global state instead of dependency injection?