I make a Java project and I faced with a serious issue.
In my project I have a lot of managers and repositories.
I partially solved the problem by referring to the MainApplication
class that holds all repositories and managers references, so when I refer to it I can get every repository I need with no efforts. The simplified code looks like this:
class MainApplication {
private final UserRepository userRepository;
private final GroupRepository groupRepository;
private final PhotoRegistry photoRegistry;
private final UserDAO userDAO;
public MainApplication() {
this.userRepository = new UserRepository(this);
this.groupRepository = new GroupRepository(this);
this.photoRegistry = new PhotoRegistry();
this.userDAO = new UserDAO();
}
public UserRepository getUserRepository() {
return userRepository;
}
public GroupRepository getGroupRepository() {
return groupRepository;
}
public PhotoRegistry getPhotoRegistry() {
return photoRegistry;
}
public UserDAO getUserDAO() {
return userDAO;
}
}
interface ResponseHandler {
void handleResponse(User user, String response);
}
class User {
private final long id;
private final long groupId;
private ResponseHandler responseHandler;
private UserRepository repository;
public User (long id, long groupId, UserRepository repository) {
this.id = id;
this.groupId = groupId;
this.repository = repository;
}
public void processResponse(String response) {
if(this.responseHandler != null) responseHandler.handleResponse(this, response);
}
public void reply(String replyText, byte [] photo) {
// some reply logic
}
public void setResponseHandler(ResponseHandler handler) {
this.responseHandler = handler;
}
public UserRepository getRepository() {
return repository;
}
public long getGroupId() {
return groupId;
}
public long getId() {
return id;
}
}
class Group {
private long id;
private String groupDescription;
public String getDescription() {
return groupDescription;
}
public long getId() {
return id;
}
}
class UserRepository {
private final MainApplication main;
private final Map<Long, User> users;
public UserRepository(MainApplication main) {
this.main = main;
this.users = new HashMap<>();
}
public User createUser(long userId, long groupId) {
User newUser = new User(userId, groupId, this);
this.users.put(newUser.getId(), newUser);
return newUser;
}
public User getUser(long id) {
return users.get(id);
}
public MainApplication getMain() {
return main;
}
}
class GroupRepository {
private final MainApplication main;
private final Map<Long, Group> groups;
public GroupRepository(MainApplication main) {
this.main = main;
this.groups = new HashMap<>();
}
public void addGroup(Group group) {
this.groups.put(group.getId(), group);
}
public Group getGroup(long id) {
return groups.get(id);
}
}
class PhotoRegistry {
private final Map<String, byte[]> photosToName;
public PhotoRegistry() {
this.photosToName = new HashMap<>();
}
public void registerPhoto(String photoName, byte[] data) {
this.photosToName.put(photoName, data);
}
public byte[] getPhoto(String photoName) {
return photosToName.get(photoName);
}
}
class UserDAO {
public double getBalanceFor(long userId) {
double balance = 0;
// retrieving balance from database
return balance;
}
}
and here's a simple usage of the above code:
class Main {
public static void main(String[] args) {
//registering the application
MainApplication main = new MainApplication();
User user = main.getUserRepository().createUser(0, 1);
ResponseHandler handler = new ResponseHandler() {
@Override
public void handleResponse(User user, String response) {
if(!response.equalsIgnoreCase("Hello")) return;
MainApplication main = user.getRepository().getMain();
double userBalance = main.getUserDAO().getBalanceFor(user.getId());
Group userGroup = main.getGroupRepository().getGroup(user.getGroupId());
byte [] attachedPhoto = main.getPhotoRegistry().getPhoto("balancePhoto");
StringBuilder builder = new StringBuilder();
builder.append("Hello user ")
.append(user.getId())
.append("\n")
.append("You are in a group with id: ")
.append(userGroup.getId())
.append(" and description: ")
.append(userGroup.getDescription())
.append("your balance is $")
.append(userBalance);
user.reply(builder.toString(), attachedPhoto);
}
};
user.setResponseHandler(handler);
// supplying a response from user
user.processResponse("Hello");
}
}
So my question is how can I structure this project better? Do I need to use a dependency injection framework like Spring
or Guice
? In my opinion this can be done much better but I have no idea how because I can't really predict which repositories or managers I need in a ResponseHandler
implementation.
I have already tried to use Guice
, but I can't really understand how it fits here. Also I tried to provide only necessary references to UserRepository
instead of passing the whole MainApplication
instance, but my project has much more repositories than this one, so the constructor gets really huge and not easy to work with.
So my question is how can I structure this project better?
Entire books have been written about program structure and software architecture (e.g. APPP, PEAA), so this isn't something that we can easily answer based on a small example. That said, you should try to avoid circular dependencies, as this is bound to make your code more complicated than it has to be.
For example, MainApplication
apparently depends on UserRepository
, but UserRepository
also depends on MainApplication
. The same seems to be the case for GroupRepository
.
In my experience, you never need circular dependencies. In APPP Robert C. Martin calls this the Acyclic Dependencies Principle (ADP) (chapter 28).
I've been modelling dependencies as a Directed Acyclic Graphs for more than a decade now, and my experience tells me that you can always find an alternative design that doesn't require cycles. Here's an example experience report.
Do I need to use a dependency injection framework like Spring or Guice?
No, you don't need a DI Container in order to take advantage of Dependency Injection.