Next: Abstract Classes, Up: Abstract Members [Contents]
2.4.1 Interfaces
I = Interface( string name, Object dfn )
Define named interface I identified by name described by dfn.
I = Interface( string name ).extend( Object dfn )
Define named interface I identified by name described by dfn.
I = Interface( Object dfn )
Define anonymous interface I as described by dfn.
I = Interface.extend( Object dfn )
Define anonymous interface I as described by dfn.
Interfaces are defined with a syntax much like classes (see Defining Classes) with the following properties:
- Interface I cannot be instantiated.
- Every member of dfn of I is implicitly
abstract
.- Consequently, dfn of I may contain only abstract methods.
- Interfaces may only extend other interfaces (see Inheritance).
Interface
must be imported (see Including) from
easejs.Interface
; it is not available in the global scope.
2.4.1.1 Implementing Interfaces
C = Class( name ).implement( I\_0[, ...I\_n]
).extend( dfn ) Define named class C identified by name implementing all interfaces I, described by dfn.
C = Class.implement( I\_0[, ...I\_n ).extend( dfn )
Define anonymous class C implementing all interfaces I, described by dfn.
Any class C may implement any interface I, inheriting its API. Unlike class inheritance, any class C may implement one or more interfaces.
- Class C implementing interfaces I will be considered a subtype of every I.
- Class C must either:
- Provide a concrete definition for every member of dfn of I,
- or be declared as an
AbstractClass
(see Abstract Classes)- C may be declared as an
AbstractClass
while still providing a concrete definition for some of dfn of I.
- C may be declared as an
2.4.1.2 Discussion
Consider a library that provides a websocket abstraction. Not all environments support web sockets, so an implementation may need to fall back on long polling via AJAX, Flash sockets, etc. If websocket support is available, one would want to use that. Furthermore, an environment may provide its own type of socket that our library does not include support for. Therefore, we would want to provide developers for that environment the ability to define their own type of socket implementation to be used in our library.
This type of abstraction can be solved simply by providing a generic API
that any operation on websockets may use. For example, this API may provide
connect()
, onReceive()
and send()
operations, among
others. We could define this API in a Socket
interface:
var Socket = Interface( 'Socket', { 'public connect': [ 'host', 'port' ], 'public send': [ 'data' ], 'public onReceive': [ 'callback' ], 'public close': [], } );
We can then provide any number of Socket
implementations:
var WebSocket = Class( 'WebSocket' ).implement( Socket ).extend( { 'public connect': function( host, port ) { // ... }, // ... } ), SomeCustomSocket = Class.implement( Socket ).extend( { // ... } );
Anything wishing to use sockets can work with this interface polymorphically:
var ChatClient = Class( { 'private _socket': null, __construct: function( socket ) { // only allow sockets if ( !( Class.isA( Socket, socket ) ) ) { throw TypeError( 'Expected socket' ); } this._socket = socket; }, 'public sendMessage': function( channel, message ) { this._socket.send( { channel: channel, message: message, } ); }, } );
We could now use ChatClient
with any of our Socket
implementations:
ChatClient( WebSocket() ).sendMessage( '#lobby', "Sweet! WebSockets!" ); ChatClient( SomeCustomSocket() ) .sendMessage( '#lobby', "I can chat too!" );
The use of the Socket
interface allowed us to create a powerful
abstraction that will allow our library to work across any range of systems.
The use of an interface allows us to define a common API through which all
of our various components may interact without having to worry about the
implementation details - something we couldn’t worry about even if we tried,
due to the fact that we want developers to support whatever environment they
are developing for.
Let’s make a further consideration. Above, we defined a onReceive()
method which accepts a callback to be called when data is received. What if
our library wished to use an Event
interface as well, which would
allow us to do something like ‘some_socket.on( 'receive', function()
{} )’?
var AnotherSocket = Class.implement( Socket, Event ).extend( { 'public connect': // ... 'public on': // ... part of Event } );
Any class may implement any number of interfaces. In the above example,
AnotherSocket
implemented both Socket
and Event
,
allowing it to be used wherever either type is expected. Let’s take a look:
Interfaces do not suffer from the same problems as multiple inheritance, because we are not providing any sort of implementation that may cause conflicts.
One might then ask - why interfaces instead of abstract classes
(see Abstract Classes)? Abstract classes require subclassing, which
tightly couples the subtype with its parent. One may also only inherit from
a single supertype (see Inheritance), which may cause a problem in our
library if we used an abstract class for Socket
, but a developer had
to inherit from another class and still have that subtype act as a
Socket
.
Interfaces have no such problem. Implementors are free to use interfaces wherever they wish and use as many as they wish; they needn’t worry that they may be unable to use the interface due to inheritance or coupling issues. However, although interfaces facilitate API reuse, they do not aid in code reuse as abstract classes do9.
Footnotes
(9)
This is a problem that will eventually be solved by the introduction of traits/mixins.
Next: Abstract Classes, Up: Abstract Members [Contents]