Как я могу объединить два класса с общими членами в один вход? - PullRequest
0 голосов
/ 18 мая 2018

Мне нужно создать сервис, который принимает 3 входа, которые в основном сводятся к A, B и комбинации A и B, которые мы назовем C.

Предположим, что эти классы определены следующим образом:

public abstract class InputBase
{
  public bool Option1 { get; set; }
  public decimal Rate { get; set; }
  public DateTime DateCreated { get; set; }
}

public class A : InputBase
{
  public decimal Fee { get; set; }
}

public class B : InputBase
{
  public decimal Fee { get; set; }
}

Fee s в A и B различны и разделены и могут отличаться, например, C, где вы можетесделать комбинацию A и B вместе в одном запросе.

В настоящее время у нас просто есть класс, который выглядит следующим образом:

public class C
{
  public A A { get; set; }
  public B B { get; set; }
}

Однако, это делает его чрезвычайно раздражающимобновить любые значения, которые являются общими в обоих.Например:

var c = new C
{
  A = new A(),
  B = new B()
}

//this is ugly and what we want to avoid
c.A.DateCreated = DateTime.Now;
c.B.DateCreated = DateTime.Now;

Мы хотели бы иметь возможность сделать что-то вроде этого:

c.DateCreated = DateTime.Now;
//later
var createdDate = c.A.DateCreated; //points to date created before

Наивным способом сделать это было бы реализовать установщики, которые запускают и обновляют обавнутренние классы со значением, но что мы будем делать с геттерами?

Кроме того, C должно наследоваться от InputBase, поскольку это технически вход в службу?

Как правильно это сделать?

Ответы [ 4 ]

0 голосов
/ 18 мая 2018

Есть определенные опасения относительно вашей структуры наследования.Я не согласен с другими ответами, но я чувствую, что они отвлеклись на эти тангенциальные элементы.

Я пытаюсь ответить только на ваш прямой вопрос, на который вы уже ответили сами:

Наивный способ сделать это - реализовать сеттеры, которые идут и обновляют оба внутренних класса со значением, но что мы будем делать с геттерами?

Вы правыо проблеме получателя, но имейте в виду, что получатель не обязан существовать.Вы можете иметь свойства только для записи.Они редки, но у них есть цель.

Я также не уверен, что именно наивно в этом.Простые решения не плохие.Во всяком случае, простые решения лучше сложных, если им удается решить ту же проблему. KISS применяется:

Принцип KISS гласит, что большинство систем работают лучше, если их поддерживать простыми, а не усложненными;поэтому простота должна быть ключевой целью в дизайне, и ненужных сложностей следует избегать.

Обратите внимание на ненужные сложности.Если это необходимо, то это (по определению) служит цели.


var c = new C
{
  A = new A(),
  B = new B()
}

//this is ugly and what we want to avoid
c.A.DateCreated = DateTime.Now;
c.B.DateCreated = DateTime.Now;

Простое исправление заключается в создании пользовательского свойства в C:

public class C
{
    public DateTime DateCreated 
    {
        set
        {
            this.A.DateCreated = value;
            this.B.DateCreated = value;
        }
    }
}

Iопущен геттер, потому что это не имеет большого смысла (вы бы отобразили A.CreatedOn или B.CreatedOn)?
Но не стоит просто опускать геттер, так что это кажется лучшим подходом здесь.

Это создает желаемое поведение:

//sets both values
c.DateCreated = DateTime.Now; 

Однако значения извлекаются отдельно:

var createdDateA = c.A.DateCreated;
var createdDateB = c.B.DateCreated;

Наивным способом сделать это было бы реализоватьсеттеры, которые идут и обновляют оба внутренних класса со значением, но что мы будем делать с геттерами?

Помимо моих предыдущих отзывов об этом, если вы действительно не любите свойства, доступные только для записи, вы можетев основном конвертировать их в метод.Функционально эквивалентный, но более явно не раскрывающий метод get:

public class C
{
    public void SetDateCreated(DateTime value)
    {
        this.A.DateCreated = value;
        this.B.DateCreated = value;
    }
}

Кроме того, должен ли C наследоваться от InputBase, поскольку технически он является входом для службы?

Нет, если C не имеет своего собственного объекта Fee, который работает точно так же, как другие производные InputBase классы.

Этот вопрос немного широк;это зависит от наследования (и SOLID) в вашей кодовой базе.Это слишком большой вопрос для StackOverflow.Если вы можете перефразировать это к конкретному вопросу о структуре наследования, он может лучше подойти для SoftwareEngineering.SE (или, возможно, CodeReview.SE, если у вас есть рабочий код).

0 голосов
/ 18 мая 2018

Возможно, я вас не правильно понял, но почему бы вам не сделать так, чтобы C наследовал от InputBase и имел AFee и BFee:

class C: InputBase {
    public decimal AFee { get; set; }
    public decimal BFee { get; set; }
}

Тогда вы могли бы добавитьконструкторы для C, которые принимают A и B.Это проверит, имеют ли они одинаковые Option1, Rate и DateCreated.Если это так, присвойте поля C с их значениями.Если это не так, создайте исключение или что-то в этом роде.

У вас также могут быть такие методы, как GetA или GetB, которые создают объекты A и B из значений C..

class C : InputBase {
    public decimal AFee { get; set; }
    public decimal BFee { get; set; }

    public C(A a, B b) {
        if (a.DateCreated == b.DateCreated && a.Option1 == b.Option1 && a.Rate == b.Rate) {
            Option1 = a.Option1;
            Rate = a.Rate;
            DateCreated = a.DateCreated;
        } else {
            throw new ArgumentException("...");
        }
    }

    public C() {}

    public A GetA() {
        return new A { DateCreated = DateCreated, Option1 = Option1, Rate = Rate, Fee = AFee };
    }

    public B GetB() {
        return new B { DateCreated = DateCreated, Option1 = Option1, Rate = Rate, Fee = BFee };
    }
}
0 голосов
/ 18 мая 2018

1. Вы можете использовать метод для обновления ввода, который выполняет действие

public class C 
{
    public A A { get; }
    public B B { get; }

    public void UpdateInput(Action<InputBase> updateFunc)
    {
        updateFunc(A);
        updateFunc(B);
    }
}

, а затем просто обновите любое из ваших свойств InputBase через действие.

var c = new C
{
  A = new A(),
  B = new B()
}
var dateTime = DateTime.UtcNow;
c.UpdateInput(input => input.DateCreated = dateTime);

2. Или вы можете сделать свойства InputBase виртуальными и переопределить их следующим образом

public abstract class InputBase
{
    public virtual bool Option1 { get; set; }
    public virtual decimal Rate { get; set; }
    public virtual DateTime DateCreated { get; set; }
}

public class C : InputBase
{
    private bool _option1;
    public override bool Option1
    {
        get => _option1;
        set
        {
            _option1 = value;
            A.Option1 = value;
            B.Option1 = value;
        }
    }

    private decimal _rate;
    public override decimal Rate
    {
        get => _rate;
        set
        {
            _rate = value;
            A.Rate = value;
            B.Rate = value;
        }
    }

    private DateTime _dateCreated;
    public override DateTime DateCreated
    {
        get => _dateCreated;
        set
        {
            _dateCreated = value;
            A.DateCreated = value;
            B.DateCreated = value;
        }
    }

    public A A { get; }
    public B B { get; }
}
0 голосов
/ 18 мая 2018

Вы можете сделать C производным от InputBase, а также иметь коллекцию InputBase.

public class C : InputBase, IHaveInputBase
{
    IList<InputBase> IHaveInputBase.Inputs { get; set; }
}

public class Service
{
    public void Handle(InputBase inputBase)
    {
        IHaveInputBase haveInputBase = inputBase as IHaveInputBase;

        if (haveInputBase != null)
        {
            foreach (InputBase input in haveInputBase.Inputs) 
            {
                this.Handle(input);
            }
        }

        inputBase.DateCreated = this.dateTimeService.UtcNow();
        ...
    }
}
...