Lee
Lee

Reputation: 3969

Maintain consistent loose coupling given complex inheritance

For the purposes of loose coupling and testing I am now replacing concrete references with interfaces and abstract classes, however I'm having trouble getting my head around the following scenario:

Say I have two concrete business layer objects whose only purpose is to call Data Access Logic methods and perform logic on the results before passing back to the controller (ASP.NET MVC 4), called FordCar and CitroenCar. They both inherit from a Car abstract class, which inherits from ICar interface:

public interface ICar
{
    ICarDAL DAL;
    bool StartEngine();
    bool StopEngine();
    Driver ChangeDriver(Driver d);
}

public abstract class Car : ICar
{
    private ICarDAL DAL;

    public virtual Car(ICarDAL DALInstance)
    {
        this.DAL = DALInstance;
    }

    public virtual bool StartEngine()
    {
        return this.DAL.UpdateEngine(true);
    }

    public virtual bool StopEngine()
    {
        return this.DAL.UpdateEngine(false);
    }

    public virtual Driver ChangeDriver(Driver d)
    {
        return this.DAL.UpdateDriver(d);
    }
}

Because the DAL contains logic that is appropriate for all cars, I implement an interface and abstract class for this too:

public interface ICarDAL<T>
{
    T EFContext;
    bool UpdateEngine(bool b);
    Driver UpdateDriver(Driver d);
}

public abstract class CarDAL<T> : ICarDAL<T>
{
    private T EFContext;

    public virtual bool UpdateEngine(bool b)
    {
        try
        {
            using(T db = new T())
            {
                // Perform DB update
                // return true
            }
        }
        catch(Exception e)
        {
            // Handle and return false
        }
    }

    // and so on..
}

Now say the FordCar has the ability to turn on windscreen heating, which the CitroenCar does not have. This is where I lose sight, in order to be consistent I expect I'd have to implement an additional small interfaces and abstract classes for just FordCar but how can I maintain using the abstract CarDAL class - as it holds mostly common functionality - yet call TurnOnScreenHeating() when it is not defined there? This requires a car type specific DAL classes, which defeats the object of my goal.

I suppose my question is: given lots of domain objects with mostly shared functionality how do I accommodate for the odd unique functionality when attempting loose coupling and dependency injection?

For in my controller I would hope to do this:

public class FordCarController : Controller
{
    private Car BLLMethods;

    public FordCarController()
    {
        // Init concrete type
        this.BLLMethods = new FordCar();
    }

    public ActionResult DoHeating()
    {
        this.BLLMethods.TurnOnScreenHeating();
        return View();
    }
}

Apologies for the convoluted scenario and poor title.

Upvotes: 1

Views: 258

Answers (1)

StriplingWarrior
StriplingWarrior

Reputation: 156624

I have a few observations. I'm hoping one or two of them will apply to the specific situation you're running into:

  1. It's often better to favor composition over inheritance. In other words, rather than having a CitroenCar and a FordCar that inherit from Car, you could just have a Car with a Make property to tell you which kind of car it is.
  2. Rather than basing functionality on what you know about specific types of cars, try focusing on feature checking. There are a few different approaches you can take. For example:
    1. Make the objects implement feature-specific interfaces (IHaveWindscreenHeater).
    2. Make the main ICar class implement a bool CanHeatWindscreen{get;} property, as well as a TurnOnScreenHeating() method. This latter method could either do nothing or throw an exception in cars that don't have heating.
  3. The fact that you're calling your controller FordController tells me that you can no longer loosely couple this: You're declaring it to do stuff pertaining to Ford cars, so even if you manage to decouple it from the FordCar class in code, you're still conceptually tightly coupled. This may be even more dangerous than having tight coupling in your code. Following observation #1 above, you may want to just use a single CarController class that handles all your Car actions.

Upvotes: 4

Related Questions