Работа с ненаследуемыми методами в подклассе - PullRequest
2 голосов
/ 13 ноября 2011

У меня есть ваучер класса:

public abstract class Voucher
    {
        public int Id { get; set; }
        public decimal Value { get; protected set; }
        public const string SuccessMessage = "Applied";
    }

и подкласс GiftVoucher

public class GiftVoucher : Voucher
    {
    }

и другой подкласс DiscountVoucher

public class DiscountVoucher : Voucher
    {
        public decimal Threshold { get; private set; }
        public string FailureMessage { get { return "Please spend £{0} to use this discount"; } }
    }

Вы можете видеть, что DiscountVoucher имеет несколько определенных свойств Threshold и FailureMessage, которые соответственно представляют сумму денег, которую нужно потратить, чтобы получить скидку, и сообщение об ошибке, которое отображается, если пользователь не потратил эти деньги.

Мой вопрос такой. У меня есть коллекция объектов Voucher, и то, что я не хочу делать в своем коде, выглядит примерно так

if (voucher is DiscountVoucher)
{
   // cast voucher to a DiscountVoucher and then call the specific methods on it
} 

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

Ответы [ 5 ]

4 голосов
/ 13 ноября 2011

В общем случае: Нет!

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

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

Может быть методом, который возвращает ноль, 0 или просто ничего не делает.

В этом случае

public virtual string FailureMessage { get { return string.Empty; } }

может быть разумной реализацией.

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

3 голосов
/ 13 ноября 2011

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

Например, выможет закончиться пятью ваучерами, которые реализуют интерфейсы, называемые «StandardVoucher», и тремя, называемыми «DiscountVoucher», но вместо того, чтобы обрабатывать восемь случаев, у вас теперь есть только два.

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

3 голосов
/ 13 ноября 2011

Нет, вы не можете, потому что перебирая более общие объекты и затем вызывая определенные методы, потребовалось бы использовать полиморфизм, чтобы иметь выделенную функциональность в каждом подклассе. Без метода переопределения в суперклассе у вас нет способа получить то, что вы хотите.

1 голос
/ 13 ноября 2011

Есть только случаи, когда вам придется разыгрывать. Здесь я бы реализовал общий механизм проверки ошибок:

public abstract class Voucher
{
    public int Id { get; set; }
    public decimal Value { get; protected set; }
    public virtual string SuccessMessage { get { return "Applied"; } }
    public virtual string FailureMessage { get { return String.Empty; } }
    public virtual bool Ok { get { return true; } }
}

public class GiftVoucher : Voucher { }

public class DiscountVoucher : Voucher
{
    public decimal Threshold { get; private set; }
    public override string FailureMessage { get { return "Please spend £{0} to use this discount"; } }
    public override bool Ok { get { return Value >= Threshold; } }
}

Затем вы можете проверить целостность ваучера любого типа, не разыгрывая:

if (voucher.Ok) {
    Console.WriteLine(voucher.SuccessMessage);
} else {
    Console.WriteLine(voucher.FailureMessage);
}

Как правило, попробуйте позволить объектам делать свои собственные вещи (здесь, чтобы проверить, в порядке ли они) вместо того, чтобы делать это "извне". Даже тот факт, что в GiftVoucher не может возникнуть ошибка, не должен быть известен «внешнему миру».

1 голос
/ 13 ноября 2011

Я думаю, вы правы, подозревая код, который вы описываете.

Моя первая мысль: если члены DiscountVoucher недостаточно широки, чтобы существовать как виртуальные или абстрактные в Voucherтогда функция, принимающая Voucher в качестве параметра, не должна их трогать.

Итак, чтобы решить проблему, я бы сказал, что вы можете сделать одну из двух вещей:

СначалаВы можете добавить виртуальные методы или свойства к Voucher, например,

public abstract class Voucher
{
    public int Id { get; set; }
    public decimal Value { get; protected set; }
    public const string SuccessMessage = "Applied";
    public decimal Threshold { get { return 0.0; } }
    public string FailureMessage { get { return ""; } }
}

Во-вторых, вы можете добавить методы, которые делают то, что вы ожидаете, для каждого Voucher.Вы сгруппировали их как ваучеры, так что подумайте, что у них общего.Если, скажем, GiftVoucher и DiscountVoucher выполняют свои собственные вычисления, чтобы определить, применимы ли они к текущему ShoppingCart, то для обнаружения этого можно использовать метод Voucher с именем isValid().Например,

public abstract class Voucher
{
    public bool isValid(ShoppingCart sc);
    public string FailureMessage { get { return "This voucher does not apply"; } }
    // ...
}

public class DiscountVoucher : Voucher
{
    private decimal Threshold;
    public override bool isValid(ShoppingCart sc)
    {
        return (sc.total >= Threshold);
    }
    public override string FailureMessage
    {
        get { return FormatString("Please spend £{0} to use this discount", Threshold); }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...