locked
What is the best way to resolve circular references RRS feed

  • Question

  • Say I have two dll libraries, Friends and Mail. The Friends library contains a class called Friends with 2 methods.... AddFriend(...) and GetFriend(...). The implementation of AddFriend persists database information, etc., and ends with a call to Mail.SendMail(...) in the Mail library. Now, among other things, the SendMail(...) implementation has a reference BACK to Friends.GetFriend(...) in order to get the information necessary to send.

    What are the options to resolve this circular reference. I am thinking, perhaps, of 1) overloading SendMail to have a method that includes data from GetFriends, or 2) use interfaces and dependency injection.

    Can anyone think of any patterns or approaches that would be useful in resolving circular references?

     

    Thursday, May 6, 2010 2:29 AM

Answers

  • I believe your overload soultion is close to a "right" answer, but lacks a couple of key details that keep the solution from working. 

    A.  Creating an overload specifically for friend still ensures that the Mailer is aware of the Friend object and that any other classes would not have any use for that overload.
    B.  Existing methods within the Mailer that contain calls to the Friend class will persist if all you do is create an overload.

    You can solve both of these problems as follows (assume friend and mailer are in different assemblies):

     

      public class Friend
      {
        public void GetFriend(int id)
        {
    
        }
        public void AddFriend(Friend afriend)
        {
          //insert Friend into DB
          //Mail.SendMail();
        }
        private void CreateAndSendMessage()
        {
          //Gather friend details
          //Insert friend details into a message
          //call Mailer and pass the message object.
        }
      }
      public class Mailer
      {
        //get rid of me
        //public void SendMail(string message)
        //{
        //  //string friendDetails = GetFriend(name)
        //}
        /// <summary>
        /// 1) Overloading sendmail to have a method that includes data from GetFriends.
        /// This alone does not solve the problem. You must get rid of the calls to GetFriend(name) within Mailer. Mailer is a utility that should be built around the possibility that more than one object may use it. 
        /// </summary>
        /// <param name="friendId"></param>
        public void SendMail(string firstName, string lastName)
        {
    
        }
        /// <summary>
        /// Implmement a generic SendMail that accepts simply a message. The details of the layout of the message should be left to the caller. E.G. A friend should have a "CreateMessage" helper method that constructs the message before Sending.
        /// </summary>
        /// <param name="message"></param>
        public void SendMail(string message)
        {
    
        }
      }
    
    

    BrianMackey.NET
    Friday, May 7, 2010 4:33 PM

All replies

  • if the Mail class is dependent on the friends class then it must not be in a stand alone dll, because its not independent.

    so why not put them in the same dll.


    Mahmoud Darwish Senior Software Developer C#/VB
    Thursday, May 6, 2010 8:16 AM
  • I think a good option would be use (2) - a simple callback. You could have classes other than Friends to send mails too and all of them just need to implement the same interface that exposes the GetDetails() method.

    Shival.

    Thursday, May 6, 2010 8:46 AM
  • You could create interfaces for your classes and put them in a seperate assembly. Then the concrete implementations can be in there own assemblies and reference each other by interface.

     

     


    HTH Ciaran http://wannabedeveloper.spaces.live.com
    Thursday, May 6, 2010 8:52 AM
  • This is a fairly common refactoring.

    Assembly A depends on assembly B, which depends on assembly A.

    (1) Identify the types upon which A and B mutually depend.
    (2) Extract those types into a new assembly C.
    (3) Make assembly A and assembly B dependent upon Assembly C.
    (4) ????
    (5) Profit!

    • Proposed as answer by HamAndFig Thursday, May 6, 2010 11:15 AM
    Thursday, May 6, 2010 8:54 AM
  • This is a fairly common refactoring.

    Assembly A depends on assembly B, which depends on assembly A.

    (1) Identify the types upon which A and B mutually depend.
    (2) Extract those types into a new assembly C.
    (3) Make assembly A and assembly B dependent upon Assembly C.
    (4) ????
    (5) Profit!

    This question was actually brought up to me during an interview and that was my initial response. However, I was told that factoring out the common method into a separate assembly was not an option as this problem was fairly common to them and there were many other ways of handling it. Besides the one you mentioned, I could only think of the two I suggested in the OP.
    Thursday, May 6, 2010 2:17 PM
  • You could do something quite similar to Matthew's suggestion, only instead of moving the dependencies into assembly C, move them into the "lowest layer".  If your using Business Layer/Data Layer then that would mean you move the common stuff into DL. 

    Sure its not a cookie cutter BL/DL model, but you don't have to maintain another Assembly (assem C).


    BrianMackey.NET
    Thursday, May 6, 2010 2:28 PM
  • You could do something quite similar to Matthew's suggestion, only instead of moving the dependencies into assembly C, move them into the "lowest layer".  If your using Business Layer/Data Layer then that would mean you move the common stuff into DL. 

    Sure its not a cookie cutter BL/DL model, but you don't have to maintain another Assembly (assem C).


    BrianMackey.NET
    The issue was that Mail was a utility module and not layered. They wanted cohesion within the modules and AddFriend/GetFriend really belonged within the Friend assembly. I was actually stumped by this question during the interview once I was told that factored classes was not an option and only thought of the other two on the drive home ( I hate when that happens. :) ). I am still at a loss to come up with "many different ways" to handle circular references.
    Thursday, May 6, 2010 2:45 PM
  • Hm, well I don't know the full details.  But, that sounds like there's another issue altogether.  One of coupling with the utility class that needs to be resolved.
    BrianMackey.NET
    Thursday, May 6, 2010 2:48 PM
  • I believe it's possible to handle this by compiling the Friend assembly without the implementation of AddFriend; then compile Mail; then recompile Friend with the full implementation.

    It may also be possible using netmodules; not sure on that one. Who uses netmodules, anyway? ;)

           -Steve


    Programming blog: http://nitoprograms.blogspot.com/
      Including my TCP/IP .NET Sockets FAQ
      and How to Implement IDisposable and Finalizers: 3 Easy Rules
    Microsoft Certified Professional Developer

    How to get to Heaven according to the Bible
    Thursday, May 6, 2010 2:53 PM
  • I believe it's possible to handle this by compiling the Friend assembly without the implementation of AddFriend; then compile Mail; then recompile Friend with the full implementation.

    Hmmm... that might work in reverse. Comment out the reference to AddFriend in MAIL and compile it. Then compile FRIEND with a reference to MAIL.SendMail... and then add the reference to Friend.AddFriend in MAIL and recompile.
    Thursday, May 6, 2010 4:42 PM
  • Actually, that's what I said... or meant to say... :)

    (Not much sleep. Teething baby.)

           -Steve


    Programming blog: http://nitoprograms.blogspot.com/
      Including my TCP/IP .NET Sockets FAQ
      and How to Implement IDisposable and Finalizers: 3 Easy Rules
    Microsoft Certified Professional Developer

    How to get to Heaven according to the Bible
    Thursday, May 6, 2010 6:06 PM
  • I believe your overload soultion is close to a "right" answer, but lacks a couple of key details that keep the solution from working. 

    A.  Creating an overload specifically for friend still ensures that the Mailer is aware of the Friend object and that any other classes would not have any use for that overload.
    B.  Existing methods within the Mailer that contain calls to the Friend class will persist if all you do is create an overload.

    You can solve both of these problems as follows (assume friend and mailer are in different assemblies):

     

      public class Friend
      {
        public void GetFriend(int id)
        {
    
        }
        public void AddFriend(Friend afriend)
        {
          //insert Friend into DB
          //Mail.SendMail();
        }
        private void CreateAndSendMessage()
        {
          //Gather friend details
          //Insert friend details into a message
          //call Mailer and pass the message object.
        }
      }
      public class Mailer
      {
        //get rid of me
        //public void SendMail(string message)
        //{
        //  //string friendDetails = GetFriend(name)
        //}
        /// <summary>
        /// 1) Overloading sendmail to have a method that includes data from GetFriends.
        /// This alone does not solve the problem. You must get rid of the calls to GetFriend(name) within Mailer. Mailer is a utility that should be built around the possibility that more than one object may use it. 
        /// </summary>
        /// <param name="friendId"></param>
        public void SendMail(string firstName, string lastName)
        {
    
        }
        /// <summary>
        /// Implmement a generic SendMail that accepts simply a message. The details of the layout of the message should be left to the caller. E.G. A friend should have a "CreateMessage" helper method that constructs the message before Sending.
        /// </summary>
        /// <param name="message"></param>
        public void SendMail(string message)
        {
    
        }
      }
    
    

    BrianMackey.NET
    Friday, May 7, 2010 4:33 PM
  • I am agreeing with P.Brian here.

    The way I look at it, at a higher level, the players are Friend and Mail Objects. A friend can exist without mail and the SendMail object can send mail to not only firends, but enemies, aliens, etc.. In order to send  mail to a friend, the SendMail object just needs a some string details of where to send the mail, and the subject, etc.

    To avoid any coupling of these two seperate entities, I would compose the two into a third class, a MailManager that uses both the Friend Object and SendMail object to cordinate the sending of mail to friends.

    Friday, May 7, 2010 6:07 PM