Reputation: 10048
I'm writing a conversion class to convert between a model used when pulling API data, do a model used with entity framework. The reason the two are seperated is due to the JSON.Net annotations on the fields, there are conflicts between the ones I need when pulling the data from the api and when using it with entitfy framework & asp.net.
I have a dozen of so classes that are almost identical save for a single field. Here is an example of two conversion methods for those.
public static IEnumerable<PlayerUnitsKilledRank> ConvertPlayerUnitsKilledRankings(IEnumerable<ApiCombatUnitsKilledRank> rankings, int world)
{
List<PlayerUnitsKilledRank> dbRankings = new List<PlayerUnitsKilledRank>();
DateTime now = DateTime.Now.Date;
foreach (ApiCombatUnitsKilledRank rank in rankings)
{
PlayerUnitsKilledRank dbRank = new PlayerUnitsKilledRank()
{
Date = now,
World = world,
Player = rank.Player,
Alliance = rank.Alliance,
Rank = rank.Rank,
UnitsKilled = rank.UnitsKilled
};
dbRankings.Add(dbRank);
}
return dbRankings;
}
public static IEnumerable<PlayerCavernRaidingRank> ConvertPlayerCavernRaidingRankings(IEnumerable<ApiRaidingCavernRank> rankings, int world)
{
List<PlayerCavernRaidingRank> dbRankings = new List<PlayerCavernRaidingRank>();
DateTime now = DateTime.Now.Date;
foreach (ApiRaidingCavernRank rank in rankings)
{
PlayerCavernRaidingRank dbRank = new PlayerCavernRaidingRank()
{
Date = now,
World = world,
Player = rank.Player,
Alliance = rank.Alliance,
Rank = rank.Rank,
Plundered = rank.ResourcesPlundered
};
dbRankings.Add(dbRank);
}
return dbRankings;
}
How can you this up to remove the redundant code and keep my class DRY? The methods are so similar to each other, but I can't think of a good way to do this off the top of my head.
I could use generic methods, but then I still ahve that single off-property I need to handle. since each of the classes are so similar, I can make a base-class they all inherit from, but the one-off property is still an issue.
Upvotes: 0
Views: 127
Reputation: 4130
Extract a common interface between ApiCombatUnitsKilledRank
and ApiRaidingCavernRank
. This interface could have a single method: IRank ProduceRank()
.
PlayerCavernRaidingRank
and PlayerUnitsKilledRank
should inherit the same IRank
interface.
The 'one-off property' you are referring to is now a concrete-implementation concern and you can actually have as many such properties as you like.
public interface IRank
{
// Your common rank properties here
// Maybe even create a base abstract Rank class ...
}
public interface IRankProducer
{
IRank ProduceRank();
}
public class PlayerCavernRaidingRank : IRank
{
}
public class PlayerUnitsKilledRank : IRank
{
}
public class ApiCombatUnitsKilledRank : IRankProducer
{
public IRank ProduceRank()
{
return new PlayerUnitsKilledRank()
{
Player = this.Player,
Alliance = this.Alliance,
Rank = this.Rank,
UnitsKilled = this.UnitsKilled
};
}
}
public class ApiRaidingCavernRank : IRankProducer
{
public IRank ProduceRank()
{
return new PlayerCavernRaidingRank()
{
Player = this.Player,
Alliance = this.Alliance,
Rank = this.Rank,
Plundered = this.ResourcesPlundered
};
}
}
public static IEnumerable<IRank> Convert(IEnumerable<IRankProducer> rankings, int world)
{
var dbRankings = new List<IRank>();
DateTime now = DateTime.Now.Date;
foreach (IRankProducer rank in rankings)
{
var rank = rank.ProduceRank();
rank.World = world;
rank.Date = now;
dbRankings.Add(rank);
}
return dbRankings;
}
Upvotes: 3
Reputation: 19149
You can pass a delegate to generic method in order to fix this issue, or if all PlayerRank
have parameterless constructor you can use new()
constraint.
public static IEnumerable<TPlayerRank> ConvertRankings<TApiRank,TPlayerRank>(IEnumerable<TApiRank> rankings, int world/*, Func<TPlayerRank> func*/)
where TApiRank : APIRank,
where TPlayerRank : PlayerRank, new()
{
List<TPlayerRank> dbRankings = new List<TPlayerRank>();
DateTime now = DateTime.Now.Date;
foreach (var rank in rankings)
{
//TPlayerRank dbRank = func();
var dbRank = new TPlayerRank();
dbRank.Date = now,
dbRank.World = world,
dbRank.Player = rank.Player,
dbRank.Alliance = rank.Alliance,
dbRank.Rank = rank.Rank,
dbRank.Plundered = rank.ResourcesPlundered
dbRankings.Add(dbRank);
}
return dbRankings;
}
TApiRank
is generic type. you specify what this type is by using constraints, where TApiRank : APIRank
, I assume APIRank
is class but as @HristoYankov suggested you can use general propose interface IRank
.
Upvotes: 0
Reputation: 7672
You can also create base class for both ApiRank
and PlayerRank
, and expose ToPlayerRank
in ApiRank
base class. Think like ToString()
.
abstract class PlayerRank
{
public DateTime Date { get; set; }
public int World { get; set; }
public int Player { get; set; }
public int Alliance { get; set; }
public int Rank { get; set;}
}
abstract class ApiRank
{
public int Player { get; set; }
public int Alliance { get; set; }
public int Rank { get; set; }
// method that should be overriden in
// concrete class that create specific player rank type
// as well as doing type specific operation
protected abstract PlayerRank CreatePlayerRank();
// put common operation here
public PlayerRank ToPlayerRank(int world, DateTime date)
{
var inst = CreatePlayerRank();
inst.Player = Player;
inst.Alliance = Alliance;
inst.Rank = Rank;
inst.World = world;
inst.Date = date;
return inst;
}
}
class PlayerUnitsKilledRank : PlayerRank
{
public int UnitsKilled { get; set; }
}
class ApiCombatUnitsKilledRank : ApiRank
{
public int UnitsKilled { get; set; }
protected override PlayerRank CreatePlayerRank()
{
var b = new PlayerUnitsKilledRank();
b.UnitsKilled = UnitsKilled;
return b;
}
}
class PlayerCavernRaidingRank : PlayerRank
{
public int Plundered { get; set;}
}
class ApiRaidingCavernRank : ApiRank
{
public int Plundered { get; set;}
protected override PlayerRank CreatePlayerRank()
{
var b = new PlayerCavernRaidingRank();
b.Plundered = Plundered;
return b;
}
}
static IEnumerable<PlayerRank> ConvertRank(IEnumerable<ApiRank> rankings, int world)
{
DateTime now = DateTime.Now.Date;
return rankings.Select(x=>x.ToPlayerRank(world, now));
}
Upvotes: 1