Events 高级语言中的松散编程,如何、为什么和多少?

Events 高级语言中的松散编程,如何、为什么和多少?,events,oop,functional-programming,haxe,aop,Events,Oop,Functional Programming,Haxe,Aop,我正在用Haxe编写代码。不过,这与问题无关,只要您记住它是一种高级语言,可以与Java、ActionScript、JavaScript、C#等进行比较(我在这里使用伪代码) 我要做一个大项目,现在正忙着准备。对于这个问题,我将创建一个小场景:一个简单的应用程序,它有一个主类(这个类在应用程序启动时执行)和一个LoginScreen类(这基本上是一个加载登录屏幕以便用户可以登录的类) 通常情况下,我猜这将如下所示: Main constructor: loginScreen = new Logi

我正在用Haxe编写代码。不过,这与问题无关,只要您记住它是一种高级语言,可以与Java、ActionScript、JavaScript、C#等进行比较(我在这里使用伪代码)

我要做一个大项目,现在正忙着准备。对于这个问题,我将创建一个小场景:一个简单的应用程序,它有一个主类(这个类在应用程序启动时执行)和一个LoginScreen类(这基本上是一个加载登录屏幕以便用户可以登录的类)

通常情况下,我猜这将如下所示:

Main constructor:
loginScreen = new LoginScreen()
loginScreen.load();

LoginScreen load():
niceBackground = loader.loadBitmap("somebg.png");
someButton = new gui.customButton();
someButton.onClick = buttonIsPressed;

LoginScreen buttonIsPressed():
socketConnection = new network.SocketConnection();
socketConnection.connect(host, ip);
socketConnection.write("login#auth#username#password");
socketConnection.onData = gotAuthConfirmation;

LoginScreen gotAuthConfirmation(response):
if response == "success" {
   //login success.. continue
}
这个简单的场景为我们的类添加了以下依赖项和缺点:

  • 如果没有LoginScreen,则无法加载Main
  • 没有自定义加载程序类,LoginScreen将无法加载
  • 如果没有自定义按钮类,LoginScreen将无法加载
  • 如果没有自定义SocketConnection类,LoginScreen将无法加载
  • SocketConnection(将来必须由许多不同的类访问)现在已设置在LoginScreen内部,这实际上与它无关,只是LoginScreen第一次需要套接字连接
为了解决这些问题,有人建议我做“事件驱动编程”,或松耦合。据我所知,这基本上意味着必须使类彼此独立,然后在单独的绑定器中将它们绑定在一起

问题1:我的观点是对的还是错的?必须使用活页夹吗

我听说面向方面编程在这方面会有所帮助。不幸的是,Haxe不支持这种配置

但是,我可以访问一个事件库,它基本上允许我创建一个信号器(public var loginPressedSignaller=new signaller()),触发一个信号器(loginPressedSignaller.fire()),并侦听一个信号器(someClass.loginPressedSignaller.bind(dosomethinghenloginpressed))

因此,在没有进一步调查的情况下,我认为这会将我以前的设置更改为:

Main:
public var appLaunchedSignaller = new Signaller();

Main constructor:
appLaunchedSignaller.fire();

LoginScreen:
public var loginPressedSignaller = new Signaller();

LoginScreen load():
niceBackground = !!! Question 2: how do we use Event Driven Programming to load our background here, while not being dependent on the custom loader class !!!
someButton = !!! same as for niceBackground, but for the customButton class !!!
someButton.onClick = buttonIsPressed;

LoginScreen buttonIsPressed():
loginPressedSignaller.fire(username, pass);

LoginScreenAuthenticator:
public var loginSuccessSignaller = new Signaller();
public var loginFailSignaller = new Signaller();

LoginScreenAuthenticator auth(username, pass):
socketConnection = !!! how do we use a socket connection here, if we cannot call a custom socket connection class !!!
socketConnection.write("login#auth#username#password");
这段代码还没有完成,例如,我仍然需要监听服务器的响应,但您可能知道我在哪里被卡住了

问题2:这种新结构有意义吗?我应该如何解决上面提到的问题!!!分隔符

然后我听说了活页夹。所以我可能需要为每个类创建一个活页夹,将所有内容连接在一起。大概是这样的:

MainBinder:
feature = new Main();    

LoginScreenBinder:
feature = new LoginScreen();
MainBinder.feature.appLaunchedSignaller.bind(feature.load);
niceBackgroundLoader = loader.loadBitmap;
someButtonClass = gui.customButton();
interface ILoginInterface {
    function show(inputHandler:String->String->Void):Void;
    function hide():Void;
    function error(reason:String):Void;
}
等等。。。希望你明白我的意思。这篇文章有点长,所以我得把它收尾一点

问题3:这有意义吗?这难道不会让事情变得不必要的复杂吗


此外,在上面的“绑定器”中,我只需要使用实例化一次的类,例如登录屏幕。如果一个类有多个实例,例如国际象棋中的一个玩家类,该怎么办呢?

首先,我对Haxe一点都不熟悉。然而,我要回答的是,这里描述的内容听起来与我在.NET中学习做事的方式非常相似,所以我觉得这是一个很好的实践

在.NET中,当用户单击按钮执行某项操作(如登录)时,会触发一个“事件”,然后执行一个方法来“处理”该事件

当触发另一个类中的事件时,总会有代码描述在一个类中执行的方法。这不是不必要的复杂,而是必然的复杂。在VisualStudioIDE中,这段代码的大部分都隐藏在“设计器”文件中,因此我不会经常看到它,但是如果您的IDE没有这项功能,那么您必须自己编写代码

关于如何使用自定义加载程序类,我希望这里有人能给您提供答案。

好吧,关于如何使用,我想向您指出我的建议。:)

对于这个问题,只有3个是真正重要的:

  • 单一责任制(SRP)
  • 接口隔离(ISP)
  • 依赖性反演(DIP)
从SRP开始,你必须问自己一个问题:“X班的责任是什么?”

登录屏幕负责向用户显示一个界面,以填写和提交其登录数据。因此

  • 它依赖于button类是有意义的,因为它需要button
  • 这是没有意义的它做所有的网络等
  • 首先,让我们抽象一下登录服务:

    interface ILoginService {
         function login(user:String, pwd:String, onDone:LoginResult->Void):Void;
         //Rather than using signalers and what-not, I'll just rely on haXe's support for functional style, 
         //which renders these cumbersome idioms from more classic languages quite obsolete.
    }
    enum Result<T> {//this is a generic enum to return results from basically any kind of actions, that may fail
         Fail(error:Int, reason:String);
         Success(user:T);
    }
    typedef LoginResult = Result<IUser>;//IUser basically represent an authenticated user
    
    正在执行登录:

    var server:ILoginService = ... //where ever it comes from. I will say a word about that later
    var login:ILoginInterface = ... //same thing as with the service
    login.show(function (user, pwd):Void {
          server.login(user, pwd, function (result) {
                 switch (result) {
                      case Fail(_, reason): 
                            login.error(reason);
                      case Success(user): 
                            login.hide();
                            //proceed with the resulting user
                 }
          });
    });//for the sake of conciseness I used an anonymous function but usually, you'd put a method here of course
    
    现在,
    ILoginService
    看起来有点像个傻瓜。但老实说,它做了它需要做的一切。现在它可以由一个类
    服务器
    有效地实现,该类将所有网络封装在一个类中,为实际服务器提供的N个调用中的每一个调用都有一个方法,但首先,ISP表明,许多特定于客户端的接口比一个通用接口更好。出于同样的原因,
    ILoginInterface
    实际上保持了最低限度

    无论这两个函数实际上是如何实现的,您都不需要更改
    Main
    (当然,除非接口更改)。这是正在应用的倾角
    Main
    不依赖于具体的实现,只依赖于非常简洁的抽象

    现在让我们来看一些实现:

    class LoginScreen implements ILoginInterface {
        public function show(inputHandler:String->String->Void):Void {
            //render the UI on the screen
            //wait for the button to be clicked
            //when done, call inputHandler with the input values from the respective fields
        }
        public function hide():Void {
            //hide UI
        }
        public function error(reason:String):Void {
            //display error message
        }
        public static function getInstance():LoginScreen {
            //classical singleton instantiation
        }
    }
    class Server implements ILoginService {
        function new(host:String, port:Int) {
            //init connection here for example
        }
        public static function getInstance():Server {
            //classical singleton instantiation
        }   
        public function login(user:String, pwd:String, onDone:LoginResult->Void) {
            //issue login over the connection
            //invoke the handler with the retrieved result
        }
        //... possibly other methods here, that are used by other classes
    }
    
    好吧,我想这很直截了当。但为了好玩,让我们做一些真正愚蠢的事情:

    class MailLogin implements ILoginInterface {
        public function new(mail:String) {
            //save address
        }
        public function show(inputHandler:String->String->Void):Void {
            //print some sort of "waiting for authentication"-notification on screen
            //send an email to the given address: "please respond with username:password"
            //keep polling you mail server for a response, parse it and invoke the input handler
        }
        public function hide():Void {
            //remove the "waiting for authentication"-notification
            //send an email to the given address: "login successful"
        }
        public function error(reason:String):Void {
            //send an email to the given address: "login failed. reason: [reason] please retry."
        }   
    }
    
    从主类的角度来看,这种身份验证可能很普通, 这不会改变任何事情,因此也同样有效

    更可能的情况是,您的登录服务位于进行身份验证的另一台服务器(可能是HTTP服务器)上,如果成功,则在实际的应用服务器上创建会话。就设计而言,这可以反映在两个单独的类中

    var server:ILoginService = Server.getInstance();
    var login:ILoginInterface = LoginScreen.getInstance();
    
    class Injector {
        static var providers = new Hash < Void->Dynamic > ;
        public static function setProvider<T>(type:Class<T>, provider:Void->T):Void {
            var name = Type.getClassName(type);
            if (providers.exists(name))
                throw "duplicate provider for " + name;
            else
                providers.set(name, provider);
        }
        public static function get<T>(type:Class<T>):T {
            var name = Type.getClassName(type);
            return
                if (providers.exists(name))
                    providers.get(name);
                else
                    throw "no provider for " + name;
        }
    }
    
    using Injector;
    
    //wherever you would like to wire it up:
    ILoginService.setProvider(Server.getInstance);
    ILoginInterface.setProvider(LoginScreen.getInstance);
    
    //and in Main:
    var server = ILoginService.get();
    var login = ILoginInterface.get();