Do Events break Interfaces?
Note: in this question, I am talking about formal Interfaces (e.g. public interface IButton {...} ).
I'm an Actionscript developer, but I suspect my question will resonate in many languages that allow both formal Interfaces and Events.
I love Interfaces. I love the separation between interface and implementation. (I wish Actionscript forced every class to implement a formal Interface, the way Objective-C does.) All of my classes implement an interface (or several), and you'll never find me writing a public method than isn't referenced in an Interface.
But Actionscript is also heavily dependent on Events. My problem is this: every part of a class that communicates with external classes or processes is (or should be) part of its Interface. (I realize this is a matter of opinion. If you disagree, this question will probably be meaningless to you.)
There are several ways to muddy this clear picture, but most of them are avoidable:
You can't list public properties in an Interface. Solution: don't use public properties in your classes. Use getters and setters instead (and I'm not all that crazy about them, either, but I use them when I must).
If classes communicate by passing arguments to constructors, those messages bypass Interfaces, since you can't list a constructor function in an Interface. I avoid this problem by (almost) never passing parameters through constructors. I prefer init() methods that can be made explicit in Interfaces.
Classes can have public methods that aren't listed in an Interface. I avoid this by not doing it. My classes implement Interfaces. Those Interfaces contain headers for ALL public methods.
Many classes dispatch Events.
That last one is the killer.
Let's say I give you a class called Blah, and you ask me how to use it. I can tell you, "It implements the IBlah Interface. Just look at that and you'll see everything you can do with Blah."
Except Blah extends EventDispatcher, which implies that it dispatches Events.
"What Events does it dispatch?" You ask.
"Uh..."
To know, you have to check the JavaDoc or read through Blah's source code. You shouldn't have to do either! You sho开发者_如何学Culd be able to know how to interact with a class by checking its Interface(s).
I wish Interfaces could look like this:
public interface IBlah
{
function foo() : void;
function bar() : Boolean;
event BlahEvent;
}
But you can't specify events in an Interface.
Am I right that Events break Interfaces? To me, it's as if I gave you a car and said here's the manual. In the manual, it supposedly explains everything that's on the dashboard. But then, when you're driving the car, weird dohickies appear and strange sounds play -- Events that aren't mentioned in the manual.
If I'm wrong, please explain.
If I'm right, is there a good way to avoid the problem?
I think that what you are missing is that events are completely optional. There is no rule that says when you instantiate a class that you have to handle all events that it raises. You can (if your implementation calls for it) just completely ignore any events called raised by an object. Also, just definining and event says nothing about when or if it will actually be raised. So putting an event in an interface would be useless because there would be no way defining when or if the event was raised when the interface was implemented in a object.
My comment aside, I think you can declare function headers in the IBlah
interface like dispatchMouseDownEvent
(that takes required parameters, of course) and implement them in Blah
to dispatch a mouse down event. Now instead of dispatching a mouse down event from a method, call dispatchMouseDownEvent
from that method. Just make sure that you don't dispatch a mouse down event from anywhere else in the class.
Here's one way of doing it:
package
{
public interface IMortal
{
function stabMe() : void;
function dispatchDeathEvent() : void
}
}
package
{
import flash.events.EventDispatcher;
import flash.events.Event;
public class Person extends EventDispatcher implements IMortal
{
private var _dispatchesAllowed : Boolean = false;
private var _lifeForce : Number = 10;
public function stabMe() : void
{
if ( --_lifeForce == 0 ) iAmDead();
}
private function iAmDead() : void
{
_dispatchesAllowed = true;
dispatchDeathEvent();
}
public function dispatchDeathEvent() : void
{
if ( _dispatchesAllowed )
dispatchEvent( new Event( Event.COMPLETE ) );
_dispatchesAllowed = false;
}
}
}
I like this, because (a) it lists the event in the Interface and (b) locking the event from outside (_dispatchesAllowed) is optional. That's an implementation detail.
I dislike it because it's a hack. It's weird for a public method to be callable but useless unless it's called by and instance of its host class.
EventDispatcher implements IEventDispatcher and interfaces can extend other interfaces. So you can just do this:
public interface IButton extends IEventDispatcher
{
}
Now the IEventDispatcher methods are available on IButton
Edit: Also, regarding point #2, the class wouldn't have to use a constructor if it were given a factory interface.
Edit: Regarding point #1, fields (no get/set) are data. Interfaces only describe behavior without describing implementation. Having a field on an interface flies in the face of this by requiring you to implement it as a field.
精彩评论