I\'m having a hard time describing this problem. Maybe that\'s why I\'m having a hard time finding a good solution (the words just aren\'t cooperating). Let me explain via cod
Most of the time, the "system" runs fine until Grapes are used. Then parts of the system act inappropriately, pealing and/or coring grapes when it's not needed or desired.
Seems to me like the problem is an introduction of a new data type. You might want to consider modeling your classes using a kind of visitor pattern, particularly since this pattern is intended for related objects with a fixed number of well-defined data types:
public abstract class Fruit {
public abstract T Match(Func f, Func g, Func h);
public class Apple {
// apple properties
public override T Match(Func f, Func g, Func h) {
return f(this);
}
}
public class Banana {
// banana properties
public override T Match(Func f, Func g, Func h) {
return g(this);
}
}
public class Grape {
// grape properties
public override T Match(Func f, Func g, Func h) {
return h(this);
}
}
}
Usage:
public void EatFruit(Fruit fruit, Person p)
{
// prepare fruit
fruit.Match(
apple => apple.Core(),
banana => banana.Peel(),
grape => { } // no steps required to prepare
);
p.Eat(fruit);
}
public FruitBasket PartitionFruits(List fruits)
{
List apples = new List();
List bananas = new List();
List grapes = new List();
foreach(Fruit fruit in fruits)
{
// partition by type, 100% type-safe on compile,
// does not require a run-time type test
fruit.Match(
apple => apples.Add(apple),
banana => bananas.Add(banana),
grape => grapes.Add(grape));
}
return new FruitBasket(apples, bananas, grapes);
}
This style is advatageous for three reasons:
Future proofing: Lets say I add a Pineapple
type and add it to my Match
method: Match(..., Func
. Now I have a bunch of compilation errors, because all current usages of Match
pass in 3 params, but we expect 4. The code doesn't compile until fix all usages of Match
to handle your new type -- this makes it impossible to introduce a new type at the risk of not being handled in your code.
Type safety: The Match
statement gives you access to specific properties of sub-types without a runtime type-test.
Refactorable: If you don't like delegates as shown above, or you have several dozen types and don't want to handle them all, its perfectly easy to wrap those delegates by a FruitVisitor
class, so each subtype passes itself to the appropriate method it the FruitVisitor.