Skip to content
Feb 15 2010 / alecmce

When is a sub-class appropriate?

My colleague Anatoly and I regularly need to create dialogs which pop-up in front of our application. Anatoly has created a class which allows the dialog to be configured by a packet of data which contains header, any text or images, button labels, footers, and information about how the buttons are processed, like this:

public class Dialog extends Sprite
{
	public function Dialog(data:DialogData)
	{
		... composes the dialog according to the data that is passed into it ...
	}
}

Anatoly – Dialog Composition

Let’s say that Anatoly wants to create a dialog in a particular view class (please ignore any other architectural considerations – I say “view class” broadly, hoping not to rile anyone who might shout “mediator”, which is beside the point in this case):

public class SomeClassWhereDialogsAreShown
{
	...

	public function onActionWhichTriggersExampleDialog():void
	{
		// this is a simplified structure to avoid complicating the point
		var header:String = "This is Header";
		var body:String = "This is body";
		var buttons:Array = ["OK","Cancel"];
		var data:DialogData = new DialogData(header, body, buttons);
		var dialog:Dialog = new Dialog(data);

		showDialog(dialog);
	}

	// this is one of many dialogs defined here...
	public function onActionWhichTriggersADifferentDialog():void { ... }
	public function onActionWhichTriggersAThirdDialog():void { ... }
	public function onActionWhichTriggersAFourth():void { ... }
	public function onActionWhichTriggersAFifthDialog():void { ... }
}

It is important to note that I have massively over-simplified the DialogData. It could be dozens of lines long, contain nested arguments, and so on. This point becomes part of the discussion later on.

Me – Dialog Inheritance

In contrast, I want to create a dialog sub-class and package away the dialog data:

public class ExampleDialog extends Dialog
{
	public function ExampleDialog()
	{
		// this is a simplified structure to avoid complicating the point
		var header:String = "This is Header";
		var body:String = "This is body";
		var buttons:Array = ["OK","Cancel"];
		var data:DialogData = new DialogData(header, body, buttons);

		super(data);
	}
}

public class SomeClassWhereDialogsAreShown
{
	public function onActionWhichTriggersExampleDialog():void
	{
		showDialog(new ExampleDialog());
	}
}

This approach allows me to encapsulate the ExampleDialog very neatly, though it requires more lines of code than the composition approach. ActionScript is verbose enough without actively seeking to add more lines of code.

Single Responsibility Principle

If the dialog needs to be changed, the inheritance approach ensures that the only place that code editing need take place is in the code which relates to the particular dialog. This conforms to the Single Responsibility Principle, based on the principle of cohesion: the degree to which lines of code form a single unit of functionality is the extent to which they should be in their own class.

I would argue that creating a particular dialog is a functional unit, especially in user-interface code. Anatoly makes the point that all our dialogs are functionally equivalent (more of which below). I suppose he thinks of functionality in terms of the developer, and I the interface.

OOP Design principles are not inviolate, but they point us towards ways of writing code which lead to more robust code. This principle points towards the dialog configuration data not being defined in the SomeClassWhereDialogsAreShown class. (which is different from saying it supports the inheritance approach!)

Liskov Substitution Princple

Creating inheritance chains can be problematic. It might be considered reasonable to create a Square class as a subclass of the Rectangle, like this:

public class Rectangle
{
	...

	public function set width(value:Number):void
	{
		_width = value;
	}

	public function set height(value:Number):void
	{
		_height = value;
	}

	public function get area():Number
	{
		return _width * _height;
	}

}

public class Square extends Rectangle
{
	override public function set width(value:Number):void
	{
		_width = _height = value;
	}

	override public function set height(value:Number):void
	{
		_height = _width = value;
	}
}

However, this can cause problems. Imagine, during your code that you utilise Rectangle like this:

public class SomeOtherClass
{
	public function doubleRectangleArea(rectangle:Rectangle):void
	{
		rectangle.width *= 2;
	}
}

The doubleRectangleArea method presupposes that the Rectangle‘s width and height properties are independent. Polymorphism allows a Square to be passed into the doubleRectangleArea method, but as configured this would quadruple the square’s area. The functionality of the doubleRectangleArea method is broken because the Square-Rectangle relationship breaks Liskov Substitution Principle (LSP).

This principle serves as a general warning against inappropriate inheritance. Anatoly invoked it in our discussion as a reason to be cautious about inheritance, but I remain unconvinced – surely not all inheritance is bad, and I am pretty sure that as things stand, no particular thing about our dialogs violate LSP?

Function and Configuration

Let’s suppose you want to model two cars: A Honda Jazz and a Lotus Exige. The following class structure may be appropriate:

public class Car { ... }

public class HondaJazz extends Car { ... }

public class LotusExige extends Car { ... }

If, on the other hand, you have two colours of Honda Jazz, the following structure makes little sense:

public class HondaJazz extends Car { ... }

public class BlueHondaJazz extends HondaJazz { ... }

public class WhiteHondaJazz extends HondaJazz { ... }

More sensible would be:

public class HondaJazz extends Car
{
	public function HondaJazz(color:uint) { ... }
}

In the case of the two types of car there are clear functional differences: acceleration, top-speed, fuel efficiency, to name a few. In the case of the two jazzes, there are no functional differences, merely different configurations.

Anatoly argues that different dialogs are essentially like different configurations, rather than different functionalities. Therefore, they should all be the same class, but the Dialog should be configured through parameters in its constructor.

Discussion

So what is the right approach? The Single Responsibility Principle (SRP) definitely points away from the composition approach, if the class in which the composition is made has many other responsibilities. Anatoly has offered to meet me half-way having conceded this point, instead storing the various configurations in static methods of Dialog, or elsewhere, rather than create a subclass.

To me, this still feels wrong. The Function and Configuration argument is a good one, and points towards no sub-class being created. However, I still feel that the sheer quantity of configuration that some dialogs require start to make the act of configuration feel inappropriate.

Furthermore, if all we are really doing is is configuring the same dialog over and over, then I do not think that the Liskov Substitution Principle (LSP) is a concern. If all dialogs really are functionally equivalent, then how can they violate LSP?

Aesthetically, the inheritance approach feels right to me, but it is important to respect that Anatoly comes from a very different tradition and has been coding a lot longer than me! The discussion was an interesting (and pleasant) one. If any of you can add to it, then I would be delighted to hear your points of view.

  • http://alecmce.com Alec McEachran

    A few notes. I didn’t quite nail this argument in a few places:

    I should have called Anatoly’s approach as “Dialog configuration” not composition. It’s funny how the word “composition” sneaked into my brain while thinking about inheritance.

    Also, Anatoly pointed out to me offline that the cars example is not quite right. The examples I gave were still configurable differences. I should have imagined more fundamentally different functionalities like ability to store luggage, or ability to recapture brake energy.

    Finally, another colleague Tony points out offline that it is not clear that the dialogs really are functionally equivalent. That’s a very good point in our practical decision making process, though even if right, this argument is still interesting, I think, if we pretend that they are.

  • http://alecmce.com alecmce

    A few notes. I didn't quite nail this argument in a few places:

    I should have called Anatoly's approach as “Dialog configuration” not composition. It's funny how the word “composition” sneaked into my brain while thinking about inheritance.

    Also, Anatoly pointed out to me offline that the cars example is not quite right. The examples I gave were still configurable differences. I should have imagined more fundamentally different functionalities like ability to store luggage, or ability to recapture brake energy.

    Finally, another colleague Tony points out offline that it is not clear that the dialogs really are functionally equivalent. That's a very good point in our practical decision making process, though even if right, this argument is still interesting, I think, if we pretend that they are.

  • http://noiseandheat.com/ mnem

    If the dialogues are functionally equivalent, would it not be cleaner to have the configuration values specified by an external file? This may seem conceptually no different than the initial “Dialog configuration” approach from Anatoly, however I think it has the important effect of reducing the fluff-to-functionality ratio in the code. By removing what is more or less just data, you can concentrate more on the functionality of the code without suffering blindness resulting from dozens of lines of configuration-as-code. I have a hunch that is one of the main reasons you want to tidy everything away into a subclass.

    Having said that, unless the dialogues are simple presentations of data or simple gatherers of input, I think I’d side with Tony as to the unclarity over their functional equivalence.

    Possibly another question that should be asked is if they are indeed functionally equivalent, *should* they be functionally equivalent, from the point of view of the impact on the user’s experience? By creating a bespoke class or subclass, could the user be given a far superior interaction with the application? In my opinion, that should ultimately drive, or at least heavily influence, which approach is taken.

  • http://noiseandheat.com/ mnem

    Also, I don’t like the square example ;) The fault lies with the incorrect assumption by the coder of SomeOtherClass. They should lrn2 geometry.

  • http://noiseandheat.com/ mnem

    If the dialogues are functionally equivalent, would it not be cleaner to have the configuration values specified by an external file? This may seem conceptually no different than the initial “Dialog configuration” approach from Anatoly, however I think it has the important effect of reducing the fluff-to-functionality ratio in the code. By removing what is more or less just data, you can concentrate more on the functionality of the code without suffering blindness resulting from dozens of lines of configuration-as-code. I have a hunch that is one of the main reasons you want to tidy everything away into a subclass.

    Having said that, unless the dialogues are simple presentations of data or simple gatherers of input, I think I'd side with Tony as to the unclarity over their functional equivalence.

    Possibly another question that should be asked is if they are indeed functionally equivalent, *should* they be functionally equivalent, from the point of view of the impact on the user's experience? By creating a bespoke class or subclass, could the user be given a far superior interaction with the application? In my opinion, that should ultimately drive, or at least heavily influence, which approach is taken.

  • http://noiseandheat.com/ mnem

    Also, I don't like the square example ;) The fault lies with the incorrect assumption by the coder of SomeOtherClass. They should lrn2 geometry.

  • Anonymous

    @mnem – Totally agree about SomeOtherClass being in the fault about how to use rectangles. Further, I’d say that Rectangle should provide a function to help avoid these kinds of errors. Perhaps an “increaseWidth(factor:Number)” would be appropriate.

    As for the main topic, I think that there are uses for both composition and inheritance. It seems to defy examples though as the choice is highly dependent on how you imagine the class will be used. If you choose composition and then make dozens of the same exact dialog, you’re either duplicating the code everywhere or in need of a helper function that might not fit anywhere nicely. If you choose inheritence and then want to make lots of customizations to a dialog you thought wouldn’t change much, you end up with a needlessly-complicated dialog with a specific name and you get to do a lot of refactoring. So I think the right answer is “it depends”. :)

    Thanks for the article. It’s been a fun thought experiment. :)

  • http://alecmce.com Alec McEachran

    Sorry to disagree with you both Jackson and David (mnem), but I don’t think you can blame SomeOtherClass for the LSP violation… It is the fault of the whole system: a property of Rectangle is the independence of the width and height variables. A property of Square is that those variables are bound.

    It is perfectly reasonable to say “if you double the width of a rectangle you double its area”. But the whole point of polymorphism is that if something extends rectangle, then it can be swapped in for rectangle… so if I had a “pretty rectangle”, I can replace rectangle and say: “if you double the width of a pretty rectangle, you double its area”. But a square doesn’t work that way, (“if you doubule the width of a square you quadruple it’s area… because in order to remain a square you also quadrupled it’s height!”) which is strong evidence to say that square should not extend rectangle.

    But, I hear you cry, “A Square is a type of Rectangle”. So it is, and here’s the confusion: “A extends B” and “A is a type of B” are not equivalent. Saying that a class extends from another class is creating a contractual relationship between the two classes that is stronger than a type-relation.

    You can get out of this… by making the Rectangle and Square immutable. An immutable square class can extend an immutable rectangle class, because the problematic methods – where you set the width and height – are not implemented.

    Thank you both for your comments however, I am glad that some people too the time to read this article! It is not exactly mainstream, but we spent a good couple of days discussing this at work at quiet moments, and I thought it was worth writing up. Please let me know if you think I’m talking rubbish in this response, too!

    • http://noiseandheat.com/ mnem

      No no, that makes perfect sense, I stand corrected :)

      On a complete tangent though, and to pointlessly overthink an imaginary system, it’d be interesting to treat square as a property of rectangle – rectangle.isSquare. Or, to take it further, have quadrilateral.isRectangle. It’s the old coder balancing act between abstract and useful. This paragraph has no point…

    • Cheers

      interresting discussion but i d like to point out that a square IS a particular rectangle and not the contrary, i means square should extend rectangle, in the example rectangle extends square witch is not logical!
      About all the single responsability, i think it has its limits, how can you say a window has a single responsability? it has to display messages, buttons, be able draggable, and many other functionalities, so even if we have to try to stick to those rules, sometimes it is just not possible.

      From my point of view, composition still remains the best because of all the testing purposes, if you want to test a class composed by other objects (interfaced of course), you will still be able to inject other mocked objects respecting the same interface but doing nothing, wereas with the inheritance your class will remain dependent on the entire inheritance tree, you will have to test all the parent classes ;)

      • http://alecmce.com Alec McEachran

        Re squares and rectangles

        Mathematicians define squares by adding a restriction to the concept of rectangle: “A rectangle is a quadrilateral with four right-angles the opposite sides of which have equal length” and “A square is a rectangle with four equal sides”. That’s useful because using “square is a rectangle…” all the restrictions on the shape of a rectangle are also defined as restrictions for the square.

        Interestingly in programming we work the other way around. Classes start as super-restricted nothings, and then we add functionality to them. So, we could start with the concept of a side length and define the method set sideLengths(value:Number). Then in a sub-class we could add more functionalities: set width(value:Number) and set height(value:Number). The sub-class has fewer restrictions than the super-class. As I wrote in the comment that you are responding to “A extends B” does not imply that “A is a type of B”.

        Your argument is based on an upper-case “IS” and asserting that rectangle extending square is not logical. I believe that the article and this and other comments assert that it is logical. Therefore, I reject your claims that rectangle extending square is illogical. I have also explained that in the programming domain of discourse a square ISN’T a particular rectangle. That is different to saying that mathematically a square is conceptually a subset of rectangle.

        Re: window

        If your window class is responsible for laying out buttons or displaying messages, then you I worry as to your understanding of OOP. The window is probably only responsible for reporting it’s size, resizing, and possibly position to an object that it contains. Alerts would be handled in a different class. Draggability is probably handled by the top-bar, which might also be in a different class. That seems to me to be an extremely satisfactory example of the single-responsibility principle.

        Re: testing

        I’m afraid I don’t follow your point about testing. Please explain further.

  • jacksondunstan

    @mnem – Totally agree about SomeOtherClass being in the fault about how to use rectangles. Further, I'd say that Rectangle should provide a function to help avoid these kinds of errors. Perhaps an “increaseWidth(factor:Number)” would be appropriate.

    As for the main topic, I think that there are uses for both composition and inheritance. It seems to defy examples though as the choice is highly dependent on how you imagine the class will be used. If you choose composition and then make dozens of the same exact dialog, you're either duplicating the code everywhere or in need of a helper function that might not fit anywhere nicely. If you choose inheritence and then want to make lots of customizations to a dialog you thought wouldn't change much, you end up with a needlessly-complicated dialog with a specific name and you get to do a lot of refactoring. So I think the right answer is “it depends”. :)

    Thanks for the article. It's been a fun thought experiment. :)

  • http://alecmce.com alecmce

    Sorry to disagree with you both Jackson and David (mnem), but I don't think you can blame SomeOtherClass for the LSP violation… It is the fault of the whole system: a property of Rectangle is the independence of the width and height variables. A property of Square is that those variables are bound.

    It is perfectly reasonable to say “if you double the width of a rectangle you double its area”. But the whole point of polymorphism is that if something extends rectangle, then it can be swapped in for rectangle… so if I had a “pretty rectangle”, I can replace rectangle and say: “if you double the width of a pretty rectangle, you double its area”. But a square doesn't work that way, (“if you doubule the width of a square you quadruple it's area… because in order to remain a square you also quadrupled it's height!”) which is strong evidence to say that square should not extend rectangle.

    But, I hear you cry, “A Square is a type of Rectangle”. So it is, and here's the confusion: “A extends B” and “A is a type of B” are not equivalent. Saying that a class extends from another class is creating a contractual relationship between the two classes that is stronger than a type-relation.

    You can get out of this… by making the Rectangle and Square immutable. An immutable square class can extend an immutable rectangle class, because the problematic methods – where you set the width and height – are not implemented.

    Thank you both for your comments however, I am glad that some people too the time to read this article! It is not exactly mainstream, but we spent a good couple of days discussing this at work at quiet moments, and I thought it was worth writing up. Please let me know if you think I'm talking rubbish in this response, too!

    • Cheers

      interresting discussion but i d like to point out that a square IS a particular rectangle and not the contrary, i means square should extend rectangle, in the example rectangle extends square witch is not logical!
      About all the single responsability, i think it has its limits, how can you say a window has a single responsability? it has to display messages, buttons, be able draggable, and many other functionalities, so even if we have to try to stick to those rules, sometimes it is just not possible.

      From my point of view, composition still remains the best because of all the testing purposes, if you want to test a class composed by other objects (interfaced of course), you will still be able to inject other mocked objects respecting the same interface but doing nothing, wereas with the inheritance your class will remain dependent on the entire inheritance tree, you will have to test all the parent classes ;)

      • Alec

        Re squares and rectangles

        Mathematicians define squares by adding a restriction to the concept of rectangle: “A rectangle is a quadrilateral with four right-angles the opposite sides of which have equal length” and “A square is a rectangle with four equal sides”. That’s useful because using “square is a rectangle…” all the restrictions on the shape of a rectangle are also defined as restrictions for the square.

        Interestingly in programming we work the other way around. Classes start as super-restricted nothings, and then we add functionality to them. So, we could start with the concept of a side length and define the method set sideLengths(value:Number). Then in a sub-class we could add more functionalities: set width(value:Number) and set height(value:Number). The sub-class has fewer restrictions than the super-class. As I wrote in the comment that you are responding to “A extends B” does not imply that “A is a type of B”.

        Your argument is based on an upper-case “IS” and asserting that rectangle extending square is not logical. I believe that the article and this and other comments assert that it is logical. Therefore, I reject your claims that rectangle extending square is illogical. I have also explained that in the programming domain of discourse a square ISN’T a particular rectangle. That is different to saying that mathematically a square is conceptually a subset of rectangle.

        Re: window

        If your window class is responsible for laying out buttons or displaying messages, then you I worry as to your understanding of OOP. The window is probably only responsible for reporting it’s size, resizing, and possibly position to an object that it contains. Alerts would be handled in a different class. Draggability is probably handled by the top-bar, which might also be in a different class. That seems to me to be an extremely satisfactory example of the single-responsibility principle.

        Re: testing

        I’m afraid I don’t follow your point about testing. Please explain further.

  • http://noiseandheat.com/ mnem

    No no, that makes perfect sense, I stand corrected :)

    On a complete tangent though, and to pointlessly overthink an imaginary system, it'd be interesting to treat square as a property of rectangle – rectangle.isSquare. Or, to take it further, have quadrilateral.isRectangle. It's the old coder balancing act between abstract and useful. This paragraph has no point…

  • Ewan

    Possibly the problem is that you’re trying to decide on the right approach based upon features of the class considered in isolation to any application. Suppose that you have an app where you have lots of use of a small number of Dialogs. Then sub-class them. But suppose that you have an app where you have lots of different Dialogs, each of which are used a small number of times. Then Build them. The guiding principle should not be Function, Composition or SRP. It should be laziness.

  • Ewan

    Possibly the problem is that you're trying to decide on the right approach based upon features of the class considered in isolation to any application. Suppose that you have an app where you have lots of use of a small number of Dialogs. Then sub-class them. But suppose that you have an app where you have lots of different Dialogs, each of which are used a small number of times. Then Build them. The guiding principle should not be Function, Composition or SRP. It should be laziness.