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?
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.Sandwich
class and the one who decided to have it implement theIPizza
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.