On Designing Good Libraries -- Part IV

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 {

   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 (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();  // 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 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

 

Published 26 July 03 12:53 by BradA

Comments

# Frans Bouma said on July 26, 2003 1:32 PM:
"Use non-virtual members unless you have specifically designed for specialization" In VB.NET, afaik, all member methods are virtual. :) Furthermore I don't agree on the favor base class over interface. If you read "Design patterns" by GoF, they say the opposite: favor interface over base class. What to believe? :)
# Ian Griffiths said on July 28, 2003 6:30 AM:
I'm always surprised by the de-emphasis on interfaces. (But then I did COM for years...) The thing is that I've done a lot of development in situations where the set of users of the class library has been relatively small, so we've had the luxury of being able to refactor frequently. I have found that given this flexibility, my designs often migrate towards interface-based designs over time even when I start out with base classes. Also, interface-based designs can be a lot easier to unit test: it's much easier to plug a mock objects when the code under test doesn't have too many preconceived ideas about the types it is dealing with. Of course you can achieve the same result with abstract base classes, but if the only reason for introducing an abstract base class was to decouple the client code from the concrete type, isn't an interface a better choice anyway? I only use inheritance when there's some implementation to inherit.
# Kevin Dente said on July 28, 2003 12:09 PM:
Question about explicit interface implimentation. It seems that when a base class implements an interface explicitly, there's no way for a deriving class to override methods of the interface and chain to the base class implementation. For example, you can't do this: ((IMyInterface)base).DoStuff(). As far as I can tell, the only option in this case is to completely reimplement the base class functionality. Is this by design?
# Rick Byers said on July 28, 2003 1:39 PM:
"Overriding: Don’t change the semantics of member, follow the contract defined on the base class". This is obviously a very important rule in OO design. What do you think of the reccomended practice of overriding Object.Equals to provide value equality when the default implementation only provides reference equality? Isn't that changing the semantics of Equals?
# Andy Smith said on July 28, 2003 1:53 PM:
Kevin: There's no built in way to call the base class's explicitly defined interface method. If you want to completely override it, you can simply explicitly implement it yourself. But if you positively, absolutely need to also call the base class implementation... I found a way that uses reflection to do so: http://weblogs.asp.net/asmith/posts/10005.aspx It uses the System.Reflection.InterfaceMapping class.
# Terence Craig said on July 29, 2003 5:54 PM:
These are great articles please keep them coming. Regarding your recommendations on base classes vs Interfaces. “Avoid having both base class and interfaces • Adds confusion about which to use • Component vs. IComponent • Little advantage” My experience is that having a base class and an interface is almost required if you are developing extensible class libraries or frameworks. By encouraging end users of the framework to use the framework's public interfaces to access objects a couple of nice benefits accrue; it gives you as the framework vendor more opportunity to protect them from implementation changes as the framework evolves, it also allows you to provide/encourage the use of interface friendly patterns Factory, etc. By providing both an interface and a base class you allow the extension of your framework by derivation if the specialization needs are small or a complete re-implementation of your interface by sophisticated extenders whose needs aren’t met by your existing implementation architecture. One nice thing about this approach is that it keeps the level of complexity appropriate to the level of customization. Cheers, Terence
# Kevin Hector said on July 30, 2003 3:49 AM:
Brad, great stuff. Are these guidelines going to make it into a revised design guidelines document in the public domain? I seem to remember you said you were preparing them for internal use only.
# Gerke Geurts said on July 30, 2003 4:13 PM:
I agree with Terence's posting about base classes vs. interfaces. I prefer designing an interface so that framework users are not necessarily forced to use my base classes. Adding a base class with a default implementation of the interface provides an easy starting point for those who don't need to use another base class.
# Frank Hileman said on August 4, 2003 11:23 AM:
I just want to log my vote against interfaces, in favor of base classes, for the same reasons Brad points out: versioning, ease-of-use. Excessive use of interfaces can be an indication of over-design: because they seem more flexible (choice of base class), and designers want to provide maximum flexibility, they go for the interface. But they are less flexible for the designer, since you cannot change the interface in a newer version. Unless you can think of a specific situation where someone will not be able to inherit from your base class, I think only the base class and not the interface should be provided. Regarding depth of class hierarchies. It would be nice if we could have arbitrary depth of inheritance within a class hierarchy, while only making certain base and leaf classes public. While I agree that the public classes in a library should correspond to "real world" (whatever that means) objects and concepts, within the library the internal classes are simply arbitrary units of code reuse. For example, I may wish to factor common code into several classes, four levels deep, but only expose some classes at the leaf level, or at the leaf and one above that. I don't understand why we are prohibited from doing that. The internal inheritance hierarchy has nothing to do with the public api of a class library. We should be able to change it without affecting users of the library. I know that other do not agree with this, but inheritance is just a form of code reuse. Not "idea reuse".
# Brad Abrams said on April 10, 2004 10:39 PM:
# Brad Abrams said on April 13, 2004 11:28 PM:
# C#deSamurai said on August 26, 2004 12:35 PM:
# Tutto fa .NET said on August 27, 2004 12:45 PM:
New Comments to this post are disabled

Search

Go

This Blog

Syndication

Page view tracker