You should rethink how much seperation you really want, this "Modifier should be "friends" with Database; that is, it should be able to read and write its variables directly." is impossible in Java - there can't be different access privileges for direct field accesses.
I assume what you really want is to strongly discourage undesirable access patterns. There are a few ways how this can be achieved:
1.) Put Modifier and Database in the same package and make the "Setters" package protected, thus the set-methods become invisible to your Viewer (as long as Viewer is not in the same package). This is more or less impractical for larger designs.
2.) Seperate the concerns into completely different Projects. Then you can set the projects dependencies that only Modifier gets access to Database at all. This implies that you change your design somewhat, either Database becomes two projects (one with the public-readonly interface and one with the full-access interface), or remove the dependency between Viewer and Database completely and make Viewer access Database only through Modifier. This solution has the advantage that its physically impossible to violate access boundaries (It will not compile in the build).
3.) A solution closer to the actual "friend" class concept would be to have two interfaces, one for read, one for write and Database implements those interfaces using inner classes. Then you can "guard" access to the inner class instances with getters that take the client as an argument like this:
public class Database {
public DatabaseReadAccess getReadAccess(Viewer viewer) { ... }
public DatabaseWriteAccess getWriteAccess(Modifier modifier) { ... }
}
This will not prevent a malicious access, but somewhat discourage them. If you want to go one step further, define "Token" classes for Viewer and Modifier and require the token instance for the access getters in Database (then the compiler will enforce the restrictions):
public class ModifierToken {
ModifierToken(Modifier modifier) {
// constructor is package protected, so no outsiders can create tokens!
}
}
I personally would go with the "separate projects" approach, it makes undesirable accesses obvious and violations pop up at latest in the build. I've never tried the "token" approach myself.