• Post Reply Bookmark Topic Watch Topic
  • New Topic

Type Safety and Getting Around Multiple Inheritance  RSS feed

 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I have a problem where I have two interfaces, In and Out. A lot of classes are going to implement In, and a lot of classes are going to implement Out. However, a lot of classes are also going to implement both In and Out. Everything that implements In will have a method like this:

Likewise, everything that implements Out will have one like this:

Of course, anything that implements both In and Out will have both those methods. They will always be implemented the same way, as simple setters. If I could inherit from multiple superclasses, I could make In and Out classes, instead of interfaces, and define both those setters once. However, Java doesn't have multiple class inheritance. With the new default methods in Java 8 interfaces, I could define those methods, but they refer to class properties (as setters tend to do), and Java 8's interfaces can't have class properties.

So... I have consolidated my interfaces into a single abstract parent class, with definitions for the two setters, and I subclass from that in all cases. The subclasses that really just want to implement the original In interface, ignore the abstract parent class's setOutName method, and the subclasses that really just want to implement the original Out interface, override the abstract parent class's setInName method, while the subclasses that want to implement both interfaces still have both methods available.

This all works fine, but it stops me from checking at runtime which instances are Ins, which are Outs, and which are both Ins and Outs. I'm tempted to use a tagging interface for this, but Horstmann says tagging interfaces are a no-no.

When I have two interfaces that will each be implemented alone, but will also be implemented together, and each implementation has a lot of code in common, how do I enforce type safety when everything is subclassed from the same parent?
 
Campbell Ritchie
Marshal
Posts: 55772
163
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
If you have a superclass which completely implements both those methods, and a subclass which ignores one method, can you legitimately say that subclass object “IS‑A” superclass object?
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Campbell Ritchie wrote:If you have a superclass which completely implements both those methods, and a subclass which ignores one method, can you legitimately say that subclass object “IS‑A” superclass object?

Yes, I think you can, because "ignores" just means that you want the subclass instance to be used by instances of other classes that never call the ignored method. If they did call it, it would still work, but it wouldn't do anything very useful. The goal here is to be able to pass references that permit calls to the In methods to those things that need the In methods, while preventing those same things from calling the Out methods, and to be able to pass references that permit calls to the Out methods to those things that need the Out methods, while preventing those same things from calling the In methods, to be able to pass references that permit calls to both the In and Out methods to those things that need both the In and Out methods, without having to duplicate the methods that In and Out have in common.

Suppose the In interface has methods m1 and mX.

Suppose the Out interface has methods m2 and mX.

A class that implements both the In and Out interfaces must provide implementations of m1, m2, and mX. In every case, those implementations will be the same code (imagine they are all setters). That seems to call for an abstract superclass. But, that effectively merges the two interfaces, as all three methods would then be incorporated into that superclass, and, therefore, into any subclasses of that superclass. I need a way to pass a reference to an instance of a subclass of that abstract superclass that, when it must serve as an In object, only permits calls to the In methods, and a way to pass a reference to an instance of a subclass of that abstract superclass that, when it must serve as an Out object, only permits calls to the Out methods, and a way to pass a reference to an instance of a subclass of that abstract superclass that, when it must serve as both and In and an Out object, permits calls to both the In and Out methods.

This might appear to be in violation of LSP, but I think it's not. First, the superclass methods are all still there, so, even if some instance were passed an In object, as a subclass of the merged superclass, that In object would still have all of the superclass methods. But, more importantly, my purpose is never to pass an In reference to anything that needs any Out methods, and never to pass an Out reference to anything that needs any In methods, and to pass references to instances that implement both In and Out only to things that need both In methods and Out methods.

By using a tagging interface in the subclasses, other methods could check the type of the subclass, to make sure they hadn't improperly been passed an In when they expected an Out (and vice versa). That's the whole point: this is to detect inadvertent misuse of an In where you need an Out, or an Out where you need an In, while still allowing some objects to legitimately be both Ins and Outs.
 
Mike. J. Thompson
Bartender
Posts: 689
17
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stevens Miller wrote:...my purpose is never to pass an In reference to anything that needs any Out methods


If something only needs methods from the Out interface then it should only accept Out references. If it has an Out reference then it cannot call any In methods because they aren't visible, even if the runtime type of the object that is being referenced also implements the In interface.

Stevens Miller wrote:and never to pass an Out reference to anything that needs any In methods


And consequently, if something only needs the In methods then it should accept an In reference and it will only have In methods available to it, no matter what methods the runtime type of the reference has available.

Stevens Miller wrote:and to pass references to instances that implement both In and Out only to things that need both In methods and Out methods..


Then you should have another interface called InOut (or whatever makes sense) that extends both In and Out. Things that require both In and Out methods should accept an InOut reference.

I'm not quite sure what problem it is you're solving here though, so I don't know if there is a better way or not.
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Mike. J. Thompson wrote:I'm not quite sure what problem it is you're solving here though, so I don't know if there is a better way or not.


In short, I want to be able to do this:



Now, with default methods, I could change In and Out from classes to interfaces, but those interfaces can't have instance variables, so my illegal multiple inheritance becomes legal, but my interfaces attempt to modify what have to be final values:



I can combine my methods into a superclass, but then my subclasses have both methods, even in the cases where some only want one:



This works, but now all my In instances are also InOut instances, and all my Out instances are also InOut instances. I want holders of references to In instances to have access to the method originally defined in my In interface, but not have access to the method originally defined in my Out interface (and vice versa). With nasty old C++'s multiple inheritance, I can do it. In Java, I need something else. Is composition a possibility? Maybe something like this:



I think that works too, and prevents Out instances from being treated like In instances (and vice versa), while still allowing for instances that can be both (the InOut class), and uses delegation to "fake" multiple inheritance. I can now subclass as many variations of In, Out, and InOut as I want to, which was my goal all along. It just feels like I'm missing a better way (and/or missing flaws in the last approach). For one thing, InOut no longer IS-A In, and it no longer IS-A Out, which it ought to be. Any thoughts?

I guess the generalized form of my question is this: is there any accepted way of doing in Java what C++ programmers do with multiple inheritance and, if so, what is it?
 
Jason Bullers
Ranch Hand
Posts: 115
11
Clojure IntelliJ IDE Java
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I agree with Mike: having an In, Out, and InOut interface makes sense if you need to define methods that accept an object that you can call In methods on, Out methods on, and both In and Out methods on.

It sounds like beyond that, you're looking for a way to have code reuse (implementation inheritance). The additions to interfaces in Java 8 do give you multiple inheritance, but not implementation inheritance. Since you can't extend multiple base classes, the only way I can think of to deal with this is through delegation. Create your base classes that implement In and Out, and then in all other classes that implement those interfaces, contain an instance of that base class and delegate to it. Similar with the InOut interface: implementers of this interface would contain an instance of each base class and delegate to them as appropriate (or you have a third base class if you can't keep those separate).
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Jason Bullers wrote:Create your base classes that implement In and Out, and then in all other classes that implement those interfaces, contain an instance of that base class and delegate to it.


That works, but it doesn't help me with type safety. The delegating classes don't yield true for delegatingInClassInstance instanceof In, or false for delegatingInClassInstance instanceof Out.

Here's another idea I just tried, that does seem to work:


AbstractInOut centralizes the implementations of all of the methods in both interfaces, but doesn't explicitly say so. Thus, its declaration doesn't have implements In, Out appended to it, even though it does implement the methods declared in those interfaces. The classes that extend AbstractInOut declare themselves to be implementations of the interfaces they want holders of references to them to be able to use. If they are passed to those holders as instances of the interfaces they permit, those holders can't get access to the other methods in their superclass without an ugly cast.

I think this solves my problem, but it decouples the actual implementation of the interfaces from the declarations, which seems risky to me. I can't imagine I'm the first Java programmer who wants to do this, so I'm looking for guidance on existing practices.
 
Stephan van Hulst
Saloon Keeper
Posts: 7817
142
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
You shouldn't give classes functionality if they shouldn't have that functionality. That seems obvious, but that's what you're circumventing with your abstract class that fulfills both roles. Instead, do it like this:




 
Stephan van Hulst
Saloon Keeper
Posts: 7817
142
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
 
Mike. J. Thompson
Bartender
Posts: 689
17
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stevens Miller wrote:
Jason Bullers wrote:Create your base classes that implement In and Out, and then in all other classes that implement those interfaces, contain an instance of that base class and delegate to it.


That works, but it doesn't help me with type safety. The delegating classes don't yield true for delegatingInClassInstance instanceof In, or false for delegatingInClassInstance instanceof Out.


I think you missed the part of Jason's post where it says that the delegating types implement either In or Out, so they will in fact return true when used with the instanceof.

Using composition instead of inheritance (as you proposed above) was going to be my next suggestion by the way. Inheritance is often overused in my opinion.

Stevens Miller wrote:Here's another idea I just tried, that does seem to work:

...

The classes that extend AbstractInOut declare themselves to be implementations of the interfaces they want holders of references to them to be able to use. If they are passed to those holders as instances of the interfaces they permit, those holders can't get access to the other methods in their superclass without an ugly cast.


This is similar to what I suggested above, and if the users or these instances are programmed to expect the interface types rather than the concrete types (which is almost always the best way to do things) then as you say it will not be possible for the code using that reference to access any methods other than those in the interface without making a cast.

However the code you suggested does mean that JustAnIn instances contain data meant for an Out and vice versa. That is a little confusing, and if other developers are maintaining this code it would be easy for them to introduce bugs. This is especially true if it is important that JustAnIn never makes use of the Out methods etc.

Stevens Miller wrote:I think this solves my problem, but it decouples the actual implementation of the interfaces from the declarations, which seems risky to me. I can't imagine I'm the first Java programmer who wants to do this, so I'm looking for guidance on existing practices.


I'm not sure what you mean here. What declaration are you saying is being decoupled from?


One final thing I would say here is that if the only code that is being shared is a specific field declaration and the setter for that field then this solution may well be more complexity than it's worth introducing. It may be better simply to duplicate the implementation of the setter in each implementation since it is so simple.
 
Mike. J. Thompson
Bartender
Posts: 689
17
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stephan's post above is exactly what I'd attempted to describe originally, but it's much clearer with the code explaining it.

From the information you've posted I think that is the best solution.
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Whoa, I was wrong about this:

Stevens Miller wrote:


The IDE doesn't flag the error, but, at run-time, it throws a ClassCastException. Which is actually better than if the cast had succeeded, as it means the holder of a reference to a JustAnIn cannot gain access to the Out methods, even though the reference is to an instance of a subclass of AbstractInOut, which includes the Out methods.

Anyone see a problem with this approach?
 
Stephan van Hulst
Saloon Keeper
Posts: 7817
142
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Yes, you can still get access to the setOutData method by casting to the abstract base type, which is undesirable if the type is only supposed to represent an In.
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Mike. J. Thompson wrote:I think you missed the part of Jason's post where it says that the delegating types implement either In or Out, so they will in fact return true when used with the instanceof.

Yeah, I did miss that. Thanks.

Stevens Miller wrote:I think this solves my problem, but it decouples the actual implementation of the interfaces from the declarations, which seems risky to me. I can't imagine I'm the first Java programmer who wants to do this, so I'm looking for guidance on existing practices.
Mike J. Thompson wrote:I'm not sure what you mean here. What declaration are you saying is being decoupled from?

The declarations in the In and Out interfaces. What I mean is that, in AbstractInOut, because it doesn't expicitly say, "implements In, Out," my IDE can't flag missing implementations for me (nor can I use the @Override annotation).

Mike J. Thompson wrote:One final thing I would say here is that if the only code that is being shared is a specific field declaration and the setter for that field then this solution may well be more complexity than it's worth introducing. It may be better simply to duplicate the implementation of the setter in each implementation since it is so simple.

Oh, absolutely! The toy examples I'm posting here are as small as I can make them. What I actually have are a moderately large set of classes, all of which were implementing quite a few methods from one, the other, or both of two interfaces. In a single-interface situation, that's a clear indication that a superclass should contain all those duplicated methods. But, as some of my subclasses were Ins, and some were Outs, and some were both, I either had to have three superclasses, with a lot methods duplicated in each, or else have all my classes subclassed from one superclass, even though not every subclass needs all the methods in the superclass. And thus, my original post.

The ideas offered here are excellent. I'll study them and see if I can improve on my approach. Thanks, folks!
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stephan van Hulst wrote:Yes, you can still get access to the setOutData method by casting to the abstract base type, which is undesirable if the type is only supposed to represent an In.


Good point.

I think moving the interfaces and the abstract class to another package, making the interfaces public, and keeping the abstract class package-private copes with that:

 
Stephan van Hulst
Saloon Keeper
Posts: 7817
142
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
It doesn't really matter if you use visibility tricks, the whole idea of having something called "JustSomething" extend "SomethingAndSomethingElse" is exactly what causes black voodoo code. Don't do it.
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stephan van Hulst wrote:


I like your approach, Stephan, because it returns the abstract class to implementing both interfaces explicitly (restoring the lost coupling I complained of). However, your MyInOut class creates two objects, an In and an Out. My objects come in three categories: some are purely Ins, some are purely Outs, and some are both Ins and Outs. In the last case, I mean a single instance is both and In and an Out. I don't think having an In and a separate Out will meet my needs, because operations on one that change its state will not be reflected by a similar state change in the other, unless I do everything to them in tandem.
 
Stephan van Hulst
Saloon Keeper
Posts: 7817
142
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Like what? Ins are Ins, Outs are Outs. Why do they have overlapping behavior? If they do, then why isn't this behavior documented in an even higher interface?


The fact that AbstractInOut internally uses an In and an Out object to implement its interface is an implementation detail, and any tricks you have to apply to make it jive with the X interface is not reflected in your API (even for classes in the same package). You shouldn't adapt a conceptually pure API just to make the implementation easier.
 
Mike. J. Thompson
Bartender
Posts: 689
17
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
The MyInOut class controls the implementation of the operations that are performed on it. Don't view it as two separate objects, that is just an implementation detail. It is a single object that can be used wherever an In is required, or wherever an Out is required, or wherever an InOut is required.

Are you saying that the In and Out instances aren't really independent of each other and need to know each others internal state? If so then its probably time to reassess the design.
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stephan van Hulst wrote:It doesn't really matter if you use visibility tricks, the whole idea of having something called "JustSomething" extend "SomethingAndSomethingElse" is exactly what causes black voodoo code. Don't do it.


Those names would be terrible in practice. I'm using them here for illustrative purposes.

What I'm actually working on is an implementation of the pipes-and-filters pattern. I have three classes: Source, Filter, and Sink. Every Source can generate data, and must have a Sink to send it to. Every Sink must have a Source, from which it will get data. No Source has a Source and no Sink has a Sink. However, a Filter is both a Source and a Sink, and must have both a Source and a Sink. Thus, a Source can "feed" a Sink, or it can "feed" a Filter (since a Filter is a Sink) which can then "feed" a Sink (since a Filter is also a Source).

All Sources must have a sink member variable, with matching getter and setter.

All Sinks must have a source member variable, with matching getter and setter.

All Filters, because they are Sources and Sinks, must have both a sink member variable and a source member variable, with appropriate getters and setters.

I started seeing all those getters and setters being duplicated in all my subclasses of Source, Sink, and Filter, so I moved them into superclasses (along with some other code that was showing up in every subclass). Even in those three superclasses, I saw a lot of identical methods and variables. So, I wanted to consolidate all that, too.

But... No Source has a source, so getSource isn't something you want it to inherit from any superclass. Yet, all Filters need that method, so they have to get it from somewhere. No Sink has a sink, so getSink isn't something you want it to inherit from any superclass. Yet, all Filters need that method too. Since Sources, Sinks, and Filters do have some methods in common, but also some they don't, I couldn't see any way to use a class hierarchy from which I could derive all three, without having some functionality that isn't used by all three in one of their superclasses, or else duplicating some of that fuctionality in superclasses that would not include the functionality their subclasses don't need.

At the risk of overkill, this diagram illustrates my universe of methods that are used by Sources, Filters, and Sinks:



The scheme I need is one that lets me share the appropriate methods with the appropriate subclasses, with as little duplication as possible, while retaining type safety (that is, no Sink should be allowed where a Source is necessary, and vice versa).
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Mike. J. Thompson wrote:Are you saying that the In and Out instances aren't really independent of each other and need to know each others internal state? If so then its probably time to reassess the design.


Hoo boy, that would be a mess. No, I am saying that some objects are Ins, some are Outs, and some are both Ins and Outs, and that only those that are both Ins and Outs will have all the methods Ins have and all the methods Outs have, and that I want the appropriate functionality inherited by each without duplicating any code. In other words, I (think) I want exactly what C++'s multiple inheritance provides, and I'm wondering what Java programmers do to get it.
 
Stephan van Hulst
Saloon Keeper
Posts: 7817
142
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Your design is conceptually flawed.

You say Sources can't have Sources and Sinks can't have Sinks, but Sources must have Sinks and Sinks must have Sources. Therefore logically, Sources can't be Sinks and Sinks can't be Sources. They can never be implemented by the same class. There is simply no way around this.

I find it questionable that Sources have Sinks (and vice versa). A source is a source and a sink is a sink. They don't have anything to do with one another. You retrieve data from a source and you put it in a sink. These types knowing of each other's existence sounds like the setup is too tightly coupled.
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stephan van Hulst wrote:The fact that AbstractInOut internally uses an In and an Out object to implement its interface is an implementation detail

I'm really not following you there, Stephan. They have to be the same instance, because I have objects that are going to be called on to implement both interfaces. Having two objects isn't an option, because the behavior of an object when accessed via the Out interface may depend on state that was set previously when it was accessed via the In interface.
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stephan van Hulst wrote:Your design is conceptually flawed.

You say Sources can't have Sources and Sinks can't have Sinks, but Sources must have Sinks and Sinks must have Sources. Therefore logically, Sources can't be Sinks and Sinks can't be Sources.


Yeah, that's how it looked when I started.

They can never be implemented by the same class. There is simply no way around this.


Well, the trick I'm using is working pretty well.

I find it questionable that Sources have Sinks (and vice versa). A source is a source and a sink is a sink. They don't have anything to do with one another. You retrieve data from a source and you put it in a sink. These types knowing of each other's existence sounds like the setup is too tightly coupled.


It's as tightly coupled as I can make it, because I'm working on real-time video processing. (If you are familiar with Microsoft's DirectShow filters-and-graphs system, I'm using a somewhat similar approach. It is notorious for making programmers crazy, but that's because it is the result of years of refinement, ending in a very complex approach to a problem that it solves magnificently well, if your measure of magnificence is speed and, when you are processing video in real time, I can assure you that speed is a very important magnificence measure.)
 
Mike. J. Thompson
Bartender
Posts: 689
17
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stevens Miller wrote:
Stephan van Hulst wrote:The fact that AbstractInOut internally uses an In and an Out object to implement its interface is an implementation detail

I'm really not following you there, Stephan. They have to be the same instance, because I have objects that are going to be called on to implement both interfaces. Having two objects isn't an option, because the behavior of an object when accessed via the Out interface may depend on state that was set previously when it was accessed via the In interface.


The MyInOut IS-A In and it IS-A Out. It is the MyInOut instance that is passed around when you have an object that needs to satisfy both the In and the Out interfaces, not the internal In and Out objects (which are just implementation details that it uses to satisfy its obligations under those interfaces, by delegating to them).
 
Mike. J. Thompson
Bartender
Posts: 689
17
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stevens Miller wrote:
Stephan van Hulst wrote:
What I'm actually working on is an implementation of the pipes-and-filters pattern. I have three classes: Source, Filter, and Sink. Every Source can generate data, and must have a Sink to send it to. Every Sink must have a Source, from which it will get data.


This doesn't make sense to me, it sounds like the Source and the Sink do not fit together properly because their interfaces are in direct contradiction to each other.

1) The Source has-a Sink and sends data to it. This implies that the Source is in charge of the interaction, and the Sink is passive.

2) The Sink has-a Source and gets data from it. This implies that the Sink is in charge, and the Source is passive.

Obviously both of these situations can't be true, so I don't see why a Source has a Sink and a Sink has a Source. Only one of them needs to know about the other.

There is another option (that Stephan alluded to earlier). You could implement it such that the Source and Sink are not aware of each other at all and both act passively. This would require a driving class that reads from the Source, passes to each filter, and then passes to the Sink at the end.
 
chris webster
Bartender
Posts: 2407
36
Linux Oracle Postgres Database Python Scala
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Is this for a real application, or is it an intellectual exploration?

If it's for a real app, you might want to look at Akka Streams which implements the kind of Source/Sink stuff you seem to be looking at here. Akka is written in Scala which allows you to mix in behaviour via traits, but there's a Java version to wrap all that Scala voodoo in old-world OO comfort if you need it...
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Mike. J. ThompsonThe [tt wrote:MyInOut[/tt] IS-A In and it IS-A Out. It is the MyInOut instance that is passed around when you have an object that needs to satisfy both the In and the Out interfaces, not the internal In and Out objects (which are just implementation details that it uses to satisfy its obligations under those interfaces, by delegating to them).


Suppose any of these could, say, have a name that it stores. Where is the code for the name setter? Since anything that implements In or Out has to be able to do this, it would be in their common superclass, no? But an InOut filter also has to do this. If it implements the behavior of In and Out by delegating that to two other objects, which one does it use for setting the name? Both? I feel like I'm really missing something basic here, but I just don't see how one filter can be implemented with two objects when the filter has to have its own state, and implement methods that are also implemented by those two objects.
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Mike. J. Thompson wrote:
This doesn't make sense to me, it sounds like the Source and the Sink do not fit together properly because their interfaces are in direct contradiction to each other.

1) The Source has-a Sink and sends data to it. This implies that the Source is in charge of the interaction, and the Sink is passive.

2) The Sink has-a Source and gets data from it. This implies that the Sink is in charge, and the Source is passive.


It can go either way. In the "push" model, the Source calls the pushData method of the Sink. In the "pull" model, the Source waits to have its getData method called by its Sink. In either case, this all happens on a thread started by the Source, one that is created by the Source's start method. The code that connects Sources, Filters, and Sinks neither knows nor cares which way it will work (although the method that actually connects a Source to a Sink will check to be sure they are compatible, and throws an exception if they're not). There is also code that disconnects them, and each class must support a stop method as well (for Sources, this one interrupts the thread it started; for all classes, it means calling the stop method of the next downstream object in this doubly-linked list; for "pull" Sinks that are at the end of the list, it means end the process of calling upstream for more data).

Obviously both of these situations can't be true, so I don't see why a Source has a Sink and a Sink has a Source. Only one of them needs to know about the other.


It helps avoid differences at higher levels that would otherwise show up to accommodate some objects being "push" model objects, and others being "pull" model objects. Also, keeping it doubly linked protects against something being connected to something else when it is already connected, and against starting the data-retrieval thread until everything that will be running on it is connected to something else. No, not every Source, Filter, or Sink needs to know about the others, but it makes handling the process of connecting/disconnecting them, and starting/stopping them, much simpler.

In some cases, the "push" and "pull" models can even be mixed. For example, a "pull" Source can be feeding a "pull" Sink that is also a Filter whose Source side is a "push" model Source, like this:

[Source] <- [Filter] -> [Sink]

Data always flows from left to right, but the arrows show you which object is calling which object's methods. In this example, the Filter calls the Source and "pulls" data in from it. The Filter then calls the Sink and "pushes" data into it. In a more complex situation, requiring a multithreaded Filter, it can look like this:

[Source] -> [Filter] <- [Sink]

Here, the Source calls the Filter and "pushes" data into it while, on another thread, the Sink calls the Filter and "pulls" data out of it. The Filter has to handle some synchronization and do some other work to avoid passing a reference to something that changes before the Sink gets it, but that's a completely different problem than we are discussing.

The point is that all the code that handles these Source, Filter, and Sink instances for connection/disconnection, starting/stopping, doesn't care at all how big the linked list gets, or which way the calls are made. You can have as complicated a set-up as you want:

[Source] -> [Filter] -> [Filter] <- [Filter] -> [Sink]

and it all just works with the same calls to build it, start it, stop it, and take it apart.

There is another option (that Stephan alluded to earlier). You could implement it such that the Source and Sink are not aware of each other at all and both act passively. This would require a driving class that reads from the Source, passes to each filter, and then passes to the Sink at the end.


You can, but that driving class has to keep that same list somewhere, to know which instance to pass the data to next, and then get it out of, and pass it to after that. Much easier to have the list manage itself, than to have a driving class build a descriptive structure and constantly have to read from it to see what to do next (and multi-threading that would be a drag, whereas individual filters can multi-thread themselves, if that's what the programmer who writes them wants to do.)

Like I said, this is all based on something notoriously confusing to applications programmers. I have no shame in admitting it took me months of working with it to feel I understood it well enough to use it. Now, since it all works in C++, I've written just enough DirectShow code to get video out of a Webcam, and used the JNI to get the video data over to Java. What I need Java to do is somewhat similar to what I could do with DirectShow, but I have simplified it down to just what I need, retaining all the flexibility/extensibility, while (I hope) getting rid of most of the stuff that has been known to drive DirectShow programmers mad.
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
chris webster wrote:Is this for a real application, or is it an intellectual exploration?


Why choose?
 
chris webster
Bartender
Posts: 2407
36
Linux Oracle Postgres Database Python Scala
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
So... you're aiming to implement Reactive Streams (like Akka Streams), but you're using Java instead of Scala so you can't use traits for mix-ins. Good to have a hobby, I guess - have fun!
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
chris webster wrote:So... you're aiming to implement Reactive Streams (like Akka Streams), but you're using Java instead of Scala so you can't use traits for mix-ins. Good to have a hobby, I guess - have fun!


Well, I don't know what a trait or a mix-in is, and I also don't know if learning another programming language is the best way for me to get this done, but thanks. I looked at the page you linked to. It's a bit of a word-salad, but the Javadoc they further link to has some similarities to what I'm doing. Its overall spec appears to be way more than I need (it requires asynchrony at every Filter--they call them Processors--which introduces speed issues I don't need). They also want to avoid buffering without, apparently, any data loss. Good luck with that. In real-time video processing, this is usually done by just dropping a frame of video if the downstream Filters/Processors can't keep up. Overall, their base structure (Processors extend both Sources/Publishers and Sinks/Subscribers) matches what I am doing. So, if their approach works for them, it's nice to know I'm on the right track. Thanks!
 
chris webster
Bartender
Posts: 2407
36
Linux Oracle Postgres Database Python Scala
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Sorry - maybe I was a bit cryptic there. Scala traits are like Java interfaces but with the option to provide implementations of the methods, so you can "mix in" known behaviour from multiple traits without actually going down the road of multiple OO inheritance. This sounds like the kind of thing you were trying to achieve above. Traits are heavily used in Scala to create many of the flexible, powerful and type-safe built-in libraries. Of course you can do mix-ins in Ruby too, but that's a rather more "adventurous" choice!

Akka Streams - yes, the docs can be a bit lumpy, but I think it's worth looking at if you're into streaming, as is the whole Reactive Manifesto/Reactive Streams bandwagon. There's the usual messianic hype you get with any new buzzword, but the basic ideas seem sound and my limited experience of playing around with Akka Streams is very positive so far. It's a nice high-level abstraction that takes a lot of the pain out of coding this stuff.

If you're curious, there's a nice overview of Akka Streams in the video I linked from this post. The first 10 minutes are a bit rambling, but the rest is good stuff. YMMV of course.

Anyway, no wish to distract you from your hard-core engineering fun.

Good luck,
Chris
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stephan van Hulst wrote:Ins are Ins, Outs are Outs. Why do they have overlapping behavior? If they do, then why isn't this behavior documented in an even higher interface?


I've been working on this question for several hours today, and finally got what you meant by it. Of course, the common behavior of the two can and should be merged into a superclass (the DRY principle). Don't know how I was blind to that for so long. Once that's done, I can, just as you suggested, implement a Filter by having it own a separate Source and Sink.

Much better than what I was doing.

Thanks!
 
Stephan van Hulst
Saloon Keeper
Posts: 7817
142
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I'm glad it helped

Interestingly, the same pattern is used by the collections framework. Take LinkedList for example. It's a concrete type that implements both Queue and List. Queues and lists have overlapping behavior, but all of that is documented by Collection (and Iterable). LinkedList avoids code-duplication by extending AbstractSequentialList, and so AbstractList and AbstractCollection as well.

I have to admit I stopped following the discussion when you dropped DirectShow. Not my area
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stephan van Hulst wrote:I have to admit I stopped following the discussion when you dropped DirectShow. Not my area


You have no idea how fortunate you are (but at least I didn't mention Scala ).
 
Stephan van Hulst
Saloon Keeper
Posts: 7817
142
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I'm actually more familiar with Scala/Akka.
 
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!