Both Game
and WorldFactory
class would have FileSystem
as their dependencies. Below is an example of dependency injected through a constructor (it is also possible to inject them directly into fields):
@Inject
public Game(FileSystem fs) { }
@Inject
public WorldFactory(FileSystem fs) { }
You could then write a provider inside one of your modules that would construct and return a new FileSystem
instance:
@Provides
FileSystem provideFileSystem() {
return ServiceLocator.getService(FileSystem.class);
}
Once you treated other services similarly, you will be able to completely remove your reliance on ServiceLocator
, moving the service-creating code directly into providers.
So then you get an odd instance where the constructor of a game entity requires that you pass an instance to the AudioSystem to it which it can play audio through. It just gets very messy with all these objects/services being explicitly passed around.
Not only this is not messy, this is the whole point of dependency injection. The thing is, however, that you should never explicitly pass all those instances manually. Instead, you can have Guice reconstruct the dependency graph from them and construct all necessary, intermediate objects (or obtain them in any other way you specify in your providers). On your side, creating the final object is just a matter of calling Injector.getInstance(Foo.class)
-- it's up to Guice to figure out how to satisify the dependencies.
Just my two cents. The goal of this answer is to give some thoughts and attract more answers, not trying to sound like definitive or even informative.
Why are streams abstract base classes instead of interfaces?
- It is possible to define a strictly minimal interface.
- Typically this consists of: read, write, seek, tell (returns current file position), get length, truncate (reduce file size), close.
- However, to make the stream implementation convenient to use, a lot of helper methods on the class would be needed.
- Without these helper methods, it would make the programming language impractical, because it would then be too troublesome to use streams as a fundamental feature of the standard library.
- These helper methods make use of the minimal interface.
- Typically, default (standard) implementations can be provided for these helper methods, that would completely satisfy the needs most of the time.
- Therefore, abstract base classes are used to provide these default helper method implementations to improve the usability of the standard library.
Is the close() method fundamental to the operation of streams?
Contrary to your point of view, I would argue that streams that do not require closing is the minority, rather than majority.
- File system handles
- Operating-system owned resources, such as:
- Sockets
- Pipes
- Operating-system managed shared memory (as opposed to JVM-managed memory)
- Database connections
To put it bluntly,
- It appears that array-backed streams are the only kind of streams that do not require explicit closing.
Does the cost-benefit analysis favor the inclusion of the close() method into the stream interface?
It seems so. For streams that do not require explicit closing, it is harmless to require it to provide a trivial close()
method that does nothing.
Similar observation can be found in the C# language, where it is common to see interfaces that subtype from IDisposable
.
Would the failure to include the close() method in the stream interface cause extraordinary harm to implementations that require it?
Yes. Not having it on the interface, or splitting it out to a second Closeable
interface would cause extraordinary harm.
In the first code snippet below, suppose InputStream
does not contain the close()
method. Here is what the code would look like:
public void parseAndCloseStream(InputStream strm) throws IOException
{
if (strm == null) { /* ... to avoid NPE inside finally */ }
try
{
// read from stream
}
finally
{
if (strm instanceof Closeable)
{
Closeable c = (Closeable)strm;
c.close();
}
}
}
In other words, because the omission implies that "not every InputStream is a Closeable", any consumer of any InputStream instance now has the additional responsibility to check whether the instance is indeed Closeable.
The try-with
pattern would then also require more code, because you cannot use an InputStream there unless you can cast it into a Closeable.
Is it a violation of Interface Segregation Principle?
No.
- If a method is fundamental to the operation for the majority of implementation, it makes sense to require it.
- Likewise, if a method is cohesive with the rest of the methods in that interface (i.e. common-sense for its inclusion), it is not a violation.
Specifically, Interface Segregation Principle does not require:
- Does not require an interface to be minimal; even though there are benefits for keeping an interface small and simple.
- Does not require one to separate out all orthogonal facets of an interface, if doing so will cause more harm than good (great inconvenience).
Best Answer
Objects that are passed around to other methods can be changed by the method, and the caller will notice the change. If you let someone else use your
Stream
, they might close it and you'd never notice until you try to write and fail. That's why it's usually a good idea not to pass mutable objects around outside your control.Confusion often arises because many textbooks categorically say "Java has pass-by-value semantics". That is technically true, but not very helpful for a language learner. Technically, it's not the object that gets passed around but a reference to an object, and the called method gets a copy of the reference, which of course references the same object.
But in practice, the difference between object and object reference is often not clear in people's minds. Nobody is ever interested in the value of a reference other than to check whether or not it is
null
. In fact, Java doesn't even have a dereferencing syntax apart from the member selector.
. What people care about is the state of the underlying object. So effectively this means that mutable objects are passed by reference in the sense that the caller gets to change them, and you might be affected by that. In this sense, the textbook explanation is correct and useful for primitive types, but correct and misleading for object types.