4

I'd like to write Java code (say, a method) that will print some lines.

The object onto which to print shall be provided by the caller. I'd like my code to not care what exactly that object is and simply call that objects' println() or println(String) methods. It should work whether that object is a java.io.PrintStream (e.g. System.out) or a java.io.PrintWriter (e.g. constructed by the caller with new PrintWriter(System.out) or new PrintWriter(new ByteArrayOutputStream())).

This would be easy if the potential classes of a "printlineable" object would share some interface that mandated the println() and println(String) methods. However they don't.

So what do I put into the signature to receive such an object without violating the DRY principle by writing twice what is essentially the same implementation, just with swapped out types (as I would have to when simply overloading the function)?

public void sayHello( ??? outThingy) {
    outThingy.println("Hello World");
    outThingy.println();
    // This just a minimal example.
    // The real implementation might be more involved
    // and non-trivial, so that avoiding duplication
    // becomes a real concern.
};


// sayHello should be usable like this:

sayHello(System.out);


// but also like this:

baos = new ByteArrayOutputStream();
pw = new PrintWriter(baos)

sayHello(pw);

pw.flush();
System.out.println(baos.toString());

Or should the fact that PrintStream and PrintWriter don't share such an interface be treated as indication that they aren't interchangeable in the regard of providing a way to print lines? (Rather than that being some kind of historical oversight back when these classes were specified.)

das-g
  • 9,718
  • 4
  • 38
  • 80

3 Answers3

2

The easiest way would just to overload the method with a version that accepts a PrintWriter and a version that accepts a PrintStream:

public void sayHello(PrintStream outThingy) {
    outThingy.println("Hello World");
    outThingy.println();
};
public void sayHello(PrintWriter outThingy) {
    outThingy.println("Hello World");
    outThingy.println();
};
GBlodgett
  • 12,704
  • 4
  • 31
  • 45
  • Is there a way without violating [the DRY principle](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)? With the short example code I used for the question, that isn't an issue, but my real function is more involved. – das-g Jul 02 '19 at 15:42
  • @das-g Yes: `void sayHello(PrintStream out) { PrintWriter pw = new PrintWriter(out); sayHello(pw); pw.flush(); }`, then implement the `void sayHello(PrintWriter out)` method. – Andreas Jul 02 '19 at 16:05
  • Heh, my current code (that I'd like to refactor) is actually somewhat like this, @Andreas: It currently accepts an OutputStream and always wraps it in a PrintWriter. – das-g Jul 02 '19 at 16:22
  • The motivation for my question was that I was wondering whether my code should use a PrintStream for the wrapping instead. (And maybe not wrap the object at all, if it already is a PrintStream?) My conclusion was that it should better be the caller's responsibility to decide that. Thus I'd like to refactor my code in a way so that the caller already provides a generic "thing" that my code can `println()` on without any wrapping, but maybe that's not possible, without imposing new interfaces upon the caller, that current Java standard library classes don't implement. – das-g Jul 02 '19 at 16:24
2

Here's a way you could do it and at least keep the client of the outThingy DRY. But, you'll make a trade off for effectively having a couple WET classes. Still, the amount of code is minimal.

// Printer allows for a common interface
interface Printer {
  void println(String line);
  void println();
}

// Used with PrintStream
class StreamPrinter implements Printer {
  private PrintStream ps;

  public StreamPrinter(PrintStream ps) {
    this.ps = ps;
  }

  @Override
  public void println(String line) {
    ps.println(line);
  }

  @Override
  public void println() {
    ps.println();
  }
}

// Used with PrintWriter
class TypeWriter implements Printer {
  private PrintWriter pw;

  public TypeWriter(PrintWriter pw) {
    this.pw = pw;
  }

  @Override
  public void println(String line) {
    pw.println(line);
  }

  @Override
  public void println() {
    pw.println();
  }
}

class Talker {
  // This class doesn't care!
  void sayHello(Printer printer) {
    printer.println("Hello world");
    printer.println();
  }
}
ChiefTwoPencils
  • 13,548
  • 8
  • 49
  • 75
1

You might be interested in a different, more functional approach. Instead of worrying about what each type offers and how to find a common interface between them, you can achieve the same thing with less code by using a Consumer and a Runnable as representations of the println methods.

// This is the common class
class FuncPrinter {
  private Consumer<String> consumer;
  private Runnable runnable;

  public FuncPrinter(PrintWriter writer) {
    consumer = writer::println;
    runnable = writer::println;
  }

  public FuncPrinter(PrintStream stream) {
    consumer = stream::println;
    runnable = stream::println;
  }

  public void println(String line) {
    consumer.accept(line);
  }

  public void println() {
    runnable.run();
  }
}

class Talker {    
  void sayHello(FuncPrinter fp) {
    fp.println("Hello World");
    fp.println();
  }
}

And you could use it like so:

Talker t = new Talker();

FuncPrinter fp = new FuncPrinter(System.out);
t.sayHello(fp);

ByteArrayOutputStream ostream = new ByteArrayOutputStream();
PrintWriter pw = new PrintWriter(ostream);
fp = new FuncPrinter(pw);
t.sayHello(fp);

fp = new FuncPrinter(
  line -> System.out.println(line),
  () -> System.out.println(42));
t.sayHello(fp);
ChiefTwoPencils
  • 13,548
  • 8
  • 49
  • 75