Home  /  Questions  /  Question



55   94.5
Jul 17, 2012


What design pattern is best for the following scenario?

I have 3 car rental agencies.  Each agency requires the data in XML, but in a different format, for example:


Agency 1
<Rental>
    <Customer>
    <FirstName>test</FirstName>
    <LastName>test</LastName>
    </Customer>
    <Pickup date="07/20/2012"/>
    <Dropoff date="07/25/2012"/>
    <Deposit cost="100"/>
</Rental>
Agency 2
<Rental>
    <Customer>
    <FirstName>test</FirstName>
    <LastName>test</LastName>
    </Customer>
    <Pickup>07/20/2012</Pickup>
    <Dropoff>07/25/2012</Dropoff>
    <Deposit>100</Deposit>
</Rental>

Agency 3

<Rental>
    <Customer>
    <FirstName>test</FirstName>
    <LastName>test</LastName>
    </Customer>
    <Pickup>07/20/2012</Pickup>
    <Dropoff>07/25/2012</Dropoff>
    <Deposit>100</Deposit>
</Rental>


As you can see from above, all 3 basically contain the same information, altough this doesn't have to be the case (some can contain more or less information), but it is structured different, so the way I access it is different.  What is the best approach to take so I can write the most minimum code, but be able to adapt to new rental agencies that come along with a different structure?
Right now, I am doing something like this:



public class Agency1
    {
    SubmitRental()
    {
    //Parse XML for Agency 1
    }

    //Other methods for agency 1
    }

    public class Agency2
    {
    SubmitRental()
    {
    //Parse XML for Agency 2
    }

     //Other methods for agency 2
    }

    public class Agency3
    {
    SubmitRental()
    {
    //Parse XML for Agency 3
    }

    //Other methods for agency 3
    }

In the above, the classes contain the same methods, but the way they are implemented is different.  There are some methods, properties, etc that are in some classes, but not in others.  Are interfaces the best way to approach this?  If so, should everything be made an interface?
In the XML samples above, the data was the same, but the format was different but what about the scenario where not only is the format different, but the data is different as well?
For example: 

Agency 4
<Rental start="07/20/12" end="07/25/12">
    <Name>John Doe</Name>
    <DriverLicenseNumber>193048204820</DriverLicenseNumber>
    <DOB>3/4/64</DOB>
<Rental>

 



90   96.5
Jul 19, 2012
Just sharing my thoughts here, may need to fine tune - as I have not tried this solution yet (may be I can get back)

Two key aspects here:
1) If you are going to process all of your Agency's XMLs (whatever may their format) into a common format - Adapter Pattern.
2) If you want to have classes or entities into your system, to represent each Agency XML , then you would need - Factory Pattern

For case 1 , you can read an agency XML into an XML object and then "adapt" its contents to your target class - Say Agency Mapper
Whenever a new schema comes into scope, you need to adapt it to the AgencyMapper class. 

For case 2:

You have build Factory pattern, that each Agency XML becomes a concrete product. I hope you would not need another layer of abstraction here. In this case whenever a new (type of) Agency comes in you may need to add a new COncrete class. I think IOC containers will help here to avoid adding code into the system.


Quick steps:

1) Have multiple Schema files each representing an Agency XML, (saved to your working directory) like Agency1.XSD..etc.,
2) When you start processing, your system should read an Agency XML file and try validating it against the existing schemas.
3) when it matches with one of the Schemas , your code should assign a type (enum?) like Agency1 or AgencyTwo etc.,
4) Pass the type Factory class that it creates the corresponding Agency Class
5) Have a mapper method in each class that it reads XML and initializes corresponding members ( I maintain the class name and XML attribute name to be the same, that I can use reflection to loop through and assign at runtime)
6) then call Submit rental()

To answer Abstract Factory or interface, if you want to enforce things against unrelated entities - interface
To enfore and enable versioning across related entities - abstract classes

Thanks and Regards
Tarriq

65   95.7
Jul 20, 2012
How about the Strategy and Factory patterns.

See example, below. I left out error checking for clarity.

How you want to pass around the derived classes (Agency1, Agency2, ... the data) is up to you. I find this way to be the easiest, but will vary depending on your app.

You could also add a property to set the behavior and pass in the required data in some other fashion.

I think you'll get the idea, though.

And, of course, the "swtich" statement in the main controller is not ideal. That too could be put into a factory.

I just wanted to illustrate and suggest the Strategy pattern without writing an entire app.

Hope this helps,

Mike

using System;

namespace MyCompany.Agency
{
    interface IAgencyBehavior
    {
        int GetNumberOfCarsAvailable();
        void SubmitRental();
    }

    public abstract class Agency
    {
        public string AgencyName { get; set;}
        public string FirstName { get; set; }
        public string LastName { get; set; }
        // all other properties common to all agencies
    }

    // A specialized agency:
    public class Agency1 : Agency
    { 
        public Agency1() 
        {
            AgencyName = "Agency1";
        }
        public DateTime DOB {get; set;} 
    }

    // A specialized agency:
    public class Agency2 : Agency
    {
        public Agency2() 
        {
            AgencyName = "Agency2";
        }
        public string LicenseNumber { get; set; }
    }

    // Main controller:
    public class AgencyMainController : IAgencyBehavior
    {
        private IAgencyBehavior _behavior;

        public AgencyMainController(Agency agency)
        {
            NewBehavior(agency);
        }

        public void NewBehavior(Agency agency)
        {
            switch (agency.AgencyName.ToLower())
            {
                case "agency1":
                    _behavior = new Agency1Behavior((Agency1)agency);
                    break;

                case "agency2":
                    _behavior = new Agency2Behavior((Agency2)agency);
                    break;

                default:
                    // Maybe throw an exception here
                    break;
            }
        }
        public int GetNumberOfCarsAvailable()
        {
            if (_behavior == null) throw new ArgumentException("Behavior has not yet been set");
            return _behavior.GetNumberOfCarsAvailable();
        }

        public void SubmitRental()
        {
            if (_behavior == null) throw new ArgumentException("Behavior has not yet been set");
            _behavior.SubmitRental();
        }
    }

    // Specialized controllers:
    public class Agency1Behavior : IAgencyBehavior
    {
        private Agency1 _agency;
        public Agency1Behavior(Agency1 agency)
        {
            _agency = agency;
        }
        public int GetNumberOfCarsAvailable()
        {
            return 1;
        }

        public void SubmitRental()
        {
            // Code here to submit Agency1's XML 
            Console.WriteLine("<Rental AgencyName=\"Agency1\"><Name>" + _agency.FirstName + " " + _agency.LastName + "><DOB>" + _agency.DOB.ToString("MM/dd/yy") + "</DOB></Rental>");
        }
    }

    public class Agency2Behavior : IAgencyBehavior
    {
        private Agency2 _agency;

        public Agency2Behavior(Agency2 agency)
        {
            _agency = agency;
        }

        public int GetNumberOfCarsAvailable()
        {
            return 2;
        }

        public void SubmitRental()
        {
            // Code here to submit Agency2's XML 
            Console.WriteLine("<Rental AgencyName=\"Agency2\"><Name>" + _agency.FirstName + " " + _agency.LastName + "<LicenseNumber>" + _agency.LicenseNumber + "</LicenseNumber></Rental>");
        }
    }


    // Example client code:
    public class MyApp
    {
        public static void Main(string[] args) 
        {
            // Somehow, somewhere you retrieved information and determined it's Agency2 and constructed an Agency2 object (perhaps through a factory):
            Agency2 agency2Object = new Agency2() {LicenseNumber="ABC123", FirstName="John", LastName="Doe"};

            AgencyMainController controller = new AgencyMainController(agency2Object);
            controller.SubmitRental();
            Console.WriteLine(controller.GetNumberOfCarsAvailable().ToString());

            // Agency1:
            Agency1 agency1Object = new Agency1() { DOB = DateTime.Now.AddYears(-20) , FirstName = "Jane", LastName = "Doe" };
            controller.NewBehavior(agency1Object);
            controller.SubmitRental();
            Console.WriteLine(controller.GetNumberOfCarsAvailable().ToString());
            Console.ReadKey();

        }
    }
}