As always, comments welcome!
Object Oriented Design
• This is not an OO Design class
• A type represents a set of responsibilities
• Modeling the real world
• Group related responsibilities together in a class
• Only 5% of developers will ever explicitly extend base classes
• But 100% will use classes
Danger of over design
• The most common library design mistake
• A new dev/pm is designing the widget feature
• Wants it to be extensible and feature rich
• The reality of shipping hits
• Design only partly thought out
• Long bug tail
• Forced hard cuts
• We ship it cumbersome and broken
• In V.next
• We now we know the extensibility needed
• But blocked by the broken design the shipped
Abstract and Base classes
• Base classes serve as the root of an inheritance hierarchy
• Abstract classes are a special kind of base class that are non-instantiable and may contain members that aren’t implemented
• Prefer broad, shallow hierarchies
• Less than or equal to 2 levels – Rough rule!
• Contracts and responsibilities are difficult to maintain and explain in deep complex hierarchies
• Consider making base classes not constructible
• Make it clear what the class is for
• Provide a protected constructor for subclasses to call
• System.Exception should not have had a public constructor
Virtual, Abstract and Non-virtual
• Virtual members are points of specialization or callbacks in your code
public virtual int Length { get {..} }
• Abstract members are required points of specialization in your code
public abstract int Read()
• Non-virtual members cannot be overridden in derived types
public string Remove (int index, int count)
public class Foo {
public override string ToString() {
return "foo";
}
public class Bar : Foo {
return "bar";
What is printed out?
Bar b = new Bar ();Console.WriteLine (b.ToString());
Foo f = b;Console.WriteLine (f.ToString());
Object o = b;Console.WriteLine (o.ToString());
• They all print “bar”. Why?
• Method call virtualizes at runtime
• The static type doesn’t matter
• This is the danger and power of virtual methods
• Danger: Owner of base classes cannot control what subclasses do
• Power: Base class doesn’t have to change as new subclasses are created
Overriding
• Don’t change the semantics of member
• Follow the contract defined on the base class
• Don’t require clients to have knowledge of your overriding
• Consider whether you should call the base implementation
• Favor calling it unless you have good reason not to
Virtual and non-virtual
• Use non-virtual members unless you have specifically designed for specialization
• Have a concrete scenario in mind
• Write the code!
• Think before you virtualize members
• Modules that use references to base types must be able to use references to derived types without knowing the difference
• Must continue to call in the same order and frequency
• Cannot increase or decrease range of inputs or output
• See the Liskov Substitution Principle (http://www.brent.worden.org/tips/2000/liskovSubstitutionPrinciple.html)
Abstract Members
• Methods, Properties and Events can be abstract
• Use abstract members only where it is absolutely required that subclasses provide a custom implementation
• Only use when the base class cannot have any meaningful default implementation
• Default to making members non-virtual
• Make it virtual if it is designed to be specialized by subclasses
• Make it abstract if no meaningful default implementation is possible
• Unless versioning issues prohibit it, in which case throw a NotImplementedException
Interfaces versus Base Classes
• Favor using base classes over interfaces
• Base classes version better in general
• Allows adding members
• Members can be added with a default implementation
• Avoids incompatibilities common in ActiveX
• Interfaces are good for versioning behavior (changing semantics)
• Avoid having both base class and interfaces
• Adds confusion about which to use
• Component vs. IComponent
• Little advantage
• Consider using Aggregation
• Don’t use attributes where a contract is needed
Explicit method implementations
• Implementing members of an interface “privately”
• Not a security boundary!
• Only accessible when cast to the interface type
• Hides implementation details
• Clean public interface
• IConvertible on Int32, etc
• Differentiates implementations
• Simpler strong typing
• The 19 ToXxx methods on IConvertible don’t “pollute” the Int32 public view
• But they are there when cast to IConvertible
• Solution: Implement them privately
public struct Int32 : IConvertible, IComparable { public override string ToString () {..} int ICovertible.ToInt32 () {..} ...}
int i = 42;i.ToString(); // worksi.ToInt32(); // does not compile((IConvertible) i).ToInt32(); // works
• Interfaces developed by different groups can have the same signature for different meanings
• Draw() a picture and Draw() a gun
• Frequently you want to differentiate the implementation
• Explicit method implementations enables this
• Avoids us recommending “unique” names in interfaces
interface IGraphics {
void Draw();
Brush Brush { get; set; }
Pen Pen { get; set; }
interface IBandit {
void Duel(IBandit opponent);
void Rob(Bank bank, IBandit[] sidekicks);
class Bandit: IBandit, IGraphics {
void IBandit.Draw() {...}
void IGraphics.Draw() {...}
• There is a natural tension between generic typing and strong typing
• List of Object vs. List of Employees
• Generic typing
• Polymorphism
• Strong typing
• More “RAD” experience
• Avoids boxing of value types
• Avoids ugly casts
• Often mutually exclusive
• You can’t have both of these in the same class
• They differ only in return type
This gives you a compile time error
public object this [int index] {
get{..} set{..}}public string this [int index] { get{..} set{..}}
This works:
public class StringList : IList public string this [int index] { get{..} set{..} } object IList.this [int index] { get{..} set{..} }
StringList slist = new StringList();string s1 = slist[0];IList genList = slist;string s2 = (string) genList[0];
Of course Generics are a better solution