6

I'm designing a re-usable class library that contains 2 assemblies (amongst others) named core.xml.dll and core.string.dll.

The xml assembly references the string assembly in order to use some string helper methods.

However now there is an string method that would be benefit from using a method contained in the xml assembly.

If I reference the xml assembly from the string assembly I will have created a circular dependency and will be unable to build both assemblies from source code. (ie chicken and the egg problem).

In order to follow the "Don't Repeat Yourself" principle I would like to avoid duplicating the functionality in both assemblies. If I find a bug in the implementation I only want to fix it in one place.

While I could merge the assemblies into one, this is not ideal as it reduces the cohesiveness of the assembly.

I would need to re-build and re-deploy the entire assembly just for a small change to a specific class. Also, eventually, with so many dependencies I would probably end up with one huge library assembly.

So in the context of a re-usable set of library assemblies what is the best approach to use here? Also, how does the .NET framework itself deal with this issue?

(In Reflector it appears that System.Configuration.dll references System.XML.DLL and vice versa. Is this actually correct, if so how is the circular dependency managed?)

Ash
  • 60,973
  • 31
  • 151
  • 169
  • Could you give a concrete example of how a "... string method that would be benefit from using a method contained in the xml assembly." – Mitch Wheat Mar 08 '09 at 04:07
  • I was looking at a conversion method that accepts XML and converts to a string based format. Yes, I'm thinking that this specifically could be handled some other way, however I'm still interested in the more general question. – Ash Mar 08 '09 at 04:14
  • Look up John Lakos' book, "Large-scale C++ Software Design". A lot of that material may be inapplicable to C#, but he goes in depth about levelized architecture, which is exactly what your question is getting at. – Tom Mar 08 '09 at 05:22
  • @Tom, thanks for the reference, I'll have a look. – Ash Mar 08 '09 at 05:33

4 Answers4

6

Agree with Knives. Circular depedencies is a design smell. Refactor it Mercilessly!

This can be challenging in the case where business objects are tightly coupled. In most cases this can be solved through dependency injection.

Pseudo C++ Example:

class Employee {
    Company company;
};

class Company {
    vector<Employee> employees;
};

Tricky? Not neccesarily:

template<class CompanyT>
class Employee {
    CompanyT company;
};

class Company {
    vector<Employee<Company> > employees;
};

More primitive types, which must depend on a higher level, can be abstracted to work with any sort of other type, so long as it fulfills its contracts.

SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
  • But Iv'e found circular depencies often appear quite naturally in an object oriented design, ie Employee works for Company, Company has a number of Employees. How do you refactor without changing this sort of relationship? – Ash Mar 08 '09 at 04:21
  • @Ash: Is the focus of your system the employee, or the company? If the former, then the employee isn't owned by the company, the company is merely one aspect of their lives. If the latter, then the company has various classes of employee, who share one or more common interfaces. – Shog9 Mar 08 '09 at 04:25
  • @Shog9, I find I create two way references in my designs quite often. But It seems you're saying that this can be a design smell and should be minimized. I'll have to think a bit more on this, thanks. – Ash Mar 08 '09 at 04:34
  • @Ash, nothing wrong with two-way references, but often two objects referencing each other shouldn't see each other the way they see themselves. I don't agree with the folks who strive to avoid ALL explicit dependencies, but one you move beyond simple relations it's worth reducing them. – Shog9 Mar 08 '09 at 04:48
3

Sounds like you need a third assembly...

  • I had though about a 3rd assembly containing Intefaces only. But doesn't this force the application (client) of both assemblies to do all the "plugging" of components together? This would require a full blown Dependency Injection framework wouldn't it? – Ash Mar 08 '09 at 04:17
  • 1
    @Ash - try not to overengineer it in your head. If you need to convert XML -> String, that doesn't require dependency injection, that just requires one single free function (which references both String and XML libraries). – Tom Mar 08 '09 at 04:19
  • agreed with Tom's answer, disagree with needing a third assembly, it is overkill when it might just be an extension method scenario i.e. think linq extension methods on IEnumerable ... – eglasius Mar 08 '09 at 04:24
3

Make it an extension method that is defined in the xml assembly (based on your comment "conversion method that accepts XML and converts to a string based format"). It is dealing with xml. Just like Linq does for IEnumerable.

eglasius
  • 35,831
  • 5
  • 65
  • 110
  • Fair tip. But is there a more generally applicable software engineering approach to this issue. My example using string and xml is perhaps not the best, what about say a Settings assembly using XML assembly and vic versa? – Ash Mar 08 '09 at 04:39
  • @Ash - a core XML module doesn't need configuration at all. An XML-based configuration makes sense. A high-level module that uses XML-based configuration to determine how to parse other XML documents will use both of those libraries. – Tom Mar 08 '09 at 05:19
3

If your String library really needs your XML library, it is probably an indication that your String library needs to be refactored into a lower-level data type definition and "core utilities" (no external dependencies, if possible), and another higher-level library that may bring in XML, SQL, regular expressions, or whatever else you find useful at the application layer.

Tom
  • 10,689
  • 4
  • 41
  • 50