Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Good object structure for selective composition without class explosion

Tags:

java

oop

php

My real scenario is a bit hard to explain so I'll map it to a more recognizable domain, say home entertainment equipment:

A particular piece of equipment can offer different services:

A Panasonic XYZ can play DVDs and CDs.
A Sony ABC can only play CDs.
A Hitachi PQR can play DVDs and receive TV.
...
...

Each service (DVD, CD, TV,...) has a default implementation which most models use, but some models have customized versions of particular services.


Interfaces

Models choosing to implement DVD'izable, CD'izable, TV'izable,... contracts would result in a lot of code duplication between models.


Single Inheritance

A single superclass implementing the default services would allow me to have a single subclass for each model containing all of its custom behaviour. My superclass would however be quite unwieldy and heavier than it need be for models that don't offer all types of services.


Multiple Inheritance

Multiple inheritance with its ability to selectively incorporate the services required and provide default implementations, on the surface seems ideal. I value the cohesiveness of having all PanasonicXYZ's custom functionality in a single class more than the coupling introduced by the inheritance.

But I'm not using C++ (rather PHP) and I sort of feel there's a better way anyway. Nor do I want to use proprietary extensions like mixins or 5.4's traits.


Composition

I see a class explosion with my custom functionality for a particular model scattered over multiple classes--I'd need a PanasonicXYZ_CD class and PanasonicXYZ_DVD class for example, and they'd only ever be used by the PanasonicXYZ object.


Is there a preferable structure?

Edit: I'll have a good think about some of the comments and answers made instead of prematurely commenting.

like image 987
jontyc Avatar asked Dec 03 '11 11:12

jontyc


People also ask

How can we prevent class explosions?

You can prevent the explosion of a class hierarchy by transforming it into several related hierarchies. Following this approach, we can extract the color-related code into its own class with two subclasses: Red and Blue . The Shape class then gets a reference field pointing to one of the color objects.

Which situation will require you to choose composition instead of inheriting from class A?

To get the higher design flexibility, the design principle says that composition should be favored over inheritance. Inheritance should only be used when subclass 'is a' superclass. Don't use inheritance to get code reuse. If there is no 'is a' relationship, then use composition for code reuse.

What advantage S does composition have over inheritance should composition always be used instead of inheritance?

One more benefit of composition over inheritance is testing scope. Unit testing is easy in composition because we know what all methods we are using from another class. We can mock it up for testing whereas in inheritance we depend heavily on superclass and don't know what all methods of superclass will be used.

Which is better composition or inheritance?

Prefer composition over inheritance as it is more malleable / easy to modify later, but do not use a compose-always approach. With composition, it's easy to change behavior on the fly with Dependency Injection / Setters. Inheritance is more rigid as most languages do not allow you to derive from more than one type.


1 Answers

Use composition Luke:

Object classes represents a role in your system. Even when is natural to think about a "Device", for OOP purposes is better to think about what are the roles of your device: DVD Player, TV Receiver, CD Player, etc.

It doesn't matter that a single device does all of them, thinking in the roles that the object is going to have will help you to end with single responsibility objects:

class SonyTvWithDVDPlayer {
     DVDPlayer asDVDPlayer();
     Tv asTv();
}

In that way is easy to refactor common functionality, you can have a GenericDVDPlayer and return that in the asDVDPlayer method.

If you want to allow a more dynamic usage, like asking to a Device which functionality it supports, you can use a kind of Product Trader, for example:

interface  MultifunctionDevice {
      <T> T as(Class<T> functionalitySpec) throws UnsupportedFunctionalityException
}

and them in the code you could do something like this:

device.as(DVDPlayer.class).play(dvd);

See that in this case the "multifunction device" acts like a Product Trader and the DVDPlayer.class is the spec of the product.

There are a lot of different ways to implement the trader and the spec, one is to use the Visitor pattern. But I found that in a lot of cases (when you want to be able to configure your "multifunction devices" dynamically) you can do something like this:

class MultifunctionDevice {
      Iterable<Device> devices;

      <T extends Device> T as(Class<T> spec) {
          for (Device dev : devices) {
              if (dev.provides(spec)) return dev;
          }
          throw new UnsupportedFunctionalityException(spec);
      }
 }

This combined with a Builder and a fluent API makes easy to define different devices without class explosion:

   dvdAndTv = new DeviceBuilder("Sony All in one TV and DVD")
        .addPart(new SonyDvdPlayer())
        .addPart(new SonyTv())
        .build();
like image 105
Diego Avatar answered Sep 21 '22 03:09

Diego