0

Please see the Rules Design Pattern on this webpage: http://www.michael-whelan.net/rules-design-pattern/. It has classes like this:

public interface IDiscountRule
{
    decimal CalculateCustomerDiscount(Customer customer);
}

public class BirthdayDiscountRule : IDiscountRule
{
    public decimal CalculateCustomerDiscount(Customer customer)
    {
        return customer.IsBirthday() ? 0.10m : 0;
    }
}

Why would interfaces be used for this instead of inheritance i.e. should BirthdayDiscountRule inherit IDiscountRule (calling it DiscountRule instead)?.

w0051977
  • 7,031
  • 6
  • 59
  • 87
  • what if developer wanted to inherit `BirthdayDiscountRule` from some other class (not from `IDiscountRule`)? See [share method logic along classes without inheriting from abstract class](https://softwareengineering.stackexchange.com/questions/346917/share-method-logic-along-classes-without-inheriting-from-abstract-class) – gnat Jul 10 '17 at 09:14
  • 4
    Possible duplicate of [Composition over inheritance but](https://softwareengineering.stackexchange.com/questions/302045/composition-over-inheritance-but) – Timothy Truckle Jul 10 '17 at 09:43
  • 6
    I think you might be a little confused on OOP terminology. Implementing an interface *is* inheritance. – Greg Burghardt Jul 10 '17 at 12:12
  • 1
    Keep in mind that interfaces are limited to only few languages; from the perspective of language designers, interfaces are simply a specialized form of base class. – Frank Hileman Jul 10 '17 at 14:22
  • 1
    Expanding on @FrankHileman's point: an interface is just an abstract base class that contains no data or implementation and which is allowed to be multiply inherited in languages that otherwise only have single inheritance. This means that as long as your base doesn't need data or implementation shared between its subclasses, the better question is *why would you ever not use an interface for your abstract base type*? – Jules Jul 10 '17 at 16:52
  • @Jules The most common reason is to provide more functionality than provided in an interface (more that none, that is). You can provide common code as well as enforce contracts using wrapper methods. Such a base class is not good for inheritance, but is perfect for reuse using traditional field based composition, which makes more sense for mathematical types of objects. – Frank Hileman Jul 10 '17 at 18:27
  • Limiting each class to a single base class prohibits mixin styles of programming. Working with a language that allows such a style makes it easier to see why multiple inheritance is important, and how interfaces are often a clumsy work around. – Frank Hileman Jul 10 '17 at 18:34

5 Answers5

8

There are a number of reasons.

  1. In c# you can inherit many interfaces, but only one base class.

  2. Inheritance has lost popularity as a method of sharing code against composition.

Say we do have some base logic we want all discounts to apply and we put it in a BaseDiscount class as you suggest. But then we find that its not as universal as we thought and we don't want to use it for all discounts.

Well we can go back and add a BaseBaseDiscount and redefine any lists of discounts to be of that type instead of BaseDiscount. But the most flexible version of this would be to have a Base class with no code, just the exposed method/properties, which is essentially an interface.

Additionally it can be hard to determine what code is actually running if you have a deep inheritance chain.

With composition I could have:

public class BirthdayDiscount: IDiscount
{
    public BirthdayDiscount(IDiscount baseDiscount)
    {
         this.baseDiscount = baseDiscount;
    }

    public decimal GiveDiscount(decimal price)
    {
        return baseDiscount.GiveDiscount(price) * birthdayDiscount;
    }
}

Now I can chop and change the baseDiscount class I pass into the constructor at run time, rather than having the change the class inheritance at compile time.

Ewan
  • 70,664
  • 5
  • 76
  • 161
  • The only problem with this answer is that inheritance is a form of composition. Some languages make mixins easier to use; are they inheritance or composition? They are both. Your answer is good for those languages that restrict inheritance. – Frank Hileman Jul 10 '17 at 14:19
  • https://en.wikipedia.org/wiki/Composition_over_inheritance – Ewan Jul 10 '17 at 16:49
  • Unfortunately wikipedia tends to reinforce common "principles" based on language limitations. Mixins are compatible with OO design, and traditional inheritance is equivalent to a combination of field based composition and delegation based method forwarding. Abstractly, inheritance is a type of composition most especially in those languages allowing multiple inheritance, but this fact is harder to see if you restrict yourself to languages prohibiting that. – Frank Hileman Jul 10 '17 at 18:18
  • 1
    im sure you are right, but im just using the terms as they are in common parlance on this topic. and the question is tagged c# – Ewan Jul 10 '17 at 18:20
4

Because interfaces declare a specific function signature rather than a specific algorithm to compute that function.

To elaborate: this is exactly what interfaces are for. Presumably there are various reasons to give discounts (maybe everyone gets 1% off on a holiday, or there's a promotion on energy drinks, or whatever), and they all have different conditions and discounts, so they would be modelled by different methods in different classes, all of which implement IDuscountRule. All these methods yield a decimal, so they fulfil the contract of CCD, but they do it in different ways.

Inheritance would mean that they all do it the same way, and that's not what the problem requires.

Kilian Foth
  • 107,706
  • 45
  • 295
  • 310
  • Thanks. Could you clarify what you mean by: "a specific algorithm to compute that function.". Inheritance allows you to override. – w0051977 Jul 10 '17 at 09:20
  • 2
    @w0051977 A signature essentially names *types* only: "take something (in this case, nothing) and produce a number". An algorithm defines types *and values*, e.g. "return 0.9 * normal price". You could override the algorithm in the superclass with one of your own, but if *all* subclasses override it, then there isn't much point in having it to begin with. – Kilian Foth Jul 10 '17 at 09:42
  • 1
    Unfortunately the way you phrase your answer doesn't distinguish between interfaces and base classes. Interfaces are simply a work around for a lack of multiple inheritance in a few languages. – Frank Hileman Jul 10 '17 at 14:12
0

I think of interfaces as a way of working around the limitation, in the two most popular modern languages, which is not to support multiple inheritance, e.g. a class cannot extend two classes.

In the example code, the interface is intended to be used to handle the computing of a discount based on some rule. Somewhere in the app there will be an object of type Person, Customer, etc. and that class will say "implements IDiscountRule" which means that the rest of the code in the app can call a "CalculateCustomerDiscount" method to obtain the discount.

There may be a discount because of birthday, a discount because of student status, or military status, etc. All of those would be coded as extending "IDiscountRule", but the "CalculateCustomerDiscount" in those classes would be specific to whatever discount is provided.

Lastly, the object that represents a customer does not "extend" but instead "implements "CalculateCustomerDiscount". If you did extend, your customer class could not extend anything else, and the discount logic is just one of probably many logical dimensions of customer.

Thomas Carlisle
  • 1,293
  • 9
  • 11
  • I don't understand why people think interfaces are more special than a work around to a language limitation. Your answer is exactly correct. – Frank Hileman Jul 10 '17 at 14:14
  • @FrankHileman, In C, I cannot use line numbers and constructs like "goto 10". Is this a limitation of C? Likewise, interfaces have been added to various languages as a way of offering the benefits of multiple inheritance, without the downsides of actually using inheritance. That doesn't sound like a limitation to me: it's an enhancement. Interfaces do have one limitation though: they are difficult to extend without introducing breaking changes. That's why Java, and soon (v8) C#, added default method implementations to interfaces. – David Arno Jul 10 '17 at 17:05
  • I simplified the many benefits to the one that I think learners will best understand, especially if they have prior experience in languages that do offer multiple inheritance. For me personally, thinking of interfaces as a work-around to that "problem" eventually it sunk in that the languages not allowing multiple inheritance is not a mistake but a blessing, and interfaces end up being cleaner and a better design. I've never ran across a true case where multiple inheritance was needed. – Thomas Carlisle Jul 10 '17 at 17:47
  • @DavidArno The downsides of multiple inheritance were already solved, from a language perspective, when Java was created. That is why many consider the omission of this feature a flaw. – Frank Hileman Jul 10 '17 at 18:15
  • @ThomasCarlisle If you have seen classes that forward many methods to child instances, without any processing, typically that is a situation that would have benefitted from a mixin style of programming. – Frank Hileman Jul 10 '17 at 18:31
0

Separate the things that change from the things that don't. Designed in such a way, code changes/new code becomes isolated with a defined way of creation. Monolithic inherited classes reduce the ability to maintain code effectively. Think about it as putting the work in now so you can be lazy in the future :D

-1

The other two answers, and the "possible duplicate question" link adequately address the problems of using base classes, rather than interfaces. But I'd like to address the code in the question itself.

The first thing to note is the return customer.IsBirthday() ? 0.10m : 0; line.

This code reveals the fact that the IsBirthday() method is likely tightly coupled to DateTime.Now. This makes testing harder than it need be, eg without changing the system time (or only running the test once every four years), there's no easy way to test that it correctly handles a customer born on 29th February. The current date should be passed to IsBirthday().

Secondly, if the current date is passed to it, CalculateCustomerDiscount becomes a pure function, thus can be made static:

public static decimal CalculateBirthdayDiscount(Customer customer, DateTime date) =>
    customer.IsBirthday(date) ? 0.10m : 0;

And it can be referenced via a Func<Customer, DateTime, decimal> delegate throughout the code. This removes the need to define the IDiscountRule interface.

Not only do we simplify the code by removing an unused interface, we remove the need to have to create mocks of that interface in test code. A simple mock method can be used instead, once again simplifying things, potentially even removing the need to use a mocking framework.

David Arno
  • 38,972
  • 9
  • 88
  • 121