Reputation: 1633
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
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
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();
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