Reputation: 2988
Currently I am in the phase of refactoring my code after it has been unit tested, and I have some concerns about the refactoring from a design point of view with regards to type safety. My original code looked a bit like this:
Interfaces
public interface IBase
{
int ID { get; set; }
}
public interface IFirstSub : IBase
{
string Description { get; set; }
}
public interface ISecondSub : IBase
{
decimal Total { get; set; }
}
public interface IThirdSub : IBase
{
int Count { get; set; }
}
public interface IBaseContainer
{
void Add(IBase baseParam);
}
Implementations
public class FirstContainer : IBaseContainer
{
public void Add(IBase baseParam)
{
if (!(baseParam is IFirstSub || baseParam is ISecondSub))
{
throw new ArgumentException(nameof(baseParam));
}
// Do Something
}
}
public class SecondContainer : IBaseContainer
{
public void Add(IBase baseParam)
{
if (!(baseParam is IThirdSub))
{
throw new ArgumentException(nameof(baseParam));
}
// Do Something
}
}
With my original implementation of FirstContainer
and SecondContainer
, it was repeating the same logic at the start of the Add
method, so I thought I would refactor the code to look something like this:
public abstract class BaseContainer : IBaseContainer
{
private readonly List<Type> _types = new List<Type>();
protected BaseContainer(params Type[] baseTypes)
{
_types.AddRange(baseTypes);
}
public void Add(IBase baseParam)
{
if (_types.All(type => !type.IsInstanceOfType(baseParam)))
{
throw new ArgumentException(nameof(baseParam));
}
DoSomething(baseParam);
}
protected abstract void DoSomething(IBase baseParam);
}
public class ThirdContainer : BaseContainer
{
public ThirdContainer() : base(typeof(IFirstSub)) { }
protected override void DoSomething(IBase baseParam)
{
// Do Something
}
}
With this refactoring done, it successfully removes the duplication of the code from the start of the Add
method, but my main concern with the refactoring is the fact that the call to the base constructor base(typeof(IFirstSub))
is not really type safe. By that, I mean I can call the base constructor like base(typeof(object))
for example, and it will compile. For the purposes of my project, I'd like to constrain the types to ones that inherit IBase
, and enforce at compile time.
Is there anyway to overcome this limitation, or would a new design be needed in order to achieve this?
Upvotes: 0
Views: 87
Reputation: 52240
Passing and validating types at run-time is not type-safe, as type-safety is a compile-time concept. In my opinion your refactoring effort does not improve the code, and in fact does something quite weird.
If you need a method that accepts either of two types, you can use function overloading:
public class FirstContainer : IBaseContainer
{
public void Add(IFirstSub param)
{
// Do Something
}
public void Add(ISecondSub param)
{
// Do Something
}
}
The compiler will automatically choose the right prototype for you, and will not allow anything other than an IFirstSub
or ISecondSub
.
Another approach requires you to add an interface for the types that have something in common, like this:
interface ICanBeHeldInFirstContainer
{ }
public interface IFirstSub : IBase, ICanBeHeldInFirstContainer
{
string Description { get; set; }
}
public interface ISecondSub : IBase, ICanBeHeldInFirstContainer
{
decimal Total { get; set; }
}
Then you do this:
public class FirstContainer : IBaseContainer
{
public void Add(ICanBeHeldInFirstContainer param)
{
// Do Something
}
}
or this:
public class FirstContainer : IBaseContainer
{
public void Add<T>(T param) where T : ICanBeHeldInFirstContainer
{
// Do Something
}
}
Upvotes: 4