On Designing Good Libraries -- Part IV

As
always, comments welcome!

 

Object Oriented
Design
"urn:schemas-microsoft-com:office:office" />


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 {

   public override string ToString()
{

      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 (https://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(); //
works
i.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
Draw();

   void Duel(IBandit
opponent);

   void = st2 ns = "urn:schemas:contacts" />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 “ns = "urn:schemas-microsoft-com:office:smarttags" />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