Рефакторинг включения запаха кода типов при добавлении метода к типу кажется неуместным - PullRequest
2 голосов
/ 15 января 2012

Допустим, у меня есть следующий метод, который, учитывая PaymentType, отправляет соответствующий запрос на платеж каждому объекту, из которого необходимо снять платеж:

public void SendRequestToPaymentFacility(PaymentType payment) {

    if(payment is CreditCard) {
      SendRequestToCreditCardProcessingCenter();
    } else if(payment is BankAccount) {
      SendRequestToBank();
    } else if(payment is PawnTicket) {
      SendRequestToPawnShop();
    }

}

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

public double GetArea(Shape shape) {
  if(shape is Circle) {
    Circle circle = shape As Circle;

    return circle.PI * (circle.radius * circle.radius);
  } else if(shape is Square) {
    Square square = shape as Square;    

    return square.length * square.width;
  }
}

GetArea() кажется довольно разумной обязанностью для каждого подкласса Shape, и, конечно, может быть красиво переработан:

public class Shape
{
  /* ... */
  public abstract double GetArea();
}

public class Circle
{

  public override double GetArea()
  {
    return PI * (radius * radius);
  }

}

Тем не менее, SendRequestToPaymentFacility() не представляется соответствующей обязанностью для PaymentType. (и, по-видимому, нарушает принцип единой ответственности). И все же мне нужно отправить запрос в соответствующий PaymentFacility в зависимости от типа PaymentType - как лучше всего это сделать?

Ответы [ 3 ]

1 голос
/ 15 января 2012

Вы можете подумать о добавлении свойства или метода в ваш класс CandyBar, который указывает, содержит ли CandyBar орехи. Теперь ваш GetProcessingPlant() метод не должен знать о различных типах CandyBars.

public ProcessingPlant GetProcessingPlant(CandyBar candyBar) {

    if(candyBar.ContainsNuts) {
        return new NutProcessingPlant();
    } else {
        return new RegularProcessingPlant();
    }

}
0 голосов
/ 22 сентября 2017

Один из подходов, которые вы можете здесь использовать, - это использование шаблона Command.В этом случае вы должны создать и поставить в очередь соответствующую команду (например, «Кредитная карта», «Банковский счет», «Пешечный билет»), а не вызывать определенный метод.Тогда вы могли бы иметь отдельные командные процессоры для каждой команды, которые предпримут соответствующее действие.

Если вам не нужна условная сложность, вы можете создать один тип команды, который включает тип оплаты в качестве свойства.и затем командный процессор может быть ответственным за выяснение того, как обрабатывать этот запрос (с соответствующим платежным процессором).

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

0 голосов
/ 15 января 2012

Один из вариантов - добавить параметр интерфейса IPaymentFacility в конструкторы для отдельных потомков PaymentType.Базовый PaymentType может иметь абстрактное свойство PaymentFacility;SendRequestToPaymentFacility для базового типа будет делегировать:

public abstract class PaymentType
{
    protected abstract IPaymentFacility PaymentFacility { get; }

    public void SendRequestToPaymentFacility()
    {
        PaymentFacility.Process(this);
    }
}

public interface IPaymentFacility
{
    void Process(PaymentType paymentType);
}

public class BankAccount : PaymentType
{
    public BankAccount(IPaymentFacility paymentFacility)
    {
        _paymentFacility = paymentFacility;
    }

    protected override IPaymentFacility PaymentFacility
    {
        get { return _paymentFacility; }
    }

    private readonly IPaymentFacility _paymentFacility;
}

Вместо того, чтобы вручную подключать внедрение зависимостей, вы можете использовать библиотеку DI / IoC Container.Настройте его так, чтобы BankAccount получил банк и т. Д.

Недостатком является то, что платежные средства будут иметь доступ только к публичным (или, возможно, внутренним) членам базового класса PaymentType.

Редактировать:

На самом деле вы можете получить у потомков класса с помощью обобщений.Либо сделайте SendRequestToPaymentFacility абстрактным (избавляясь от абстрактного свойства), либо придумайте:

public abstract class PaymentType<TPaymentType>
    where TPaymentType : PaymentType<TPaymentType>
{
    protected abstract IPaymentFacility<TPaymentType> PaymentFacility { get; }

    public void SendRequestToPaymentFacility()
    {
        PaymentFacility.Process((TPaymentType) this);
    }
}

public class BankAccount : PaymentType<BankAccount>
{
    public BankAccount(IPaymentFacility<BankAccount> paymentFacility)
    {
        _paymentFacility = paymentFacility;
    }

    protected override IPaymentFacility<BankAccount> PaymentFacility
    {
        get { return _paymentFacility; }
    }

    private readonly IPaymentFacility<BankAccount> _paymentFacility;
}

public interface IPaymentFacility<TPaymentType>
    where TPaymentType : PaymentType<TPaymentType>
{
    void Process(TPaymentType paymentType);
}

public class Bank : IPaymentFacility<BankAccount>
{
    public void Process(BankAccount paymentType)
    {
    }
}

Недостатком здесь является связь Банка с классом BankAccount.

Кроме того, ЭрикЛипперт не одобряет это , и он делает несколько превосходных очков.

...