Base class should not use derivatives

Rule ND1201 Object Oriented Design

The open/closed principle states: software entities should be open for extension, but closed for modification. http://en.wikipedia.org/wiki/Open/closed_principle

Creating or modifying a derived class shouldn't require modifying the base class. A base class using its derivatives likely will, making extension no longer simple: bad design.

No warning when the derived class used is nested private in the base class (an encapsulated implementation detail).

How to fix

Understand the need for using derivatives, imagine a new design, refactor. Typically, encapsulate base-class access to derived classes behind an abstract or virtual method. Conditions on typeof(DerivedClass) need urgent refactoring the same way. A base class creating derived instances calls for the factory method pattern http://en.wikipedia.org/wiki/Factory_method_pattern or the abstract factory pattern http://en.wikipedia.org/wiki/Abstract_factory_pattern

Estimated Debt: 3 minutes per derived class used + 3 minutes per derived class member used.

CQLinq source

This rule is written in CQLinq, C# LINQ over the NDepend code model. It ships with every new NDepend project: you can read it, adapt it, or use it as a starting point for your own custom rules.

// <Name>Base class should not use derivatives</Name>
// <Id>ND1201:BaseClassShouldNotUseDerivatives</Id>
warnif count > 0 
from baseClass in JustMyCode.Types
where baseClass.IsClass && baseClass.NbChildren > 0 // <-- for optimization!
let derivedClassesUsed = baseClass.DerivedTypes.UsedBy(baseClass)
   // Don't warn when a base class is using nested private derived class
   .Where(derivedClass => 
            !(derivedClass.IsNested && 
              derivedClass.Visibility == Visibility.Private && 
              derivedClass.ParentType == baseClass))
where derivedClassesUsed.Count() > 0

let derivedClassesMemberUsed = derivedClassesUsed.SelectMany(c => c.Members).UsedBy(baseClass)
orderby derivedClassesMemberUsed.Count() descending

select new { 
   baseClass, 
   derivedClassesUsed,
   derivedClassesMemberUsed,

   Debt = 3*(derivedClassesUsed.Count()+derivedClassesMemberUsed.Count()).ToMinutes().ToDebt(),
   Severity = Severity.High
}

//<Expl>
// The *base {0}* should not rely on its derivative{1?:s}, specifically the *{1}*,
// because it should remain *open* for the creation of new derived 
// classes without requiring modifications.
//</Expl>

//<Desc>
// The **open/closed principle** states: *software entities should be open
// for extension, but closed for modification*.
// http://en.wikipedia.org/wiki/Open/closed_principle
//
// Creating or modifying a derived class shouldn't require *modifying* the
// base class. A base class using its derivatives likely will, making
// extension no longer simple: bad design.
//
// No warning when the derived class used is nested private in the base class
// (an encapsulated implementation detail).
//</Desc>

//<HowToFix>
// Understand the need for using derivatives, imagine a new design, refactor.
// Typically, encapsulate base-class access to derived classes behind an
// abstract or virtual method. Conditions on *typeof(DerivedClass)* need
// *urgent refactoring* the same way. A base class creating derived instances
// calls for the *factory method pattern*
// http://en.wikipedia.org/wiki/Factory_method_pattern
// or the *abstract factory pattern*
// http://en.wikipedia.org/wiki/Abstract_factory_pattern
//
// Estimated Debt: 3 minutes per derived class used + 3 minutes per derived
// class member used.
//</HowToFix>

Object Oriented Design: all 14 items