Еще один вопрос, как уменьшить дублирование кода в C# - PullRequest
2 голосов
/ 10 июля 2020

У меня есть два объекта, назовем их A и B.

Каждый из них содержит следующее свойство:

        [IgnoreDataMember]
        public string SalesforceId { get; set; }

Затем у меня есть еще два объекта, давайте назовем их UpdatedA и UpdatedB, которые соответственно расширяют A и B и не включают ничего, кроме:

        [DataMember(Name = "sf__Id")]
        public new string SalesforceId { get; set; }
        [DataMember(Name = "sf__Created")]
        public bool SalesforceCreated { get; set; }

Причина в том, что я могу использовать ServiceStack для преобразования A и B в файлы CSV, а затем использовать его снова для преобразования CSV файлы из Salesforce обратно в C# объекты (если я не проигнорирую SalesforceId, загрузка в Salesforce Bulk API 2.0 не удастся).

Итак, первая часть вопроса: действительно ли я необходимо создать два отдельных класса для UpdatedA и UpdatedB, так как эти классы почти идентичны и на самом деле оба являются полтергейстами, потому что я использую их только в следующих двух методах:

        private Dictionary<string, A> Update(Dictionary<string, A> aByExternalIds, RelayerContext context) {
            IConfiguration config = context.Config;
            string url = $"{config["SalesforceInstanceBaseUrl"]}/services/data/{config["SalesforceVersion"]}/jobs/ingest/{context.job.Id}/successfulResults";
            
            this.restClient.Get(url, context.token)
                .FromCsv<List<UploadedA>>()
                .ForEach((updatedA) => {
                    if (aByExternalIds.TryGetValue(updatedA.ExternalId, out A oldA)) {
                        oldA.SalesforceId = updatedA.SalesforceId;
                    }
                });

            return aByExternalIds;
        }

        private Dictionary<string, B> Update(Dictionary<string, B> bBySalesforceAId, RelayerContext context) {
            IConfiguration config = context.Config;
            string url = $"{config["SalesforceInstanceBaseUrl"]}/services/data/{config["SalesforceVersion"]}/jobs/ingest/{context.job.Id}/successfulResults";

            this.restClient.Get(url, context.token)
                .FromCsv<List<UploadedB>>()
                .ForEach((updatedB) => {
                    if (bBySalesforceAId.TryGetValue(updatedB.A__c, out B oldB)) {
                        oldB.SalesforceId = updatedB.SalesforceId;
                    }
                });

            return bBySalesforceAId;
        }

Что приводит ко второй части этот вопрос.

Оба эти вопроса очень похожи. Мы можем видеть, что входы отображаются разными свойствами на A и B ... поэтому я думаю, что мог бы сделать что-то вроде создания интерфейса:

    public interface Identifiable {
        public string getIdentifier();
    }

, который можно было бы использовать для возврата либо updatedA.ExternalId или updatedB.A__c.

Но я не уверен, как будет выглядеть подпись метода, если я использую дженерики. Кроме того, если я не знаю, как я мог бы обрабатывать FromCsv<List<UploadedA>>() и FromCsv<List<UploadedB>>() обычным c способом (возможно, передавая функцию?)

В любом случае, чтобы подвести итог, что бы я Я хотел бы сократить эти два метода до одного, и если я смогу удалить один или оба из этих Uploaded классов, тем лучше.

Есть идеи?

1 Ответ

1 голос
/ 10 июля 2020

Как насчет чего-то вроде этого:

public interface IBase
{
    string SalesforceId { get; set; }
}
public class A : IBase
{
    public string SalesforceId { get; set; }
}
public class UploadedA : A
{
    public new string SalesforceId { 
        get => base.SalesforceId;
        set => base.SalesforceId = value; }
    public bool SalesforceCreated { get; set; }
}

public static void Update<T, TU>(Dictionary<string, T> oldBySalesForceId, Func<TU, string> updatedId)
        where TU : T
        where T : IBase
{
    // Call service and read csv to produce a list of uploaded objects...
    // Substituting with an empty list in the example
    var list = new List<TU>();
    foreach (var updated in list)
    {
        if (oldBySalesForceId.TryGetValue(updatedId(updated), out var old))
        {
            old.SalesforceId = updated.SalesforceId;
        }
    }
}

Я удалил некоторые детали, которые не казались важными для этого примера. Здесь используются универсальные шаблоны с ограничениями и интерфейс, чтобы гарантировать, что и у обновленного, и у старого значения есть SalesForceId.

Я изменил производный класс так, чтобы он использовал тот же SalesforceId, что и базовый класс, вы можете изменить его на виртуальный / переопределите, если хотите, но, вероятно, не рекомендуется, чтобы базовый и производный классы имели независимые свойства с одним и тем же именем, поскольку это может сбивать с толку.

Он использует делегат для описания идентификатора / ключа для ОбновленоA / ОбновленоB. Вместо этого вы можете использовать интерфейс, если хотите.

...