xakepoc
xakepoc

Reputation: 33

Abstract class for classes with different types of parameters

I'm pretty new to C# and I believe similar question has been answered already, but am not sure what to search for, so here is the question:

I have two classes that have some identical methods and constructors. However, these methods and constructors accept different types of parameters (the Dictionaries variables are different). So how do I put these methods and constructors into abstract class Brain?

Brain.cs

public abstract class Brain
{
    protected int width;
    protected int height;
}

Reward.cs

public class Reward:Brain
{

    public Dictionary<Tuple<Point, Direction>, int> r = 
              new Dictionary<Tuple<Point, Direction>, int>();
    public Reward(int w, int h)
    {
        int directionsCount = Enum.GetNames(typeof(Direction)).Length;
        int direction;
        width = w;
        height = h;
        for (int i = 0; i < w; i++)
            for (int j = 0; j < h; j++)
                for (direction = 0; direction < directionsCount; direction++)
                {
                    Point state = new Point(i, j);
                    r[Tuple.Create(state, (Direction)direction)] = 0;
                }
    }
    public void Set(Point state, Direction direction, int reward)
    {
        r[Tuple.Create(state, direction)] = reward;
    }
    public int Get(Point state, Direction direction)
    {
        return r[Tuple.Create(state, direction)];
    }       
}

Quantity.cs

public class Quantity:Brain
{   
    public Quantity(int w, int h)
    {
         .........
        for (int i = 0; i < width; i++)
            for (int j = 0; j < height; j++)
                for (direction = 0; direction < directionsCount; direction++)
                {
                    Point state = new Point(i, j);
                    Set(state, (Direction)direction, 0);
                }
    }
    private Dictionary<Tuple<Point, Direction>, decimal> q =
            new Dictionary<Tuple<Point, Direction>, decimal>();         
         .....
    public decimal Get(Point state, Direction action)
    {
        return q[Tuple.Create(state, action)];
    }
    public void Set(Point state, Direction action, decimal value)
    {
        q[Tuple.Create(state, action)] = value;
    }           
}

Upvotes: 1

Views: 1671

Answers (3)

Phil1970
Phil1970

Reputation: 2623

Well, I would consider alternatives to your design.

Inheritance should not be used for code reuse purpose but for Is-A relationship. It would be preferable to use composition instead: Inheritance (IS-A) vs. Composition (HAS-A) Relationship.

public class Brain<T>
{
    private readonly int width;
    private readonly int height;
    private readonly Dictionary<Tuple<Point, Direction>, T> r;

    Brain(int width, int height)
    {
        this.width = width;
        this.height = height;
        .........
        r = new Dictionary<Tuple<Point, Direction>, T>();

        for (int i = 0; i < width; i++)
            for (int j = 0; j < height; j++)
                foreach (Direction direction in Enum.GetValues(typeof(Direction))
                {
                    var state = new Point(i, j);
                    Set(state, direction, default(T));
                }            
    }

    public void Set(Point state, Direction direction, T reward)
    {
        r[Tuple.Create(state, direction)] = reward;
    }

    public T Get(Point state, Direction direction)
    {
        return r[Tuple.Create(state, direction)];
    }
}

Then other classes would use composition. For example, for the Reward class:

public class Reward
{
    Reward(int width, int height)
    {
        rewards = new Brain<int>(width, height);

        // Somehow initialize actual rewards here...
        rewards.Set(new Point(5, 8), Direction.Up, 10); // fictif example
    }

    private readonly Brain<int> rewards;
}

Thus the Brain class would essentially manage information for each position.

Then, I might consider if it really worth to have multiple types. Maybe rewards could also be a decimal afterwards particularly if they are multiplied at some point by the quantity which is already decimal.

However, even better might be to have a class that represent all information about a given position and direction:

public class PositionInfo
{
    public PositionInfo(int reward, decimal quantity)
    {
        Reward = reward;
        Quantity = quantity.
    }

    public int Reward { get; }    // C# 6 - Read-only properties.
    public decimal Quantity { get; }
}

If many of the items use default values and the dimension are larges, you might consider using TryGetValue and only fill information for position/direction that are not using the default values. Then modify Get to return default information when item is not in the dictionary. In fact, if all positions are filled, then using a multi-dimensional array might be a better option.

I would also add validation like System.Diagnostics.Debug.Assert to help find bugs during development.

Upvotes: 0

Maksim Simkin
Maksim Simkin

Reputation: 9679

Define Brain as Generic class (https://msdn.microsoft.com/library/sz6zd40f.aspx) :

public abstract class Brain<T>
 {
     protected int width;
     protected int height;
     public Dictionary<Tuple<Point, Direction>, T> r = 
                 new Dictionary<Tuple<Point, Direction>, T>();
     public void Set(Point state, Direction direction, T reward)
     {
         r[Tuple.Create(state, direction)] = reward;
     }
     public T Get(Point state, Direction direction)
     {
         return r[Tuple.Create(state, direction)];
     }
 }

And than you could define your classes as:

public class Reward : Brain<int>
{
}

public class Quantity : Brain<decimal>
{
}

You could perhaps redefine Reward and Quantity methods as only one method in base class, they do look very likely, but i don't know, what is happenning in the code you hide behind "..."

Upvotes: 2

Ren&#233; Vogt
Ren&#233; Vogt

Reputation: 43876

You could do this using generics:

public abstract class Brain<TValue> // TValue is the generic type parameter
{
    protected int width;
    protected int height;

    // use TValue in the declarations
    public Dictionary<Tuple<Point, Direction>, TValue> r = new Dictionary<Tuple<Point, Direction>, TValue>();
    public abstract void Set(Point state, Direction direction, TValue reward);
    public abstract TValue Get(Point state, Direction direction);
}

And declare the derived classes like this:

public class Reward : Brain<int> // int is now the generic type argument
{
    public override void Set(Point state, Direction direction, int reward)
    {
        r[Tuple.Create(state, direction)] = reward;
    }
    public int Get(Point state, Direction direction)
    {
        return r[Tuple.Create(state, direction)];
    }
}

public class Quantity : Brain<decimal>
{ ... }

But this has the disadvantage that Brain<int> and Brain<decimal> are totally different invariant types. So you could not assign an instance of Reward and an instance of Quantitiy to the same variable of type Brain<sometype>:

Brain<int> brain = new Reward(0, 0); // works
brain = new Quantitiy(0,0); // fails because Quantity is not a Brain<int>

So if you only want to inherit functionality or declarations, the generic approach is fine. But if you for example want to keep Rewards and Quantity instances together in a List<Brain<T>> this will not work.

Upvotes: 0

Related Questions