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