-2

Refer to the highest upvoted answer here for why you should make an IPizza interface with a IPizza.Prepare() method.

This is highly upvoted, But I think the answer is flawed. Sure a pizza can be prepared, but so can literally anything. Interfaces are only a guarantee of behaviors, not a guarantee of type, proven here:

    public interface IPizza
    {
        void Prepare();
    }
    public class Sandwich : IPizza
    {
        public void Prepare() { }
    }

If you expect to anywhere use a Collection<IPizza>, you have introduced a vulerability, maybe not security-wise but logic-wise. Because simply naming something IPizza is a guarantee of nothing (regarding type).

The above developer now must reintroduce type checking if (IPizza is Pizza) which per the highest upvoted comment, was a reason to use interfaces.

You might be tempted to add a return type to the Prepare() method, i.e. IPizzaTopping Prepare() but again, until you factor out the interface you can make anything be an IPizzaTopping. Once you factor out one interface, you might as well factor them all out in favor of hard types.

Here is the pizza example using the template pattern, all hard types:

    public class Pizza
    {
        private void Add(PizzaTopping topping) { }

        protected virtual PizzaTopping? Subclass_PrepareTopping(){ return null; } // Template pattern

        public void Prepare()
        {
            ThrowDough();  // private method
            SpreadSauce(); // private method
            SpreadCheese(); // private method

            if(Subclass_PrepareTopping() is PizzaTopping p){ Add(p); } // This might weaken my point by needing a type check, but it is really only a null check which is more a weakness of collections than a weakness of this example.

            Bake(); //private method
        }
    }

    public class PepperoniPizza : Pizza
    {
        protected sealed override PizzaTopping Subclass_PrepareTopping()
        {
            return new PepperoniTopping();
        }
    }

Now any public user of a Pizza can call the Pizza.Prepare() method and be guaranteed to get exactly the correct preparation.

If you say "Well I'll just inherit from PizzaTopping and return a gravy, and aha! I invalidated this whole discussion!"

...well the point of OOP is to create abstractions to the granular level. It's your job to make sure a PizzaTopping contains properties like Collection<Cheese>,Collection<SlicedMeat>, where, when the user desires to create a new PizzaTopping, they must compose the topping from your predefined granular types, which at the granular level are all hard sealed types.

Is this logic wrong in any way? Is there a pizza preparation example that throws a wrench in the above?

12
  • 5
    The point of an interface is that you do not need or want to care about the type that implements it. Creating code that does pointless things behind an poorly named interface is not exploiting an attack vector, it is just misunderstanding a concept and applying it inappropriately. Mar 5 at 7:34
  • 3
    The core issue your question points out seems to be "if I name things inappropriately, then things aren't what they seem". Well, yes. The point is more that your IPizza interface states that "anything can be an IPizza as long as it has a public void Prepare() method". And then you created something that complies with that contract, but arbitrarily used a name that references other food. I fail to see how that is an issue other than one of nomenclature.
    – Flater
    Mar 5 at 8:10
  • 2
    @Christophe: It's not about preference, it's that it brings to light that this is not a technical issue but one of naming and setting your own expectations; which is highly subjective depending on a given context, and therefore prone to opinionated answers. OP chose for the class to implement the interface. If he thinks it's bad that this is the case; they should not have done so. There is nothing more to this.
    – Flater
    Mar 5 at 18:37
  • 3
    @NWoodsman: Who precisely is it that you are trying to defend your application from? Whoever is using your logger is doing so in their own runtime, where they get to call their own shots and suffer their own consequences. If this is your runtime, then you are also the source of the Sandwich class and the one who decided to have it implement the IPizza interface. If that is bad, then you should not have done it. If you cannot trust a developer on your own project to not inject malicious code, that is a very different fish to fry and design patterns are not the appropriate solution.
    – Flater
    Mar 5 at 18:44
  • 2
    @NWoodsman A web api also does not receive classes from its caller. A caller cannot inject a class into your codebase. It can only pass data, which your runtime instantiates into a class that your runtime knows. The only way to inject a new class here is either because your application blindly loads and executes third party DLLs (which is a problem of your own making) or by tampering with your application's DLLs, at which point anything is possible and interfaces are not a particular weak point compared to anything else.
    – Flater
    Mar 5 at 18:51

3 Answers 3

12

I think there is a subtle distinction you're missing between guaranteeing consistency and guaranteeing truth.

When you write public class Sandwich : IPizza you are telling the compiler that a Sandwich is a type of IPizza. The compiler can then prove the consistency of code that passes a concrete Sandwich to something that is designed to work with any IPizza.

What the compiler can't do is verify the absolute truth of the English statement "a sandwich is a type of pizza". The step where real-world concepts are translated into types in the program is unavoidably performed by a human.

The purpose of defining the interface is that it leaves collaborating classes open to extension by providing them with new types of pizza. It defines a contract that those new types of pizza must follow. That contract doesn't tell you anything about whether the result will be pleasant to eat.

You might argue whether a chocolate pizza is a "real" pizza, but if your pizza-making machinery can handle one, then there is no problem declaring class ChocolatePizza : IPizza. However, trying to extend ChocolatePizza from a base Pizza class with default implementation is going to be a mess - it barely shares any of the same implementation, even though it can meet the same interface.

So, even though you didn't plan to support chocolate pizzas, your code was open to extension by someone else implementing that new class.

0
3

From responses to previous answers I'm unsure if this is an honest question or argued in bad faith. Assuming the former:

No language construct can "protect" against developer intent (or incompetence). If someone writes code that compiles but makes no sense ala

public class Sandwich : IPizza

well, that's on the developer. One can write all sorts of stuff that confuses and befuddles in any language (especially {your least favorite language here}). This doesn't make the language constructs used obsolete. It might say something about the author however....

4
  • I demonstrated why the template pattern was better than the most upvoted answer on SO for "why use interfaces". I argue that strong typing protects against silly interface implementations, which also represents slip-ups/bad logic in code. It also demonstrates why interfaces are attack vectors as opposed to strong typing.
    – NWoodsman
    Mar 5 at 4:27
  • Then it appears you've answered your own question. FWIW, an interface defines a contract as noted in a previous answer and is not a "guarantee of behaviors." Mar 5 at 4:41
  • @NWoodsman "attack vectors" imply hostile intent. An interface is in no way a security barrier. If you want to shoot yourself in the foot, no language can stop you, it might, at best, make it more difficult. To protect against actual attacks you need other constructs, like processes or virtual machines. Besides, 'strong typing' is not well defined, and may be interpreted as 'whatever I think is good'.
    – JonasH
    Mar 10 at 15:28
  • @JonasH I was browsing Reference Source and came across this in System.Windows.Controls.ItemsControl class: "We use explicit polymorphism via internal methods for this. Another way would be to define an interface IGeneratedItemContainer with corresponding virtual "core" methods. Base classes would implement the interface and forward the work to subclasses via the "core" methods. While this is better from an OO point of view, and extends to 3rd-party elements used as containers, it exposes more public API. Management considers this undesirable, hence the following rather inelegant code."
    – NWoodsman
    Mar 11 at 1:02
2

Interfaces and generics are two very different, yet complementary things:

  • An interface defines a behavioral contract. A class implementing IPizza must fulfill this contract, i. e. offer a Prepare method that meets the expectations. We don’t care if it’s a kind of pizza or not (for this we have classes). Btw, in real life projects, I never encountered any IPizza: naming of interfaces often describe a behavioral trait: IPrintable, IDeliverable, ISortable, IComparable. The problem with tutorial examples is that they are often obersimplified and thus misleading.
  • Generics are using some kind of generalisation of a family of contracts or types to compose a more complex programming construct (e.g. class). Typically a collection of items that obey to dome sets of interfaces/types whose methods can be combined for making a more complex behavior. They do not substitute to interfaces. On contrary, they may use interfaces to define more complex containers.

By their essence, generics are compile-time type substitution, whereas interfaces offer a run-time polymorphism thanks to their defined contract.

6
  • Given the assertion "the only thing an interface can offer is the expectation of finding a named property or method", how would you prove that "it's more than just a named property, its also a behavior" as you said? I don't see the behavioral aspect other than what's in the name. If the interface is expected to implement a property with a return type, that's somewhat better (if hard typed, i.e. the property isn't itself an interface instance).
    – NWoodsman
    Mar 5 at 1:18
  • I would also say that an ICollection is more of an IPizza than a IComparable, both of the former being abstractions of objects and not behaviors...
    – NWoodsman
    Mar 5 at 1:24
  • @NWoodsman I don‘t have to prove anything: that‘s the definition of an interface. Prove the contrary ;-) Thinking that it‘s just a set of names is a misunderstanding of the concept: you cannot just rely on the syntax of the language and ignore semantics and purposeof the language constructs
    – Christophe
    Mar 5 at 1:30
  • But you proved my point by not having to prove anything =). I guess I will keep on tagging my Cars with IFlyable ... hopes and dreams.
    – NWoodsman
    Mar 5 at 3:26
  • 2
    @NWoodsman The reason why interface definitions don't formalize contracts is because in most cases it wouldn't be feasible to do so. I have seen some attempts in more exotic programming languages to add pre-conditions and post-conditions to interface methods or to mandate things like implementations of certain methods must be side-effect free. But usually that's more work than it's worth and still doesn't cover everything you would expect to be part of the contract. So if you expect that any implementation of IPrintable.print() actually prints something, that must go into the documentation.
    – Philipp
    Mar 8 at 14:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.