Skip to main content
Known Participant
February 27, 2009
Question

Cfinterface returntype limitation

  • February 27, 2009
  • 13 replies
  • 1450 views
It seems that the returntype validation in cfinterface and implementing classes is a bit too limiting....I can't see how a CFC function could ever return an instance of itself.

For example, let's say I have an IOrder interface with an init() function where the returntype="IOrder". Then I create an Order.cfc that implements IOrder and add my init() function with returntype="Order". CF blows up at object creation and says the return types do not match. I think this is mistaken, since the Order returntype is in fact an instance of IOrder.

Is there something I'm missing? I know I can use "any" in both functions as a workaround, but that undermines the whole point of using strict interfaces in the first place.
This topic has been closed for replies.

13 replies

BKBK
Community Expert
Community Expert
March 6, 2009
Rich.thibault wrote:
This feels kinda wrong to me, and yet examining MediaExporter objects returned by getInstance() shows them to still be instances of MediaExporter, and all functions are available, even those not mandated by the IMediaExporter interface.

I think it's runtime polymorphism in action. One reference, many forms. At runtime, the returned this is at once of type IMediaExporter and an instance of MediaExporter. For example, the following test will display

string zero
string one
string two

even though the functions f and f2 aren't defined in the interface IExample1.

BKBK
Community Expert
Community Expert
March 6, 2009
Rich.thibault wrote:
public interface IMediaExporter {
public IMediaExporter getInstance();
}

public class MediaExporter implements IMediaExporter {
public MediaExporter getInstance() {
return this;
}
}

This does not work in CF:

<cfinterface>
<cffunction name="getInstance" output="false" returntype="IMediaExporter">
</cffunction>
</cfinterface>

<cfcomponent output="false" implements="IMediaExporter">*
<cffunction name="getInstance" output="false" returntype="MediaExporter">*
<cfscript>
return this;
</cfscript>
</cffunction>
</cfcomponent>


-==cfSearching==- wrote:
Since CF does not support overloading either, I would not be very surprised if it did not support covariant return types, by design. Personal preferences aside, it does mention in the documentation "Must be identical to value in interface; however, an omitted type option and an option value of any are equivalent". That certainly does read as if it was a conscious decision. Though the fact that you can also return "any" muddies the waters a bit.

I suspect it's got to do with the fact that Java's concept of static type-checking doesn't (yet) carry over to Coldfusion. I am referring to the meaning static that is opposed to dynamic or runtime.

The static type is the type of the reference. Java checks the static type at compile-time. To borrow from your example, one may write in Java

IMediaExporter myMediaExporter = new MediaExporter();

Here, the static type, that is, the type of the reference, is the interface, IMediaExporter. There is no equivalent to that line of code in Coldfusion components. Coldfusion has (as yet) no static-type checking. I guess that that is the reason behind the requirement that the return types be identical or else "any".

However, all is not lost. Modify your code as follows.

<cfinterface>
<cffunction name="getInstance" output="false" returntype="IMediaExporter">*
</cffunction>
</cfinterface>

<cfcomponent output="false" implements="IMediaExporter">
<cffunction name="getInstance" output="false" returntype="IMediaExporter">*
<cfscript>
return this;
</cfscript>
</cffunction>
</cfcomponent>

<!--- IMediaExporter type --->
<cfset iObj = createobject("component", "MediaExporter").getInstance()>

<p>Is returned IMediaExporter type an instance of MediaExporter: <cfoutput>#isInstanceOf(iObj,'MediaExporter')#</cfoutput></p>

Coldfusion would tell you that the returned IMediaExporter type is an instance of MediaExporter. Also, there is always the possibility to leave everything unchanged in your Java code, and to do this:

<cfset obj=createobject("java","MediaExporter")>
<cfset iObj=createobject("java","IMediaExporter")>



* Correction following feedback from Rich-thibault and -==cfSearching==-. The type returned should indeed be the interface IMediaExporter , not the component MediaExporter. There is no such thing as an instantiation of the interface, as I, myself, pointed out. (NB: I interprete Coldfusion's isInstanceOf to mean, in the case of interfaces, typing rather than instantiation)



Inspiring
March 2, 2009
> Hi Adam, what you say all makes sense, but I still think I should be able to
> return MediaExporter in the implementing class, just like I can in Java.

Crikey dick, so you can! I didn't know that. I didn't believe you so I
knocked together a test class and lo and behold. I'm not sure why I didn't
come across that before. I'm by no means a Java expert (I'm very much a
Java novice), but I'm sure I would have recalled that from my travails had
any of my books and what not pointed it out. Unfortunately my Head FIrst
Java book is in a box in NZ and I'm in the UK, so I can't be checking.

OK, so my wee story before was woven on the basis of how CF works, and just
guessing how it ought to work elsewhere. I take a small amount of solace
from the fact (although I'm not sure) that C# doesn't seem to allow it.
Well my 10min foray into experimentation failed, anyhow.


> Since
> MediaExporter implements IMediaExporter, it should be perfectly legal to
> substitute it there as the returntype. It's not a mismatch at all, as the
> error message indicates, IMHO.

Yep, sure. I agree with you now (note to others around here: see it does
happen sometimes ;-).

I wonder whether CF's way of doing it is a bug, or by design or some side
effect of typelessness? I'm suspecting the first one. I shall do some
more experimentation and ask around.

Cheers for the heads-up: I like being proven wrong because it means I learn
something new ;-)

--
Adam
Inspiring
March 3, 2009
> I still think I should be able to return MediaExporter in the implementing class, just like I can in Java.

In later versions, yes.

> I wonder whether CF's way of doing it is a bug, or by design or some side
> effect of typelessness? I'm suspecting the first one. I shall do some
> more experimentation and ask around.

Since CF does not support overloading either, I would not be very surprised if it did not support covariant return types, by design. Personal preferences aside, it does mention in the documentation "Must be identical to value in interface; however, an omitted type option and an option value of any are equivalent". That certainly does read as if it was a conscious decision. Though the fact that you can also return "any" muddies the waters a bit.
(But that might just be a necessity due to the typelessness factor.)

Bottom line, I do not know the true "why" of it either. While I have my suspicions, I would be very interested in hearing a more definitive answer.

> I take a small amount of solace
> from the fact (although I'm not sure) that C# doesn't seem to allow it.
> Well my 10min foray into experimentation failed, anyhow.

You are right. I did not know C# does does not support them either. So cheers for that.

> I agree with you now (note to others around here: see it does
> happen sometimes ;-).

Phew! That explains the flying pigs I saw out my window ;-)

(Just kidding)
Known Participant
March 6, 2009
Hi BKBK,

The only problem with your approach - putting the concrete class as the return type in the interface - is that there may (and should!) be multiple implementations tied to the same interface, and the interface doesn't (and shouldn't!) have any knowledge of them. It doesn't know whether to return a MediaExporter, a VideoMediaExporter, an AudioMediaExporter, a PurpleBananaMediaExporter, etc.

I'm actually leaning more towards Adam's suggestion of returning IMediaExporter in both places:

<cfinterface>
<cffunction name="getInstance" output="false" returntype="IMediaExporter">
</cffunction>
</cfinterface>

<cfcomponent output="false" implements="IMediaExporter">
<cffunction name="getInstance" output="false" returntype="IMediaExporter">
<cfscript>
return this;
</cfscript>
</cffunction>
</cfcomponent>

This feels kinda wrong to me, and yet examining MediaExporter objects returned by getInstance() shows them to still be instances of MediaExporter, and all functions are available, even those not mandated by the IMediaExporter interface. I haven't played around with it enough to know if there are any issues when MediaExporter implements multiple interfaces, but I imagine there aren't.

Known Participant
March 2, 2009
Hi Adam, what you say all makes sense, but I still think I should be able to return MediaExporter in the implementing class, just like I can in Java. Since MediaExporter implements IMediaExporter, it should be perfectly legal to substitute it there as the returntype. It's not a mismatch at all, as the error message indicates, IMHO.
Inspiring
March 2, 2009
> Adam, you are correct, I can return the interface type in both the interface
> and the concrete implementation, but that's not what I want. I want to return
> a full MediaExporter object, including any additional functions it may include,
> or even any additional interfaces that it implements. Seems to me since
> MediaExporter implements IMediaExporter, I should be able to use either one of
> those as the returntype in MediaExporter.getInstance().

No, that's not how it works. Just specify the interface name in the return
type: that is actually what you want to do here.

As you say, a MediaExporter object *is* a specialised type of
IMediaExporter, so that's OK.

Your method signature in the interface is saying the that the method needs
to return an object that is IMediaExporter. It's not saying it
specifically needs to be a MediaExporter.

Bear in mind that another component might also implement that interface,
and it would possibly not be returning a MediaExporter object. Although it
could be!

The difference being that an IMediaExporter-type object might only defined
method1, method2 and method3. A MediaExporter might implement those (well:
it would have to in this instance!), but also method4 and method5. Those
are two different sets of expectations for the calling code, so you need to
be precise about what you tell it to expect. Either a IMediaExporter-type,
or a MediaExporter-type.

That your implementation of that method is such that it actually
specifically returns a MediaExporter is down to the implementation, and
beyond the interest of the interface, but it fulfils the requirement, so no
problem: there's nothing wrong with that.

If you want a method to specifically return a MediaExporter and no other
sort of object, it's liekly to be outside the scope of the IMediaExporter
interface, because it's too specific. If MediaExporterNew.cfc also
implemented IMediaExporter, you'd probably not want its implementation of
getInstance() to return a MediaExporter object.

Make sense? I'm not sure I'm explaining to as well as one possibly could.

--
Adam
Known Participant
March 2, 2009
Hi all,

Thanks for the responses. I think the init/constructor issue is besides the point though. This issue applies to any function that returns "this".

So let's say instead I have a getInstance() function. This works in Java:

public interface IMediaExporter {
public IMediaExporter getInstance();
}

public class MediaExporter implements IMediaExporter {
public MediaExporter getInstance() {
return this;
}
}

This does not work in CF:

<cfinterface>
<cffunction name="getInstance" output="false" returntype="IMediaExporter">
</cffunction>
</cfinterface>

<cfcomponent output="false" implements="IMediaExporter">
<cffunction name="getInstance" output="false" returntype="MediaExporter">
<cfscript>
return this;
</cfscript>
</cffunction>
</cfcomponent>

ERROR: The getInstance function does not specify the same return type in the MediaExporter ColdFusion component and the IMediaExporter ColdFusion interface.

Adam, you are correct, I can return the interface type in both the interface and the concrete implementation, but that's not what I want. I want to return a full MediaExporter object, including any additional functions it may include, or even any additional interfaces that it implements. Seems to me since MediaExporter implements IMediaExporter, I should be able to use either one of those as the returntype in MediaExporter.getInstance().
BKBK
Community Expert
Community Expert
March 1, 2009
Rich.Thibault wrote:
For example, let's say I have an IOrder interface with an init() function where the returntype="IOrder". Then I create an Order.cfc that implements IOrder and add my init() function with returntype="Order". CF blows up at object creation and says the return types do not match. I think this is mistaken, since the Order returntype is in fact an instance of IOrder.

Is there something I'm missing? I know I can use "any" in both functions as a workaround, but that undermines the whole point of using strict interfaces in the first place.


I do believe -==cfSearching==- has hit it spot-on. An interface is simply a contract that tells clients** which behaviour(i.e. which methods) a server promises to implement. Since the client knows the interface, it can tell the server to deliver just the behaviour that is exposed in the interface, no more. That makes for clean and efficient coding.

As contract, an interface is therefore an empty implementation, and has no business defining a constructor. If it did, that would defeat its very purpose, which is to decouple the client from the server.

Since the client depends only on the interface, it is decoupled from the implementation in the component. This reduces dependencies between different parts of your application. You can then vary the implementation within your components at any time without fear of breaking clients.

This led to the programming maxim, "Code to the interface, rather than to the implementation". Applying the maxim to your example, would result in something like

io = createobject("component", "Order").init(); // io is of type IOrder

The init() returns an Order, not an IOrder. See how the lefthand side, io, a static type, refers to a dynamic type at runtime. There lies the power of runtime polymorphism.



**By client I mean the object making a request in the form of a method call. By server I mean the object that serves the call, that is, an instance of the component that implements the interface.

Inspiring
March 1, 2009
> I'm not sure I necessarily agree, but I see where they're coming from ;-)

Yes, even after thinking about it I am still in the opposite camp ;-) I would still lean towards using interfaces to group classes/components with similar behaviors, and inheritance for classes/components with similar implementations. For example, you might have different types of Orders that have high level properties in common (like an Order Number), but distinctly different implementations.

But, it is not as if using interfaces to enforce a constructor signature is mandatory. So I suppose the world will not end if one does it that way ;-) Though if anyone can think of scenarios where using interfaces would be preferable, I would be interested in hearing it.
Inspiring
February 28, 2009
> An
> interface does not care _how_ a class implements something, only that it does.
> Constructors are more closely linked to the "how" of the implementation, than
> methods.

Interesting.

I've just done some reading and that's certainly the position the Java
world takes.

I'm not sure I necessarily agree, but I see where they're coming from ;-)

--
Adam
Inspiring
February 28, 2009
> In the context of CF, there is no concept of constructors, so
> init() is just a method.

With regard to CF components, yes.

> Because one might want to ensure the class provides a constructor
> taking a specific combination of arguments.

Yes.. but to me that is more a case for extends versus implements. An interface does not care _how_ a class implements something, only that it does. Constructors are more closely linked to the "how" of the implementation, than methods.

Having said that, it is possible I just have too much java on the brain ;-) With many of the CF features being modeled after java, it often makes good sense to apply java concepts to CF code as well. Though not always. In some areas CF is more flexible, and it would be silly not take advantage of that.

Technically, there is nothing syntactically wrong with what you suggested. So perhaps I am just rejecting the break with java tradition here. I will have to give it some more thought :-)