3

I'm writing a Websocket implementation in Java, and I've set it up so that it basically just wraps an InputStream and OutputStream, and has public methods for both sending and receiving. While input and output are related, they're also complete opposites, and share very little functionality. Would it make more sense to have WebsocketInput and WebsocketOutput classes instead?

Java's Socket class really has no methods for sending and receiving, other than providing an InputStream and OutputStream. I'm not aware of any commonly used classes in Java that encapsulate both reading and writing, but it feels wrong to split them up here, since no user would have much of a use for one half of a Websocket. What would be the best approach here?

4 Answers 4

3

In addition to the other very good answers here, one way to think about it is that you are not really improving the level of abstraction (in the direction of a full duplex capability) by applying individual wrappers to the input & output streams.

While it is true that the input & output streams are individually providing their own single responsibility when viewed at their level of abstraction: on the other hand, the bundle of input & output put together forms a higher level of abstraction, the full duplex stream. Having a higher level of abstraction, when we look it it that way: it has its own single responsibility, the pairing of an appropriate input & output stream to form a full duplex com link.

One hint that suggests you are raising the level of abstraction by combining the two together because now you have a single entity that will provide full duplex, and you can pass around to others, and especially whereas by comparison:

If you wrap input & output individually and separately, you would (still) have two abstractions to pass around, and clients would have to concern themselves with (potentially the details of) pairing the two, if not at least keeping them together when passed around, to form the full duplex abstraction. (You could still put a wrapped input & an output together to finally manifest a good full duplex abstraction, but then what's the point of the intermediate wrappers?)

So, while the underlying unidirectional streams each have their own single responsibility, so does a class that bundles them together have its own single responsibility. These are not mutually exclusive: because the latter is at a higher level of abstraction. Taken together these classes are exhibiting layering, which is one way to keep designs focused on their single responsibility. Layering is where one abstraction is implemented entirely by abstractions at the next layer below, without having to go any lower. (Layering is a technique I like to reduce complexity of very large software projects.)

4

If you would like to split it up into several classes, the Java approach is exactly how you would approach it.

  • class Socket
    • method getInputStream returns InputStream
    • method getOutputStream returns OutputStream
    • other methods that apply to the Socket itself and not permitted to be individually controllable by the input aspect or the output aspect alone.

In short:

  • The underlying mechanism is a socket (technically speaking).
    • It is infeasible for you to try to conceal this full-duplex mechanism from your users, because it will not work that way (as pointed out by other answerers).
    • In other words, it is better not to split this mechanism class into two independent classes.
  • However, you are allowed to provide facade or interfaces that each exposes a single aspect (input or output).
    • This satisfies Interface Segregation Principle.

This is exactly what Java does.


The list of alternatives are:

  • One main class, plus two facade classes that can be accessed from the main class (discussed above)
  • One class, two or more interfaces. To call methods on each interface, the user performs an interface cast.
  • One main class only. All methods corresponding to both aspects (input stream and output stream) are piled up onto the main class.

The latter two options do not permit method name conflicts. In other words, aside from being harder for users, it is also harder to give names to methods.

The third option is the least flexible. Code written to use that main class cannot be adapted to, say, a full-duplex communication that is formed by pairing two half-duplex communications.


On the other hand, you may already have an entire design for the communication protocol. In this case, you do not have to expose any lower-level detail (aside from what is necessary to manage connections). Instead you just need to expose the functionalities of your communication protocol. In other words, there is no need to write a general-purpose class if it is not needed.

For example, instead of operating at the byte stream level, you may want to have a class that sends or receives structured, validated messages. If such class exists in your design, such class can be written to use the Socket class directly, thus skipping a layer of abstraction.

2

A full duplex communication line is a single responsibility. Treat it accordingly.

1

You must have a reason to wrap the input and output streams rather than use them directly. You may have a client that needs them both in a pair. Maybe the details of that pair change together.

If this reason is one reason and not two reasons then there would only ever be one reason for the class to change. If you can say that your fine.

This issue isn't about how many forms of communication are dealt with in the class. It's about how many reasons you'd choose this class.

This class may be the wrapper that logs everything sent through it. It may be the one that sends everything to dev/null. It may be the one that sends everything on to where it's supposed to go. Satisfying those 3 reasons should produce 3 classes. Not 6. Not 9.

So yes full duplex communication is a single responsibility. You're following the single responsibility principle if that's what the class provides. Not more. Not less.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.