9

I have IPrice interface:

public interface IPrice
{
    decimal TaxPercent { get; set; }
    decimal TotalDebt { get; set; }
    decimal Discount { get; set; }
    DiscountTypeEnum DiscountType { get; set; }
    decimal Commission { get; set; }
    DiscountTypeEnum CommissionType { get; set; }
}

I have interface IExtendPrice and its default implementation:

public interface IExtendPrice
{
    decimal TotalDebtWithoutTax { get; }
    decimal TaxSum { get; }
    decimal DiscountSum { get; }
    decimal CommissionSum { get; }
    decimal DebitPrice { get; }
}

public class ExtendPrice : IExtendPrice
    {
        private IPrice m_Price = null;
        public ExtendPrice(IPrice price)
        {
            m_Price = price;
        }

        public decimal TotalDebtWithoutTax { get { return (m_Price.TotalDebt / (1 + (m_Price.TaxPercent / 100))); } }
        public decimal TaxSum { get { return m_Price.TotalDebt - TotalDebtWithoutTax; } }
        public decimal DiscountSum
        {
            get
            {
                decimal discount = m_Price.Discount;
                if (m_Price.DiscountType == DiscountTypeEnum.PERCENTS)
                {
                    discount = discount * NetoPrice / 100;
                }
                return discount;
            }
        }

        public decimal CommissionSum
        {
            get
            {
                decimal commission = m_Price.Commission;
                if (m_Price.CommissionType == DiscountTypeEnum.PERCENTS)
                {
                    commission = commission * NetoPrice / 100;
                }
                return commission;
            }
        }

        public decimal NetoPrice { get { return CalculateNetoPrice(); } }



        private decimal CalculateNetoPrice()
        {
            decimal debitPrice = 0;
            if (m_Price.DiscountType == DiscountTypeEnum.COINS &&
                m_Price.CommissionType == DiscountTypeEnum.COINS)
            {
                //TotalDebtWithoutTax=X-Discount+Commission
                debitPrice = TotalDebtWithoutTax + m_Price.Discount - m_Price.Commission;
            }
            else if (m_Price.DiscountType == DiscountTypeEnum.COINS &&
               m_Price.CommissionType == DiscountTypeEnum.PERCENTS)
            {
                //TotalDebtWithoutTax=X-Discount+Commission*X/100
                debitPrice = (TotalDebtWithoutTax + m_Price.Discount) / (1 + m_Price.Commission / 100);
            }
            else if (m_Price.DiscountType == DiscountTypeEnum.PERCENTS &&
               m_Price.CommissionType == DiscountTypeEnum.COINS)
            {
                //TotalDebtWithoutTax=X-Discount*X/100+Commission
                debitPrice = (TotalDebtWithoutTax - m_Price.Commission) / (1 - m_Price.Discount / 100);
            }
            else if (m_Price.DiscountType == DiscountTypeEnum.PERCENTS &&
               m_Price.CommissionType == DiscountTypeEnum.PERCENTS)
            {
                //TotalDebtWithoutTax=X-Discount*X/100+Commission*X/100
                debitPrice = TotalDebtWithoutTax / (1 - m_Price.Discount / 100 + m_Price.Commission / 100);
            }
            return debitPrice;
        }
    }

I have classes such as Invoice, PurchaseInvoice, DeliveryNote that each: 1. implements IPrice using its members. 2. implements IExtendPrice using ExtendPrice default implementation. Such class can look like:

public class Invoice : IPrice, IExtendPrice
    {
        public virtual decimal TotalDebt { get; set; }
        public virtual decimal TaxPercent { get; set; }
        public virtual decimal Discount { get; set; }
        public virtual DiscountTypeEnum DiscountType { get; set; }
        public virtual decimal Commission { get; set; }
        public virtual DiscountTypeEnum CommissionType { get; set; }

        private IExtendPrice extendPrice = null;
        public Invoice()
        {
            extendPrice = new ExtendPrice(this);
        }

        public decimal TotalDebtWithoutTax { get { return extendPrice.TotalDebtWithoutTax; } }
        public decimal TaxSum { get { return extendPrice.TaxSum; } }
        public decimal DiscountSum { get { return extendPrice.DiscountSum; } }
        public decimal CommissionSum { get { return extendPrice.CommissionSum; } }
        public decimal DebitPrice { get { return extendPrice.DebitPrice; } }
    }

But noe I have class that called CreditInvoice. It has the following member:

    public decimal TotalCreditSumWithoutTax
    {
        get { return Math.Round(m_TotalCreditSum / (1 + (m_Tax / 100)), 2); }
    }

Which is the same implementation like ExtendPrice's TotalDebtWithoutTax. The difference is with it's name instead of credit - debit/debt.

What is the best practice to enables the use of ExtendPrice in CreditInvoice without changing the names of its members?

Naor
  • 23,465
  • 48
  • 152
  • 268

3 Answers3

18

Use an explicit interface implementation for that member:

class CreditInvoice : IExtendPrice
{
    // ...
    public decimal TotalCreditSumWithoutTax { /* ... */ }


    // Only seen when accessing an instance by its IExtendPrice interface
    decimal IExtendPrice.TotalDebtWithoutTax {
        get { return TotalCreditSumWithoutTax; }
    }
}
Community
  • 1
  • 1
Cameron
  • 96,106
  • 25
  • 196
  • 225
  • 1
    How explicit implementation will help? I don't want to add meanningless member to CreditInvoice. – Naor Jul 27 '11 at 15:09
  • 3
    if it's meaningless why do you want it in the first place. saying this class implements that interface is also saying _all_ mebers from that interface have a meaning for this class. So make up your mind whether it's meaningless (in which case you would have realized there's a design error either in your interfaces or you classes) or if it's indeed meaningful then implement it – Rune FS Jul 27 '11 at 15:46
4

You have to do something like this:

public decimal TotalDebtWithoutTax { get { return TotalCreditSumWithoutTax; } }

Although when you read that, it does sound as though there are some design flaws in your code.


In the comments you make it clear that you want to implement the interface, but not implement a method named TotalDebtWithoutTax. That's not possible. In order to implement an interface, you need to implement all the methods.

My guess is that your choice of name for TotalDebtWithoutTax is too constraining. You are probably objecting to having to implement it with a method name TotalCreditSumWithoutTax. How can a credit be a debit? I guess you'll need to generalise the underlying interface to remove this impedance.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I don't want to add another meaningless member to CreditInvoice. – Naor Jul 27 '11 at 15:06
  • 1
    I don't understand that comment. Do you mean that you don't want to implement `TotalDebtWithoutTax`? – David Heffernan Jul 27 '11 at 15:06
  • 1
    You have two choices. What @Cameron and I offer. Or you could rename `TotalCreditSumWithoutTax` as `TotalDebtWithoutTax`. – David Heffernan Jul 27 '11 at 15:12
  • I prefer other solutions that will not needed to add TotalDebtWithoutTax property because this property can confuse users of this class (CreditInvoice doesn't have Debt). – Naor Jul 27 '11 at 15:13
  • @Naor You are searching for the end of the rainbow. You can't implement an interface without implementing all of its members. – David Heffernan Jul 27 '11 at 15:14
  • So maybe a solution will be to change somehow the interface. – Naor Jul 27 '11 at 15:17
  • 1
    It's hard to know what the solution is because you haven't told us the problem. Start by explaining why you don't want to implement `TotalDebtWithoutTax` in this class. – David Heffernan Jul 27 '11 at 15:18
1

You are looking for the Adapter Pattern. This creates an adapter that implements the interface and then calls into the existing methods on the adapted type.

Ryan Gross
  • 6,423
  • 2
  • 32
  • 44
  • 1
    I disagree with you. It's broken usage of inheritance and polymorphism. Adapter will just make things worse. – Arnis Lapsa Jul 27 '11 at 15:04
  • E.g. - I'm quite sure that there's no such thing as ExtendPrice in his domain and it's purely artificial code to glue things together. Also - if CreditInvoice inherits from Invoice but CreditInvoice shouldn't have any of Invoice members - it's violation of Liskov substitution principle. http://bit.ly/4CeczH Those are 2 main things I see as wrong. – Arnis Lapsa Jul 27 '11 at 15:20
  • Also - "I don't want to add another meaningless member to CreditInvoice" sentence makes me think that IPrice itself is wrong. More likely price should be referenced from Product. Stuff like Comission and TaxRate should be decomposed from Price and referenced in similar fashion. But I'm guessing, don't know his domain... – Arnis Lapsa Jul 27 '11 at 15:24
  • @Arnis L.: CreditInvoice doesn't inherit from Invoice.. where did you see that?.. In who's domain there's no ExtendPrice? You mean mine? I am not sure but also think that maybe IPrice is wrong. Do you have any suggest? – Naor Jul 27 '11 at 15:30
  • @Naor that doesn't matter if instead of inheriting Invoice CreditInvoice implements IExtendPrice interface but does not want to implement it properly - it's still violation. – Arnis Lapsa Jul 27 '11 at 15:34
  • @Naor yes, I do suspect that ExtendPrice is just a placeholder for "additional stuff". do Your clients talk about extended prices? if they don't, it's artificial. I did suggest what I would try to do (4th comment), but it's really hard for me as I got no clues what Your application is trying to solve. – Arnis Lapsa Jul 27 '11 at 15:36
  • @Naor btw, care to explain why debt lives underneath price? that truly confuses me. – Arnis Lapsa Jul 27 '11 at 15:39
  • @Arnis L.: There isn't relation between Invoice and CreditInvoice! don't confuse the names. I really don't understand what you suggest and what is missing for you to understand what I try to do. – Naor Jul 27 '11 at 15:53
  • @Arnis L.: debt is a name. Maybe I should change it to Price and then use Adapter. – Naor Jul 27 '11 at 15:55
  • I already edited my mistake saying that it doesn't matter if class inherits something it is not or says it's implementing interface that it actually does not want to implement - it's same violation. I'm missing good knowledge about what You are trying to accomplish (from business perspective) and some time to pay more attention. Might come back later and post proper answer. Meanwhile - feel free to write gazillion adapters. – Arnis Lapsa Jul 27 '11 at 16:00