Domanda

I am not experienced in using .Net generics. I am designing a software system for and Investment Holding Company in .Net 4.0. The company has Retail business and IntellectualRights business. BookShop and AudioCDShop are examples of Retail business. EngineDesignPatent and BenzolMedicinePatent are examples of IntellectualRights business. These two business types are totally unrelated.

The investment company has a concept called InvestmentReturn. It is the profit gained from each business. For each “Business Type” (Retail, IntellectualRights ), the calculation logic is different for Investment return.

I need to create a InvestmentReturnCalculator by calculating investment of each “Business Type”.

public static class InvestmentReturnCalculator
{
    public static double GetNetInvestementReturn(List<IBusiness> allMyProfitableBusiness, List<InvestmentReturnElement<IBusiness>> profitElements)
    {
        double totalReturn = 0;
        foreach (IBusiness b in allMyProfitableBusiness)
        {
            //How to do calculation?
        }
        return totalReturn;
    }
}

QUESTION

  1. How to add various business elements into List<InvestmentReturnElement<IBusiness>> profitElements in the Main function ?

    Note: I am getting compilation error when I do the following profitElements.Add(salesProfitBook);

  2. How to implement the GetNetInvestementReturn method in a generic way? If I make the code as follows, there is repetition of the algorithm for different types.. And generics can be used when algorithm is same for multiple types. So the following approach is not DRY.

Note: The following code does not compile.

    foreach (IBusiness b in allMyProfitableBusiness)
    {
        if (b is IRetailBusiness)
        {
            RetailProfit<IRetailBusiness> retailInvestmentProfit = new RetailProfit<IRetailBusiness>();
            totalReturn = totalReturn + retailInvestmentProfit.GetInvestmentProfit(b);
        }
        else if (b is IIntellectualRights)
        {
            IntellectualRightsProfit<IIntellectualRights> intellectualRightsInvestmentProfit = new IntellectualRightsProfit<IIntellectualRights>();
            totalReturn = totalReturn + intellectualRightsInvestmentProfit.GetInvestmentProfit(b);
        }
    }

UPDATE:

The BookShop, EngineDesignPatent inherits a different base class. So I cannot make IBusiness, IRetailBusiness, and IIntellectualRights as abstract classes. They should remain as interfaces.

Now @Grzenio suggestion is to implement a GetInvestmentProfit method in each entity (BookShop, AudioCDShop, etc). Here I will be repeating the same code. Again this is not satisfying DRY.

Moreover the InvestmentReturn concept is for the investment holding company. The individual business types are unaware of such a concept.

Investment Return Element

    public abstract class InvestmentReturnElement<T>
    {
        public abstract double GetInvestmentProfit(T obj);
    }

    public class RetailProfit<T> : InvestmentReturnElement<T> where T : IRetailBusiness
    {
        public override double GetInvestmentProfit(T item)
        {
            return item.Revenue * 5/100;
        }
    }

    public class IntellectualRightsProfit<T> : InvestmentReturnElement<T> where T : IIntellectualRights
    {
        public override double GetInvestmentProfit(T item)
        {
            return item.Royalty * 10/100;
        }
    }      

Business Type Abstractions

public interface IBusiness
{

}

public interface IRetailBusiness : IBusiness
{
    bool IsOnSale { get; set; }
    double Revenue { get; set; }
}

public interface IIntellectualRights : IBusiness
{
    double Royalty { get; set; }
}

Concrete Businesses

    #region Intellectuals
    public class EngineDesignPatent : IIntellectualRights
    {
        public double Royalty { get; set; }
    }

    public class BenzolMedicinePatent : IIntellectualRights
    {
        public double Royalty { get; set; }
    }
    #endregion

    #region Retails
    public class BookShop : IRetailBusiness
    {
        public bool IsOnSale { get; set; }
        public double Revenue { get; set; }
    }

    public class AudioCDShop : IRetailBusiness
    {
        public bool IsOnSale { get; set; }
        public double Revenue { get; set; }
    }
    #endregion

Client

    class Program
    {

        static void Main(string[] args)
        {

            #region MyBusines

            List<IBusiness> allMyProfitableBusiness = new List<IBusiness>();

            BookShop bookShop1 = new BookShop();
            AudioCDShop cd1Shop = new AudioCDShop();
            EngineDesignPatent enginePatent = new EngineDesignPatent();
            BenzolMedicinePatent medicinePatent = new BenzolMedicinePatent();

            allMyProfitableBusiness.Add(bookShop1);
            allMyProfitableBusiness.Add(cd1Shop);
            allMyProfitableBusiness.Add(enginePatent);
            allMyProfitableBusiness.Add(medicinePatent);

            #endregion

            List<InvestmentReturnElement<IBusiness>> profitElements = new List<InvestmentReturnElement<IBusiness>>();

            var salesProfitBook = new RetailProfit<BookShop>();
            var salesProfitAudioCD = new RetailProfit<AudioCDShop>();
            var intellectualProfitEngineDesign = new IntellectualRightsProfit<EngineDesignPatent>();
            var intellectualProfitBenzolMedicine = new IntellectualRightsProfit<BenzolMedicinePatent>();

            //profitElements.Add(salesProfitBook);
            Console.ReadKey();
        }

    }
È stato utile?

Soluzione 3

Following is a solution based on the first response from @Kit in Refactoring Code to avoid Type Casting

It avoids any type check with the help of generics.

enter image description here

Calculator Abstraction

public abstract class InvestmentReturnCalculator
{
    public double ProfitElement { get; set; }
    public abstract double GetInvestmentProfit();

    protected double CalculateBaseProfit()
    {
        double profit = 0;
        if (ProfitElement < 5)
        {
            profit = ProfitElement * 5 / 100;
        }
        else
        {
            profit = ProfitElement * 10 / 100;
        }
        return profit;
    }
}

public abstract class InvestmentReturnCalculator<T> : InvestmentReturnCalculator where T : IBusiness
{
    public T BusinessType { get; set; }
}

Concrete Calculators

public class RetailInvestmentReturnCalculator : InvestmentReturnCalculator<IRetailBusiness>
{
    public RetailInvestmentReturnCalculator(IRetailBusiness retail)
    {
        BusinessType = retail;
        //Business = new BookShop(100);
    }

    public override double GetInvestmentProfit()
    {
        ProfitElement = BusinessType.GrossRevenue;
        return CalculateBaseProfit();
    }
}

public class IntellectualRightsInvestmentReturnCalculator : InvestmentReturnCalculator<IIntellectualRights>
{

    public IntellectualRightsInvestmentReturnCalculator(IIntellectualRights intellectual)
    {
        BusinessType = intellectual;
    }

    public override double GetInvestmentProfit()
    {
        ProfitElement = BusinessType.Royalty;
        return base.CalculateBaseProfit();
    }
}

Business Abstractions

public interface IBusiness
{
    InvestmentReturnCalculator GetMyInvestmentReturnCalculator();
}

public abstract class EntityBaseClass
{

}

public interface IRetailBusiness : IBusiness
{
    double GrossRevenue { get; set; }
}

public interface IIntellectualRights : IBusiness
{
    double Royalty { get; set; }
}

Concrete Businesses

public class EngineDesignPatent : EntityBaseClass, IIntellectualRights
{
    public double Royalty { get; set; }
    public EngineDesignPatent(double royalty)
    {
        Royalty = royalty;
    }

    public InvestmentReturnCalculator GetMyInvestmentReturnCalculator()
    {
        return new IntellectualRightsInvestmentReturnCalculator(this);
    }
}

public class BookShop : EntityBaseClass, IRetailBusiness
{
    public double GrossRevenue { get; set; }
    public BookShop(double grossRevenue)
    {
        GrossRevenue = grossRevenue;
    }

    public InvestmentReturnCalculator GetMyInvestmentReturnCalculator()
    {
        return new RetailInvestmentReturnCalculator(this);
    }
}

public class AudioCDShop : EntityBaseClass, IRetailBusiness
{
    public double GrossRevenue { get; set; }
    public AudioCDShop(double grossRevenue)
    {
        GrossRevenue = grossRevenue;
    }

    public InvestmentReturnCalculator GetMyInvestmentReturnCalculator()
    {
        return new RetailInvestmentReturnCalculator(this);
    }

}

Client

    static void Main(string[] args)
    {

        #region MyBusines

        List<IBusiness> allMyProfitableBusiness = new List<IBusiness>();

        BookShop bookShop1 = new BookShop(75);
        AudioCDShop cd1Shop = new AudioCDShop(80);
        EngineDesignPatent enginePatent = new EngineDesignPatent(1200);
        BenzolMedicinePatent medicinePatent = new BenzolMedicinePatent(1450);

        allMyProfitableBusiness.Add(bookShop1);
        allMyProfitableBusiness.Add(cd1Shop);
        allMyProfitableBusiness.Add(enginePatent);
        allMyProfitableBusiness.Add(medicinePatent);

        #endregion

        var investmentReturns = allMyProfitableBusiness.Select(bus => bus.GetMyInvestmentReturnCalculator()).ToList();

        double totalProfit = 0;
        foreach (var profitelement in investmentReturns)
        {
            totalProfit = totalProfit + profitelement.GetInvestmentProfit();
            Console.WriteLine("Profit: {0:c}", profitelement.GetInvestmentProfit());
        }

        Console.ReadKey();
    }

Altri suggerimenti

To answer your questions

(1) Try making InvestmentReturnElement contravariant (and all inheritors):

public abstract class InvestmentReturnElement<in T>
{
    public abstract double GetInvestmentProfit(T obj);
}

(2) I might be wrong, but I don't think it is possible to magically create InvestmentReturnElement<T> parameterised for the calling type, when you don't know T statically when you are calling it.

Option 1: Either you can make the business class calculate its profit

public interface IBusiness
{
    double Profit {get;}
    double OtherProfit (SomeCalculatorObject o);
}

Option 2: Or you might conclude that the businesses are so different from profit calculation perspective that you will keep the list of one type separate from the other type and handle them without generics.

Although I couldn't think of a solution that uses generics, I believe that this is what you want:

public abstract class InvestmentReturnElement
{
    protected InvestmentReturnElement(IBusiness business)
    {
        this.Business = business;
    }

    public IBusiness Business { get; private set; }

    public abstract double GetInvestmentProfit();
}

public class RetailProfit : InvestmentReturnElement
{
    public RetailProfit(IRetailBusiness retailBusiness)
        : base(retailBusiness)
    {
    }

    public override double GetInvestmentProfit()
    {
        return ((IRetailBusiness)this.Business).Revenue * 5 / 100;
    }
}

public class IntellectualRightsProfit : InvestmentReturnElement
{
    public IntellectualRightsProfit(IIntellectualRights intellectualRightsBusiness)
        : base(intellectualRightsBusiness)
    {
    }

    public override double GetInvestmentProfit()
    {
        return ((IIntellectualRights)this.Business).Royalty * 10 / 100;
    }
}

Client

List<InvestmentReturnElement> profitElements = new List<InvestmentReturnElement>();

var salesProfitBook = new RetailProfit(bookShop1);
var salesProfitAudioCD = new RetailProfit(cd1Shop);
var intellectualProfitEngineDesign = new IntellectualRightsProfit(enginePatent);
var intellectualProfitBenzolMedicine = new IntellectualRightsProfit(medicinePatent);

profitElements.Add(salesProfitBook);
profitElements.Add(salesProfitAudioCD);
profitElements.Add(intellectualProfitEngineDesign);
profitElements.Add(intellectualProfitBenzolMedicine);

foreach (var profitelement in profitElements)
{
    Console.WriteLine("Profit: {0:c}", profitelement.GetInvestmentProfit());
}

Console.ReadKey();

That's as DRY as it gets, considering that the formula is tied to the target interface.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top