Is using interfaces for datatypes an anti-pattern?
https://softwareengineering.stackexchange.com/questions/275181
-
07-10-2020 - |
Question
Suppose I have a various entities in my model (using EF), say User, Product, Invoice and Order.
I am writing a user control that can can print the summaries of entity objects in my application where the entities belong to pre-decided set, in this case I say that summaries of User and Product can be summarized.
The summaries will all only have a ID and a description, so I create a simple interface for this:
public interface ISummarizableEntity {
public string ID { get; }
public string Description { get; }
}
Then for the entities in question, I create a partial class that implements this interface:
public partial class User : ISummarizableEntity
{
public string ID
{
get{ return UserID.ToString(); }
}
public string Description
{
get{ return String.Format("{0} {1} is from {2} and is {3} years old", FirstName, LastName, Country, Age); }
}
}
public partial class Product: ISummarizableEntity
{
public string ID
{
get{ return ProductID.ToString(); }
}
public string Description
{
get{ return String.Format("{0} weighs {1}{2} and belongs in the {3} department", ProductName, WeightValue, WeightUnit, Department); }
}
}
This way my user control / partial view can just bind to any collection of ISummarizableEntity and doesn't need to be interested in the source at all. I have been told that interfaces shouldn't be used as datatypes but I didn't get more information than that. As far as I can see, although interfaces normally describe behavior, just using properties isn't an anti-pattern in itself as properties are just syntactic sugar for getters/setters anyway.
I could create a concrete datatype and map from the entities to that but I can't see the benefit. I could make the entity objects inherit from an abstract class and then define the properties but then I am locking the entities to no further use as we can't have multiple inheritance. I am also open to having any object being ISummarizableEntity if I wanted (obviously I would rename the interface)
The solution I am using in my mind is maintainable, extensible, testable and fairly robust. Can you see the anti-pattern here?
Solution
Interfaces don't describe behaviour. Quite the opposite, sometimes.
Interfaces describe contracts, such as "if I am to offer this object to any method that accepts an ISummarizableEntity, this object must be an entity that is able to summarize itself" -- in your case, that is defined as being able to return a string ID and a string Description.
That's a perfect use of interfaces. No anti-pattern here.
OTHER TIPS
You have chosen the better path for this design because you are defining a specific type of behavior that will be required of multiple, different, types of objects. Inheritance in this case would imply a common relationship between the classes that does not actually exist. In this case, composability is favored over inheritance.
Interfaces that only carry properties should be avoided since :
- it obfuscates the intent : you solely need a data container
- it encourages inheritance : probability that someone will mix concerns in the future
- it prevents serialization
Here you are mixing two concerns :
- summary as a data
- summary as a contract
A summary is made of two strings : an id and a description. This is plain data :
public class Summary {
private readonly string id;
private readonly string description;
public Summary(string id, string description) {
this.id = id;
this.description = description;
}
public string Id { get { return id; } }
public string Description { get { return description; } }
}
Now that you have defined what a summary is you want to define a contract :
public interface ISummarizableEntity {
public Summary GenerateSummary();
}
Note that using intelligence in getters is an anti-pattern and should be avoided : it should be located in functions instead. Here is how implementations look like :
public partial class User : ISummarizableEntity {
public Summary GenerateSummary() {
var id = UserID.ToString();
var description = String.Format("{0} {1} is from {2} and is {3} years old", FirstName, LastName, Country, Age);
return new Summary(id,description);
}
}
public partial class Product : ISummarizableEntity {
public Summary GenerateSummary() {
var id = ProductID.ToString();
var description = String.Format("{0} weighs {1}{2} and belongs in the {3} department", ProductName, WeightValue, WeightUnit, Department);
return new Summary(id,description);
}
}