6

I've recently read several articles about the advantages of the composition over inheritance. Their authors said that you can always replace inheritance with composition (to be precise, they say they don't know such situations where it can't be done). However, I can't think how to do this in the following case:

abstract class Reader
{
    public Int32 ReadSum()
    {
        return this.ReadValue() + this.ReadValue();
    }

    public Int32 ReadSub()
    {
        return this.ReadValue() - this.ReadValue();
    }

    public Int32 ReadMul()
    {
        return this.ReadValue() * this.ReadValue();
    }

    public Int32 ReadDiv()
    {
        return this.ReadValue() / this.ReadValue();
    }

    public abstract Int32 ReadValue();
}

class RandomSource: Reader
{
    public Int32 ReadValue()
    {
        return Math.RandomInt32();
    }
}

class UserSource: Reader
{
    public Int32 ReadValue()
    {
        return Console.ReadInt32();
    }
}

Is this at all possible?

1
  • Worth noting that both the inheritance based solution in the question and the composition based solution in the two answers can be described as design patterns: Template Method and Strategy respectively.
    – Aviv Cohn
    Aug 5, 2014 at 12:40

2 Answers 2

10

This example is a bit contrived but here's one way.

You seem to have things going on here - operations and reading. The reading should be done by Reader and the operations can be performed by a different class.

class ReadOperations {

     private Reader reader;

     public Int32 ReadSum()
     {
       return reader.ReadValue() + reader.ReadValue();
     }

     public Int32 ReadSub()
     { 
        return reader.ReadValue() - reader.ReadValue();
     }

     public Int32 ReadMul()
     {
       return reader.ReadValue() * reader.ReadValue();
     }

     public Int32 ReadDiv()
     {
        return reader.ReadValue() / reader.ReadValue();
     }

}

interface Reader {

  public Int32 ReadValue();
}

class RandomSource: Reader
{
    public Int32 ReadValue()
    {
        return Math.RandomInt32();
    }
}

class UserSource: Reader
{
    public Int32 ReadValue()
    {
        return Console.ReadInt32();
    }
}

Depending on what you want there are other ways to do this.

You should have to find a way to inject the reader - probably with a constructor.

Disclaimer - for practical purposes you don't "need" to use composition, but doing so is extensible and you should use it whenever you think your program can evolve in a certain direction.

In this case, if you think your operations will evolve independently from the way you "read".

3
  • +1 Your example is equivalent to mine; I only chose a function because I find it simpler. An interface may help document intent, but it’s extensible, so the intent may be diluted over time if you carelessly add methods.
    – Jon Purdy
    Aug 5, 2014 at 6:41
  • @JonPurdy You are right - your answer seems equivalent although I don't know C# :-). I didn't see your answer when I posted mine.
    – Chip
    Aug 5, 2014 at 8:59
  • Yeah, you posted while I was still writing my answer, though I didn’t read it until after I’d posted.
    – Jon Purdy
    Aug 5, 2014 at 9:16
7

Sure, for example you can pass the reading behaviours into the Reader class from outside:

class Reader
{
    private Func<Int32> read;

    public Reader(Func<Int32> read)
    {
        this.read = read;
    }

    public Int32 ReadSum()
    {
        return read() + read();
    }

    …

}

Then you say something like new Reader(() => Math.RandomInt32()) to construct the equivalent of a RandomSource, or new Reader(() => Console.ReadInt32()) for a UserSource.

In general, replacing inheritance with composition leads to fewer nominal types such as UserSource, because their behaviour emerges from the composition of simpler components. You can of course make “constructor functions” like NewUserSource() for the sake of convenience.

(I’m not up on my C#, so bear with me if I’ve gotten some minor language details wrong here.)

1
  • +1 although personally I would prefer a new explicit interface such as IReaderStrategy rather than a Func<> delegate, for clarity
    – Alex
    Aug 5, 2014 at 11:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.