App
App

Reputation: 356

C# Interface: extracting additional public property value (not part of interface) from a concrete class

Code sample to begin with:

  internal class ClubHouse : ILeasable
  {
      public int Id { get; set; }

      public int AreaInSquareFeet { get; set; }
  }
 public class Parking : ILeasable
 {
    public int Id { get; set; }
    public int CarCapacity { get; set; }
 }
  internal interface ILeasable
  {
    int Id { get; set; }
  }
  class LeasableRepository
  {
    private List<ILeasable> _leasable = new List<ILeasable>()
    {
        new ClubHouse() {Id = 208, AreaInSquareFeet = 7500 },
        new ShowRoom(){ Id = 202, AreaInSquareFeet = 4000 },
        new Parking() {Id = 504, CarCapacity = 4},
    };

    private Dictionary<int, ILeasable> _leasableDictionary = new Dictionary<int, ILeasable>();

    public LeasableRepository()
    {
        _leasableDictionary = _leasable.ToDictionary(x => x.Id, x => x);
    }

    public ILeasable GetLeasable(int id)
    {
        if (_leasableDictionary.ContainsKey(id)) return _leasableDictionary[id];
        return null;
    }
  }

  public class ChargeCalculatingFacade
  {
    LeasableRepository leasableRepository = new LeasableRepository();
    public void ShowLeasingCharges(int id)
    {
        var leasable = leasableRepository.GetLeasable(id);
        var leasingCharge = GetLeasingCharges(leasable);

    }

    private int GetLeasingCharges(ILeasable leasable)
    {

        // This is not possible as I can't be sure that leasable is ClubHouse
        var property = (ClubHouse) leasable;
        var areaInSquareFeet = property.AreaInSquareFeet;

        return areaInSquareFeet * 10;
    }
  }

Now, in class ChargeCalculatingFacade class, in method ShowLeasingCharges(int id), based on the id, I called GetLeasable(int id) which returns one of the implementation of ILeasable. However it return as an interface ILeasable.

I pass that ILeasable to a private method GetLeasingCharges(leasable) to calculate the leasing charges based on the AreaInSquareFeet.

Now, leasable parameter is just ILeasable, which has just "Id" property available. Now how to identify which concreat class implementation is passed as parameter, I can cast it to get AreaInSquareFeet like this

        var property = (ClubHouse) leasable;
        var areaInSquareFeet = property.AreaInSquareFeet;

But the above code is not posible as I am not sure if the leasable is ClubHouse as it just picks leasable from a dictionary based on Id.

All class does not have the same additional property. For instance, Parking has additional property as "CarCapacity". I have 10 such classes, now cannot put 10 if logic to check if the interface is of required class type.

I wonder if some design pattern or some SOLID principle can simplify the design.

I have following questions:

  1. How do I get the areaInSquareFeet in such case
  2. Is this a good practice to have an interface with few methods and properties and again have additional public methods or properties in concreate class.

Note: I do not want to use reflection. I would like to change a design in case without reflection is not possible. Any design suggestions? Any desing pattern can be used in such scenario?'

Thank you. Mita

Upvotes: 0

Views: 71

Answers (3)

IMil
IMil

Reputation: 1400

While this might be an overkill, in a case similar to yours I have on a few occasions used a pseudo-DSL approach. That is, I first come up with a language to express my intent, and then implement it.

What do you need? The ability to express calculations in a readable way. Let's do it this way: assuming you have a class

public class LeaseCalculator
{
    public int CalculateLease(int id) ...

I'd like to initialize it like this:

    var builder = new LeaseCalculatorBuilder();

    LeaseCalculator calculator = builder
        .On<ClubHouse>(house => house.AreaInSquareFeet)
        .On<Parking>(park => park.CarCapacity)
        .On<ShowRoom>(room => room.AreaInSquareFeet)
        .Build(leasableRepository);

Is the intent clear? I believe so. If we have a club house, we do the first thing; for parking, something else, etc, etc.

Now, to the implementation. I could also walk step by step, but to cut story short:

public class LeaseCalculatorBuilder
{
    internal Dictionary<Type, Func<ILeasable, int>> Calculations { get; } = new Dictionary<Type, Func<ILeasable, int>>();

    internal LeaseCalculatorBuilder On<T>(Func<T, int> calculation) where T : class, ILeasable
    {
        Calculations.Add(typeof(T), (ILeasable c) => calculation((T)c));
        return this;
    }

    internal LeaseCalculator Build(LeasableRepository leasableRepository)
    {
        return new LeaseCalculator(leasableRepository, this);
    }
}

public class LeaseCalculator
{
    private readonly Dictionary<Type, Func<ILeasable, int>> _calculations;
    private readonly LeasableRepository _leasableRepository;

    internal LeaseCalculator(LeasableRepository leasableRepository, LeaseCalculatorBuilder builder)
    {
        _leasableRepository = leasableRepository;
        _calculations = builder.Calculations;
    }

    public int CalculateLease(int id)
    {
        ILeasable property = _leasableRepository.GetLeasable(id);
        Type type = property.GetType();
        if (_calculations.TryGetValue(type, out var calculation))
        {
            return calculation(property);
        }
        throw new Exception("Unexpected type, please extend the calculator");
    }
}

And finally, a default creator:

public static class DefaultLeaseCalculator
{
    internal static LeaseCalculator Build(LeasableRepository leasableRepository)
    {
        var builder = new LeaseCalculatorBuilder();

        LeaseCalculator calculator = builder
            .On<ClubHouse>(house => house.AreaInSquareFeet)
            .On<Parking>(park => park.CarCapacity)
            .On<ShowRoom>(room => room.AreaInSquareFeet)
            .Build(leasableRepository);

        return calculator;
    }
}

Neat?

Upvotes: 0

wut-excel
wut-excel

Reputation: 2295

I quite agree with @tymtam approach. You can also use an abstract class in an alternative.

public abstract class ChargeCalculatingFacadeBase<T> where T : ILeasable
{
    LeasableRepository leasableRepository = new LeasableRepository();
    public ILeasable leasable;
    public void ShowLeasingCharges(int id)
    {
        leasable = leasableRepository.GetLeasable(id);
        var leasingCharge = GetLeasingCharges((T)leasable);

    }
    public abstract int GetLeasingCharges(T leasable);
}
public class ChargeCalculatingFacade : ChargeCalculatingFacadeBase<ClubHouse>
{
    public override int GetLeasingCharges(ClubHouse leasable)
    {
        var property = leasable;
        var areaInSquareFeet = property.AreaInSquareFeet;

        return areaInSquareFeet * 10;
    }
}

Upvotes: 0

tmaj
tmaj

Reputation: 34987

A. ILeasable.GetLeasingCharges

If GetLeasingCharges depends only on the data the object already has I could be argued that it may be better choice to make GetLeasingCharges part of ILeasable.

internal interface ILeasable
{
  int Id { get; set; }
  int GetLeasingCharges();
}


internal class ClubHouse : ILeasable
{
  public int Id { get; set; }

  public int AreaInSquareFeet { get; set; }

  public int GetLeasingCharges() => AreaInSquareFeet * 10;
}

internal class ClubHouse : ILeasable
{
  public int Id { get; set; }

  public int CarCapcity{ get; set; }

  public int GetLeasingCharges() => CarCapcity * 15;
}

B. GetLeasingCharges not part ILeasable

From C#7.0 you can use pattern matching for situations like this.

public static int GetLeasingCharges(ILeasable leasable)
{
    // From c#7.0
    switch (leasable)
    {
        case ClubHouse c:
            return c.AreaInSquareFeet * 10;
        case ShowRoom s:
            return s.AreaInSquareFeet * 12;
        case Parking p:
            throw new ArgumentException(
                message: "Parkings cannot be leased!",
                paramName: nameof(leasable));
        default:
            throw new ArgumentException(
                message: "Unknown type",
                paramName: nameof(leasable));
    }
}

Before C#7.0 you could use if.

if (leasable is ClubHouse)
{
    var c = (ClubHouse)leasable;
    return c.AreaInSquareFeet * 10;
} 
else if (leasable is ShowRoom)
{
    var c = (ShowRoom)leasable;
    return s.AreaInSquareFeet * 12;
}
else if(leasable is Parking)
{
    throw new ArgumentException(
         message: "Parkings cannot be leased!",
         paramName: nameof(leasable));
}
else 
{
    throw new ArgumentException(
        message: "Unknown type",
        paramName: nameof(leasable));
}

Upvotes: 1

Related Questions