As part of a program I am developing for practice purposes, I've created a namespace Core
designed to contain the core code resources for the program to use. This namespace is in a Class Library project.
Two of the sub-namespaces in Core
are Core.Files
and Core.Errors
. In Core.Files
there is a TxtFile
class that is responsible for input-output to .txt files. In Core.Errors
there is an Error
class which is responsible for managing a specific error. The class API allows you to manage an error in three ways:
- Display the error to the user.
- Record the error in a log file.
- Perform both.
In the last two options, Error
depends on TxtFile
to record the error in the log file (using the Write()
method).
The Write()
method in TxtFile
is built in such a way that if it fails to write text into the file, Error
is instantiated. Since something is obviously not working in Write()
, the error is displayed to the user but not logged in the file.
Everything works great. Over time, however, the two namespaces grew, and I thought it would be appropriate to create a separate project for each of them.
The problem is that since Error
depends onTxtFile
and TxtFile
depends onError
, there is a circular dependency between the two projects, and it is impossible to add references between them.
This answer suggests that such a situation is an indication of bad design. How can I improve the design so that this problem does not occur? I want to preserve the structure of a single assembly per sub-namespace.
Thanks.
1 Answer 1
If you are not interested in redesigning the object model, the following (fairly common) change will get you going again.
Start a new class library called
Interfaces
or something similar.In both of your existing projects, add a reference to
Interfaces
.In the new library, define an interface
IError
andITxtFile
that match the methods and properties needed of both classes by both libraries.Modify the two existing libraries so that
Error
andTxtFile
implement the interfaces you just created.Write factories for both
TxtFile
andError
. They don't have to be fancy.Modify the constructor for
Error
so that it takes aTxtFileFactory
as a constructor argument. Use the factory to create theTxtFile
(instead of thenew
keyword).Modify the constructor for
TxtFile
so that it takes aErrorFactory
as a constructor argument. Use the factory to create theError
(instead of thenew
keyword).Add
TxtFileFactory
andErrorFactory
to your dependency injection container.Modify your code so you only work with the interfaces, not the concrete types.
Explanation: Neither project will depend on the other for object creation. Instead, they will get their references from a factory that is injected. Because the object graph is defined in the composition root, the need for circular dependency for object creation is eliminated, and because of the interfaces defined in the third library, there is no longer any circular interface dependency.
-
Thank you so much for your answer. I am pretty new to design patterns, so I am not very familiar with dependency injection containers. Could you please modify your answer so a DIC is not required? Thanks again!Michael Haddad– Michael Haddad2017年05月21日 09:14:42 +00:00Commented May 21, 2017 at 9:14
-
Could you help me, please? :)Michael Haddad– Michael Haddad2017年05月27日 20:50:30 +00:00Commented May 27, 2017 at 20:50