Interfaces
An interface is a promise that a type can do certain things, with nothing
said about how. It lists methods and properties, and has no fields and no
code. Any type can take one on with an implements line, and a type can take
on as many as it needs without giving up its one base.
'' A contract: what an encoder can do, with no data and no code. Any type
'' can take it on with an 'implements' line.
interface IEncoder
function Name( ) as ustring
function Encode( byref key as ustring, byval value as int32 ) as ustring
end interface
'' A plain type: no base, no vtable.
type IniEncoder
implements IEncoder
declare function Name( ) as ustring implements IEncoder.Name
declare function Encode( byref key as ustring, _
byval value as int32 ) as ustring _
implements IEncoder.Encode
sep as ustring = "="
end type
function IniEncoder.Name( ) as ustring
return "ini"
end function
function IniEncoder.Encode( byref key as ustring, _
byval value as int32 ) as ustring
return key & sep & value
end function
'' A class: a type that always extends Object.
class JsonEncoder
implements IEncoder
declare function Name( ) as ustring implements IEncoder.Name
declare function Encode( byref key as ustring, _
byval value as int32 ) as ustring _
implements IEncoder.Encode
end class
function JsonEncoder.Name( ) as ustring
return "json"
end function
function JsonEncoder.Encode( byref key as ustring, _
byval value as int32 ) as ustring
return """" & key & """: " & value
end function
'' Written once, against the contract only.
sub Save( byval enc as IEncoder )
print "as "; enc.Name( ); ":"
print " "; enc.Encode( "width", 640 )
print " "; enc.Encode( "height", 480 )
end sub
dim as IniEncoder ini
dim as JsonEncoder json
Save( ini )
Save( json )
print "an IEncoder value is"; sizeof( IEncoder ); " bytes"
Output
as ini:
width=640
height=480
as json:
"width": 640
"height": 480
an IEncoder value is 16 bytes
IEncoder is all contract: two members, every one public and abstract, so
nothing else is written on them. Save is written against IEncoder alone. It
runs unchanged for every encoder there is, including ones written after it.
implements IEncoder at the top of a type's body takes the contract on.
Then each method says which member it provides: implements IEncoder.Name.
IniEncoder is a plain type, with no base and no table of virtual methods;
JsonEncoder is a class, a type that always extends Object. Both can
implement an interface, and Save cannot tell them apart.
An interface value is a reference: the object's address and the table that
type uses for this interface, 16 bytes. Save( ini ) passes a reference to
ini, not a copy of it, and enc.Name( ) is called with .. Because it only
refers to the object, the object must outlive it: a temporary, such as a
function's result, cannot be converted to an interface (TKO1012), and
delete through an interface is refused (TKO1013).
The mapping is explicit. A public Name with the right signature does not
implement IEncoder.Name until its declaration says so:
interface IEncoder
function Name( ) as ustring
end interface
'' Name is public and has the right signature, but nothing maps it.
type IniEncoder
implements IEncoder
declare function Name( ) as ustring
sep as ustring = "="
end type
function IniEncoder.Name( ) as ustring
return "ini"
end function
unmapped.tiko(7,16): error TKO1000: 'iniencoder' does not implement 'iencoder.name'; add 'implements iencoder.name' to one of its methods
|
7 | implements IEncoder
| ^
unmapped.tiko(2,5): note: 'name' is required by interface 'iencoder', declared here
|
2 | function Name( ) as ustring
| ^
Being explicit is what lets one type follow two contracts that use the same member name for different things: see Several interfaces.
An abstract type can serve as a contract too, and that is the right choice when the base should also supply code that derived types share. But a type extends exactly one base, so a contract written that way uses the base up.
Activities
- Add a
CsvEncoderthat writeswidth,640, and pass one toSave. - Give
IEncodera third member,function Footer( ) as ustring, and read what the compiler says about each encoder until both provide it. - Rename
IniEncoder.NametoLabel, keepingimplements IEncoder.Name. What changes in the output?