So I am trying to understand this sample code from Lynda.com without explanation.
IScore.cs
internal interface IScore
{
float Score { get; set; }
float MaximumScore { get; set; }
}
ScoreEntity.cs
internal class ScoreEntity : IScore
{
public float Score { get; set; }
public float MaximumScore { get;set;}
}
ScoreUtility.cs
internal class ScoreUtility
{
public static IScore BestOfTwo(IScore as1, IScore as2)
{
var score1 = as1.Score / as1.MaximumScore;
var score2 = as2.Score / as2.MaximumScore;
if (score1 > score2)
return as1;
else
return as2;
}
}
Calling code:
var ret = ScoreUtility.BestOfTwo(new ScoreEntity() { Score = 10, MaximumScore= 4},
new ScoreEntity() {Score = 10, MaximumScore = 6});
return;
My question is, in the ScoreUtility, what's really the benefit of using the interface type as return type in the method and argument type in the parameters, vs. just using ScoreEntity for the return and parameters, since the method itself returns a concrete object? Is this just to make the code looked 'smart'?
CodePudding user response:
Any class that implements the IScore
interface can be passed into the ScoreUtility method.
Perhaps there's a reason to implement another ScoreEntity
class, like wanting to add a name seen in NamedScoreEntity
:
internal class NamedScoreEntity: IScore
{
public string Name { get; set; }
public float Score { get; set; }
public float MaximumScore { get;set;}
}
If that's the case, then you can still use your utility methods on the new classes because of the contract the IScore
interface enforces.
In this case, you could now compare the score between an un-named score object to a score object that also has a name.
Similarly, returning IScore
allows the method to remain flexible enough to be used on multiple object types, and you can easily cast the returned type when you need to.
CodePudding user response:
Given this declaration
public static IScore BestOfTwo(IScore as1, IScore as2)
The method does not know (or care) about the concrete types of as1 and as2. They may be ScoreEntity, or may be something entirely different. All it knows is that they implement IScore.
So if the method was to return a concrete type, then you have effectively limited the as1 and as2 parameters to being ScoreEntity, and have reduced its flexibility.
The only way you could guarantee that the method wouldn't fail would be to rewrite it as
public static ScoreEntity BestOfTwo(ScoreEntity as1, ScoreEntity as2)
CodePudding user response:
In the posted code there is very little benefit, and I would argue that the interface should be removed. I would also argue that the comparison logic probably should be part of the object, or possibly an implementation of IComparer<T>
.
Interfaces are most useful when there is a good reason for having multiple implementations. IComparer<T>
is a great example, you might want to compare scores by the absolute score value, or the relative. So providing an abstraction is really useful.
Just applying interfaces to all classes is not a good idea, it will just make your code more complicated without any benefit. You might also discover that it can be difficult to provide a second implementation if the interface is not designed at the right abstraction level. This may result in adding properties or methods to objects that does not do anything or throws, just because they are required by a implemented interface. See for example IList<T>
where many of the implementer throw exceptions for some of the methods.
In the specific case of IScore
, it may be useful if you have some need for additional score-classes. But in many cases I would argue that composition might be more useful, i.e.
public class MyNamedScore{
public string Name {get;}
public ScoreEntity Score {get;}
}