The Design of Software (CLOSED)

A public forum for discussing the design of software, from the user interface to the code architecture. Now closed.

The "Design of Software" discussion group has been merged with the main Joel on Software discussion group.

The archives will remain online indefinitely.

Reference for UI coding patterns / best practices

So let's say I've got a good handle on object-oriented design principles, design patterns, and all that good stuff that goes on under the hood.  Problem is, everytime I want to design the GUI, I'm unsure of the best way to code it.

For example, I've got a main form, and then other forms that pop up for different tasks.  The way the Visual Studio .NET form designer works, all of these forms will inherit from System.Windows.Forms.Form.  But my forms are actually very different - they have different constructor signatures, because they require different initialization data, etc.  So if I make them subclasses of Form but then extend their public interfaces, doesn't that violate the, er... I think it's the Liskov Substitution Principle (should be able to use subclasses via the base class interface)?

So instead do I make each "form" its own class, and then contain a Form as a member variable?

This is my currently pressing question, but the REAL question is whether anyone has a recommendation on a book or website that describes the current thinking on how to create an object-oriented GUI.  Most UI "patterns" or "best practices" books and sites seem to focus on the HCI aspects of high-level "design"... I'm interested in something along the lines of Meyer, Larman, Martin, etc., but specifically about coding the UI.

I mainly work in C# right now, but I can't imagine this topic is particularly platform-dependent, and I've no problem reading Java or anything else, really.

Thanks for any suggestions!
Jesse Smith
Wednesday, June 22, 2005
 
 
>>So if I make them subclasses of Form but then extend their public interfaces, doesn't that violate the, er... I think it's the Liskov Substitution Principle (should be able to use subclasses via the base class interface)?

I believe that is exactly what you have.  You can treat your derived form as if it was a System.Windows.Forms.Form.  That is because your derived form has to fulfill Form's contract.

You do have to explicitly call the constructor of the derived form (which makes sense right because you are creating a derived form not Form).  But once you have it you can treat it like any old Form if you need (call the Show method for instance).

Does that make sense?
Mark Flory Send private email
Wednesday, June 22, 2005
 
 
Liskov doesn't apply to constructors. You'll always know the concrete type when constructing an object, so the kind of substitution that LSP talks about never happens there.

I haven't seen much in the realm of UI patterns in books yet. Probably the most important paper I've seen is Michael Feather's "The Humble Dialog Box":

http://www.objectmentor.com/resources/articles/TheHumbleDialogBox.pdf
Chris Tavares Send private email
Wednesday, June 22, 2005
 
 
Ok, I guess I wasn't thinking completely clearly when I wrote the above.  Obviously, I will need to call the subclass's constructor with whatever initialization arguments it requires.

But even so, if I make each form a subclass of System.Windows.Forms.Form, and I want to abide by the LSP, doesn't that mean none of my forms can have new public methods?  What if I want my forms to send messages to each other, beyond Show()/Close(), etc.?  I'm not enormously experienced at GUI design, but won't situations come up where I need my forms to talk to each other?

Maybe I just haven't thought this through enough yet...

I'll read the paper you mentioned, Chris - thanks!
Jesse Smith
Wednesday, June 22, 2005
 
 
"So if I make them subclasses of Form but then extend their public interfaces, doesn't that violate the, er... I think it's the Liskov Substitution Principle (should be able to use subclasses via the base class interface)?"

I'm not following.  How would extending the interface of one of your forms make the Forms.Form base class interface methods unusable in the subclass form?  You will have that as a common denominator in all your forms.  You can add as many public methods to subclassed forms as you want, so long as it still implements methods of the ancestor classes.  Or is the LSP rule saying something different?
Herbert Sitz Send private email
Wednesday, June 22, 2005
 
 
==>and I want to abide by the LSP, doesn't that mean none of my forms can have new public methods?

I've never heard of LSP (I'll be googling soon), but if you ask me, that would make the whole object oriented thing, and in particular the inheritance thing, completely useless. That's the whole idea as I see it. You want to inherit a bunch of functionality, and *extend* it with some new stuff ("new public methods").

WTF good is inheritance if you can't extend? Why inherit then? Why not just use an instance of the base clase? This makes no sense at all to me.

I'm off to google LSP. Maybe it'll make more sense to me after. Probably not.
Sgt.Sausage Send private email
Wednesday, June 22, 2005
 
 
As I understand it, the Liskov Substitution Principle says, roughly, that a derived type may be used anywhere a base type may be used.  That doesn't mean derived types can't add to the interface, but any additional interface elements won't be exercised by code working with the base class interface.

So adding extra methods in a derived class interface won't violate the LSP per se, but unless they are used to implement the functionality behind the base class interface, they'll essentially just sit there and do nothing (as far as code working with just the base class interface is concerned).
Mike Bland Send private email
Wednesday, June 22, 2005
 
 
Hmm... so the LSP states that you should be able to use the subclass through the base class interface without unexpected side-effects.  This is what allows polymorphism to work. 

I've also seen it stated as a best practice to do:

IList theList = new ArrayList();

rather than:

ArrayList theList = new ArrayList();

because subsequent code can then manipulate the list via the IList interface and not get broken if you later decide to use a customized list class instead of the system ArrayList.

But as pointed out here, the LSP does not say you can't add more methods to your subclasses.  The .NET Framework obviously does this: the great-grandfather class of Form is Control, which lacks, for example, the public Close() method that Form provides.

But the question this raises for me is: in which situations do you want to manipulate classes only through their base class interface and in which situations is in acceptable/preferable to work at the lower abstraction level of the subclass?  I guess it depends on whether I intend to take advantage of polymorphism or not.

Maybe I should forget about worrying about UI patterns and get back to basic OOP principles.  I've read Larman's "Applying UML and Patterns" book.  I suppose I should tackle Bertrand Meyer's "OO Software Construction" next?
Jesse Smith
Wednesday, June 22, 2005
 
 
>> in which situations do you want to manipulate classes only through their base class interface and in which situations is in acceptable/preferable to work at the lower abstraction level of the subclass?

Typically where I find this really usefull is where you have a bunch of derived classes that you need to perform the same Overloaded operation on.

For example, you have a fruit base class with a virtual Eat method.  All of your derived fruits have their own override Eat method (because you don't eat an orange like a bannana). 

Now, with a basket of fruit (some kind of List or Array) you can iterate through all the members and tell them to Eat without having to figure out what tyep of fruit it is and then telling it Pear.Eat, Peach.Eat, etc.  You just say Fruit.Eat.

I could probably supply a more realistic example but this one makes me smile.
Mark Flory Send private email
Wednesday, June 22, 2005
 
 
Guess the rule of thumb for me has always been that if the abstraction is too abstract, don't use it.  You can't factor out commonality that isn't there.

But the same object will appear at different levels of abstraction in the same overall system sometimes, depending on the context in which it's being manipulated.  A Form is just a Control in some circumstances, like when resizing or repainting a window, or notifying observers that an event has been fired.  In another context, it's an object intended to receive a specific type of user input and send it off for processing, so it's a Form.

So, I don't know how much that advice helps, but the point is that you just need to know how your system breaks down into its component subsystems, and how your objects function and interact as they flow through these different components.  Then tailor your objects to fit as necessary.
Mike Bland Send private email
Wednesday, June 22, 2005
 
 
Try this article and look for other related links within the article:

http://www.martinfowler.com/eaaDev/PresentationModel.html
Roger Jack Send private email
Wednesday, June 22, 2005
 
 
What I found for LSP was this:

"If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T."

http://c2.com/cgi/wiki?LiskovSubstitutionPrinciple

If that's what the rule is, then it seems more like a rule regarding the underpinnings of OOP theory, not a rule that's meant to be observed while programming an application.  It's just a rule that defines what it means to be a subtype in the world of OOP.

Seems to me link in the Delphi and .NET frameworks (and any other OOP frameworks?) it is actually not even so easy to create subclasses that would not qualify as subtypes under above definition.  If methods of a base class were virtual and you overrode them in a descendant to implement polymorphic behavior then your subclass would not then be a "subtype" according to above definition.  (Is that right?) 

But in any case the LSP doesn't seem to be a normative one.  That is, it doesn't seem to describe how you _should_ do something.  Rather, it's simply a rule that describes what must be the case in order for one object to be a 'subtype' of another object.  Doesn't say anything about whether all our subclassed objects _should_ be subtypes of their base objects at all.  In fact, if what I said in previous paragraph about polymorphism and LSP is true, then you almost certainly should _not_ want all your subclasses to be subtypes as defined by LSP.

If I were just starting out with OOP I would try stay away from this sort of theoretical stuff and get going with some basic "how to" stuff and just play around with some oop code.  I'm not that far along myself, and I admit I did buy the B. Meyer book when I was just starting.  But the 'dummies' type books and just working with code were much more helpful at the very beginning. 

Of course, I could be way off track.  Hopefully someone who knows more about this will point out any mistakes in what I've just said.
Herbert Sitz Send private email
Wednesday, June 22, 2005
 
 
Actually, Herbert, I think the LSP is enables polymorphic behavior to work.  An informal version of the Liskov Substitution Principle is "functions that use a reference to a base class must be able to use objects of a derived class without knowing it".  Craig Larman's "Applying UML and Patterns" (a very good intro to OO design) gives this example:

public void addTaxes( ITaxCalculatorAdapter calculator, Sale sale)
{
  List taxLineItems = calculator.getTaxes( sale );
  // ...
}

This lets you use a variety of different tax calculators (maybe depending on which state the customer is in), or even a mock calculator during testing.  As long as you wrap the calculator in an adapter class that implements the ITaxCalculatorAdapter interface (which has a getTaxes method that returns a List of tax line items), you don't have to know which calculator you are using.

So the LSP is in some sense a normative principle, in that it defines a statement that must be true to use subclasses for polymorphic behavior.

I found a good article at:
http://www.objectmentor.com/resources/articles/lsp.pdf
There doesn't seem to be an author listed, but I think it is by Robert Martin.  He explains it much better than I can!

Thanks for the interesting discussion, everyone!
Jesse Smith
Thursday, June 23, 2005
 
 
" Actually, Herbert, I think the LSP is enables polymorphic behavior to work."

Okay. Thanks, I'll go look at your links.

If that's what LSP is then it seems to me like satisfaction of the normative element of the principle is taken care of by the compiler.  C# or Delphi will give you compile errors if you try to compile code that violates the principle, won't they?
Herbert Sitz Send private email
Thursday, June 23, 2005
 
 
Okay, another try; I guess I still had it wrong.

"public void addTaxes( ITaxCalculatorAdapter calculator, Sale sale)
{
  List taxLineItems = calculator.getTaxes( sale );
  // ...
}"

If LSP is just saying that whatever CalcAdapter interface you plug in must actually get the tax total for that particular Sale (i.e., work as expected), then that's something that you could violate, but borders on being so obvious that it goes without saying (which doesn't mean it's not an appropriate principle to state).

But then it also applies only to cases where you have subclasses that have different implementations of a method than the base class (not when you're using an inherited implementation) or for interfaces where you have to explicitly code implementation separately for each class that implements the interface. 

This will not generally happen with the original question re: adding new methods to subclasses of the base Forms.Form class in .NET.  Unless you override the bae class methods, your subclasses will all use _same_ implementation of each method that is derived from the base class (thus automatically satisfying LSP) and any methods you add only in subclass aren't even covered by LSP because there's no base class that they descend from; they're unique to that class.  No? 

I guess rule as applied to interfaces would be a little different, because then you are actually coding the implementation of each interface method separately for each class that implements the interface, and need to make sure all act as "expected".  But in case of something like descendants of base form class in .NET (or Delphi), there's no polymorphism, all forms by default use the implementation of the base form's methods.  E.g., in Delphi the method SomeForm.Show will work to make SomeForm
visible regardless of what class of form SomeForm is.  You could override the method if you wanted.  That brings LSP into play, so if overriding you would have to do it in a way that retained the expected behavior (showing the form) even if it extended a particular classes behavior beyond just the showing in the base class. 

Is this right?  It it is, still seems to me like violating the principle is something you'd have to go out of your way to do, in context of form inheritance in .NET or Delphi frameworks.  Just exending subclass forms by adding new methods not present in ancestor class (i.e., not by overriding methods existing in ancestor) isn't covered by the principle, it seems to me.
Herbert Sitz Send private email
Thursday, June 23, 2005
 
 
You can easily write code that violates the LSP without the compiler complaining.  It's really a question of your class hierarchy semantics - the compiler can't stop you from creating nonsensical hierarchies. 

Check out the pdf I linked to - the second example (Square and Rectangle) demonstrates why violating the LSP is a bad idea.

In the end, the basic idea is that subclasses must maintain the base class's "contract".  In the square and rectangle example, the Rectangle class establishes an expectation that height and width can vary independently.  You can't then go and create a Square subclass that breaks this expectation.
Jesse Smith
Thursday, June 23, 2005
 
 
I think you are correct with your analysis of the forms question.  I was clearly wrong about this being a problem (not thinking clearly at the end of the day).

My college classes (in the mid-1990s) taught an overview of  OOP (in Object Pascal!), but never went into detail about these kinds of design principles, or design patterns (which were just being formalized at the time, I believe).  So I always more or less avoided inheritance where I could, and only recently started seeing the power of these techniques.  But I guess I need to just get more experience before OO design will feel natural and smooth to me.
Jesse Smith
Thursday, June 23, 2005
 
 
Yes, actually I think working within something like the Delphi or .NET frameworks in some ways can naturally ease you into good OOP practices.

There are some areas where (visual development with) Delphi definitely works against bad OOP practices.  Not sure whether any of these are carried over into the .NET framework, but I suspect at least some may be: 

http://www.marcocantu.com/papers/RADbad.htm
Herbert Sitz Send private email
Thursday, June 23, 2005
 
 
Jesse,

"A subtype must require no more and promise no less than its supertype" ( http://ootips.org/lsp.html ). This is proper inheritance ( http://www.parashift.com/c++-faq-lite/proper-inheritance.html )

This means that if you've got a function F which currently uses a Base object, then if you want to write a class Derived which is a subtype of Base, and expect F to be able to use a Derived instead of a Base, then:

Derived can't require anything else of F (since F was only written to be sure to satisfy all of Base's requirements).
AND
Derived can't promise any less than Base (since F may be relying on the promises that Base makes).
Exception guy Send private email
Tuesday, June 28, 2005
 
 

This topic is archived. No further replies will be accepted.

Other recent topics Other recent topics
 
Powered by FogBugz