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

Допустим, у меня есть следующий метод, который, учитывая 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 ответа

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

public ProcessingPlant GetProcessingPlant(CandyBar candyBar) {

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

}

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

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

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

Один из вариантов - добавить параметр интерфейса 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. Настройте его так, чтобы банковский счет получил банк и т. Д.

Недостатком является то, что платежные средства будут иметь доступ только к общедоступным (или, возможно, внутренним) членам класса 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.

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

Другие вопросы по тегам