[concurrency-interest] Adding interfaces Sink and Source?

Joshua Bloch josh@bloch.us
Sun, 3 Oct 2004 10:18:13 -0700


Folks,

Vaguely related to this, here's a cool pattern for use when you want
to do several forwarding wrappers of the same interface.  I learned
about this pattern from Neal, after I wrote my book.  Had I known
about it earlier, it would probably be in the book.  The basic idea is
to make a helper wrapper class that does nothing but forwarding:

public class ForwardingQueue<E> extends Queue<E> {
    private final Queue<E> queue;

    ForwardingQueue(Queue<E> queue) { this.queue = queue; }

    boolean offer(E o) { return queue.offer(o);  }
    E poll()           { return queue.poll();    }
    E remove();        { return queue.remove();  }
    E peek();          { return queue.peek();    }
    E element();       { return queue.element(); }

    ... // add Collections methods or (much better) extend forwardingCollection
}

Then you can make specialized wrappers by overriding only the methods
where you want to do something other than vanilla forwarding, e.g.:

public class Collections {
    public static <E> Queue<E> putOnlyQueue(Queue<? super E> queue) {
        return new ForwardingQueue<E>() {
            E poll();    { throw new UnsupportedOperation(); }
            E remove();  { throw new UnsupportedOperation(); }
            E peek();    { throw new UnsupportedOperation(); }
            E element(); { throw new UnsupportedOperation(); }
            ... // Eliminate some collections ops also
        };
    }

    pubic static <E> Queue<E> takeOnlyQueue(Queue<? extends E> queue) {
        return new ForwardingQueue<E>() {
            boolean offer(E o) { throw new UnsupportedOperation(); }
            ... // Eliminate some collections ops also
        };
    }

    ...
}

You get the idea.  I have never actually used this pattern in
practice, but that's only because I learned about it too late.  I
think it's a good idea.

            Josh

On Sat, 02 Oct 2004 21:38:10 -0400, Tim Peierls <tim@peierls.net> wrote:
> Doug Lea wrote:
> > The way we left it, it is still possible for individual developers
> > to unify usages themselves by adding a bit of glue. For example:
> >
> > interface Puttable<E> { void put(E x) throws Exception; }
> > class MyLinkedQueue<E> extends LinkedQueue<E> implements Puttable<E> {
> >   ...
> > }
> 
> Expanding on this a bit, it might be convenient to use wrapper factories
> that convert BlockingQueues to custom Puttable and Takable interfaces.
> 
> class QUtil {
>   static Puttable<E> asPuttable(final BlockingQueue<E> q) {
>     return new Puttable<E>() {
>       public void put(E e) throws InterruptedException { q.put(e); }
>     }
>   }
>   static Takable<E> asTakable(final BlockingQueue<E> q) {
>     return new Takable<E>() {
>       public E take() throws InterruptedException { return q.take(); }
>     }
>   }
> 
> But if the only reason you miss Puttable and Takable is the way that they
> prevented consumers from accidentally or maliciously accessing producer
> methods and vice versa, then a simple way to achieve the same effect without
> introducing new interfaces is via a factory that creates wrapped blocking
> queues that throw UOE on all but a subset of the queue methods:
> 
> class QUtil {
>   static BlockingQueue<E> putOnlyBlockingQueue(final BlockingQueue<E> q) {
>     return new BlockingQueue<E>() {
>       public void put(E e) throws InterruptedException { q.put(e); }
>       // similarly for all "put"-like methods that you want to support
> 
>       public E take() { throw new UnsupportedOperationException(); }
>       // similarly for all "take"-like methods and anything else you
>       // don't want to support
>     };
>   }
> 
>   static BlockingQueue<E> takeOnlyBlockingQueue(final BlockingQueue<E> q) {
>     return new BlockingQueue<E>() {
>       public E take() throws InterruptedException { return q.take(); }
>       // similarly for all "take"-like methods that you want to support
> 
>       public void put(E e) { throw new UnsupportedOperationException(); }
>       // similarly for all "put"-like methods and anything else you
>       // don't want to support
>     };
>   }
> }
> 
> Use this utility to wrap a queue before handing it over to producers and
> consumers. For example,
> 
>   interface Producer {
>     public void produceTo(BlockingQueue<Foo> putOnlyQueue);
>   }
> 
>   interface Consumer {
>     public void consumeFrom(BlockingQueue<Foo> takeOnlyQueue);
>   }
> 
>   void start(Collection<Producer> producers,
>              Collection<Consumer> consumers,
>              BlockingQueue<Foo> fooq) {
> 
>     for (Producer producer : producers)
>       producer.produceTo(QUtil.putOnlyBlockingQueue(fooq));
> 
>     for (Consumer consumer : consumers)
>       consumer.consumeFrom(QUtil.takeOnlyBlockingQueue(fooq));
>   }
> 
> Now malicious or broken producers and consumers can't touch the "wrong"
> end of the queue. This approach trades away compile time type information
> in favor of a smaller set of interfaces and run time checking, roughly
> analogous to the design tradeoff whose resolution in the Collections
> framework resulted in the Collections.unmodifiableXXX static methods.
> 
> --tim
> 
> 
> 
> _______________________________________________
> Concurrency-interest mailing list
> Concurrency-interest@altair.cs.oswego.edu
> http://altair.cs.oswego.edu/mailman/listinfo/concurrency-interest
>