Douglas Gaskell
Douglas Gaskell

Reputation: 10048

Keeping methods DRY with minor differences in each method

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

Answers (3)

hyankov
hyankov

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

M.kazem Akhgary
M.kazem Akhgary

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

Niyoko
Niyoko

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

Related Questions