Majak
Majak

Reputation: 1633

C# Abstract class has another abstract class object

I have few classes representing some measured data, at first ICut abstract class with derived RoundCut and SquareCut like this:

public abstract class ICut
{
}

public class RoundCut : ICut
{
    DoSomeWithRoundCut(){}
}

public class SquareCut : ICut
{
    DoSomeWithSquareCut(){}
}

Of course it contains some implementation but it isn't important for this question. ICut is abstract class instead of interface because it has some implemetation itself.

Then, here are classes representing set of ICut data, again base abstract IRoll and derived RoundRoll and SquareRoll:

public abstract class IRoll
{
    // list of measured cuts
    public List<ICut> cuts;     

    // Create new cut
    public abstract ICut CreateCut();
}

public class RoundRoll : IRoll
{        
    public RoundRoll ()
    {
        cuts = new List<RoundCut>();
    }

    public override ICut CreateCut()
    {
        RoundCut cut = new RoundCut();
        cuts.Add(cut);
        return cut;
    }
}

public class SquareRoll : IRoll
{
    public SquareRoll()
    {
        cuts = new List<SquareCut>();
    }

    public override ICut CreateCut()
    {
        SquareCut cut = new SquareCut();
        cuts.Add(cut);
        return cut;
    }
}

And now, of course I'm not able to reach directly RoundCut or SquareCut extra implementation by calling for example:

IRoll roll = new RoundRoll();
roll.CreateCut();
roll.cuts[0].DoSomeWithRoundRoll();

And I can't use neither:

(roll.cuts[0] as RoundCut).DoSomeWithRoundRoll();

because I generally don't know if which IRoll derivation roll is.

I'm refactoring huge project where all roll objects were of type RoundRoll and now the other has to be added.

Maybe I'm missing some kind of suitable design pattern, I'm in the begining of advanced OOP patterns learning curve, and I've been thinking about solution of this problem all the day.

UPDATE After many experiments, I realized that as a contrary to my primary opinions, I ended up with the solution of @The-First-Tiger with some improvements. I created simple factory:

// Cut factory
public static class CutFactory
{
    // Get new cut by given shape type
    public static ICut GetCut(RollShape shape)
    {
        switch (shape)
        {
            case RollShape.Round:
                return new RoundCut();
            case RollShape.Square:
                return new SquareCut();
            default:
                throw new ArgumentException();
        }
    }
}

So I can create Cut like:

ICut cut = CutFactory.GetCut(roll.GetShape());

And if needed to have different behavior:

if (cut is RoundCut) 
    (cut as RoundCut).RoundCutSpecificMethod();
else if (cut is SquareCut) 
    (cut as SquareCut).SquareCutSpecificMethod();

Upvotes: 1

Views: 667

Answers (2)

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 727137

One way to work around the problem is to make IRoll generic on the type of its ICut:

public abstract class AbstractRoll<T> where T : ICut, new {
    // list of measured cuts
    public List<T> cuts = new List<T>();
    // Create new cut
    public T CreateCut() {
       var res = new T();
       curs.Add(res);
       return res;
    }
}

Now you can do this:

public class RoundRoll : AbstractRoll<RoundCut> {
    ...
}

public class SquareRoll : AbstractRoll<SquareCut> {
    ...
}

Note that C# lets you move the boilerplate code to the base class by applying constraints to generic types.

The only remaining issue now is that AbstractRoll is no longer a common interface for RoundRoll and SquareRoll, so you cannot create a collection of rolls.

This problem can be solved by adding a non-generic interface IRoll on top of AbstractRoll class, with the operations that are common to all rolls, and are also independent of the type of the roll's ICut:

public interface IRoll {
    IEnumerable<ICut> Cuts { get; }
    ... // add other useful methods here
}
public abstract class AbstractRoll<T> : IRoll where T : ICut, new {
    ...
    public IEnumerable<ICut> Cuts {
        get {
            return curs.Cast<ICut>();
        }
    }
    ... // implement other useful methods here
}

Upvotes: 4

The-First-Tiger
The-First-Tiger

Reputation: 1584

You could rewrite your code using interfaces:

public interface ICut
{
   DoSomething();
}

public class RoundCut : ICut
{
    DoSomething(){}
}

public class SquareCut : ICut
{
    DoSomething(){}
}

public interface IRoll
{
    IEnumerable<ICut> Cuts { get; };      

    ICut CreateCut();
}

public class RoundRoll : IRoll
{        
    public IEnumerable<ICut> Cuts { get; private set; }

    public RoundRoll ()
    {
        this.Cuts = new List<ICut>();
    }

    public ICut CreateCut()
    {
        var cut = new RoundCut();
        this.Cuts.Add(cut);

        return cut;
    }
}

public class SquareRoll : IRoll
{
    public IEnumerable<ICut> Cuts { get; private set; }

    public SquareRoll ()
    {
        this.Cuts = new List<ICut>();
    }

    public ICut CreateCut()
    {
        var cut = new SquareCut();
        this.Cuts.Add(cut);

        return cut;
    }
}

IRoll roll = new RoundRoll();
var cut = roll.CreateCut();
cut.DoSomething();

Update

If SquareCut and RoundCut have a lot of methods which are not common you can still check for ICuts concrete type and then cast to it:

IRoll roll = new RoundRoll();
var cut = roll.CreateCut();

if (cut is RoundCut) {
    (cut as RoundCut).RoundCutSpecificMethod();
}
else if (cut is SquareCut) {
    (cut as SquareCut).SquareCutSpecificMethod();
}

Upvotes: 1

Related Questions