好吧,关于
,我会指出我的
5 commandments
致您。:)
从…开始
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
从主类的角度来看,登录屏幕如下所示:
interface ILoginInterface {
function show(inputHandler:String->String->Void):Void;
function hide():Void;
function error(reason:String):Void;
}
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
看起来有点胖。但老实说,它做了它需要做的一切。现在它可以由一个类有效地实现
Server
N
调用实际服务器提供的,但首先,
网络服务提供商
这意味着
. 出于同样的原因
ILoginInterface
是真的保持在最低限度。
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();
当然,这远不是一个干净的方法。事实是,这是最简单的方法,依赖性仅限于一次,以后可以通过
dependency injection
作为一个简单的例子
IoC
-haXe中的容器:
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
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();
这样,实际上各个类之间没有耦合。
关于如何在按钮和登录屏幕之间传递事件的问题:
这只是一个品味和执行的问题。
事件驱动编程的要点是,源和观察者仅在某种意义上是耦合的,
源必须发送某种通知,而目标必须能够处理它。
someButton.onClick = handler;
基本上是这样的,但它是如此优雅和简洁,你不会使一个模糊的。
someButton.onClick(handler);
现在说到AOP,在这种情况下它不是正确的方法。把组件连接起来并不是一个聪明的方法,而是处理
cross-cutting concerns
在你的代码库里放一些意大利面条是可以的,只要
-
-
意大利面部分足够小,可以在合理的时间内理解或重构/重写,而不会破坏应用程序(第一点应该保证)
编辑:
针对汤姆的问题:
-
那是品味的问题。在某些框架中,人们甚至使用外部配置文件,但这对于haXe来说没有什么意义,因为您需要指示编译器强制编译在运行时注入的依赖项。在您的代码中,在一个中心文件中设置依赖关系,同样需要大量的工作,而且要简单得多。对于更多的结构,您可以将应用程序拆分为“模块”,每个模块都有一个负责注册其提供的实现的加载程序类。在主文件中,加载模块。
-
那要看情况了。我倾向于根据它们在类的包中声明它们,然后将它们重构到一个额外的包中,以防其他地方需要它们。通过使用匿名类型,还可以完全解耦,但在flash9平台上会有轻微的性能影响。
-
我不会抽象这个按钮,然后通过IoC注入一个实现,但可以这样做。我会明确地创建它,因为最终,它只是一个按钮。它有样式、标题、屏幕位置和大小,并触发单击事件。我认为,正如上面指出的,这是不必要的模块化。
-
behavioral patterns
尔兹