Reputation: 33
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
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
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
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 Reward
s and Quantity
instances together in a List<Brain<T>>
this will not work.
Upvotes: 0