从2个派生类中选择正确的枚举列表

时间:2012-12-01 17:17:26

标签: c# dictionary enums modeling

我正在建立一个图书馆应用程序,我有一本抽象书类,两种类型的派生书和两本能够保存书籍类型的枚举。 每本书都可以与一种或多种类型相关。

    abstract public class Book
    {   
       public int Price { get; set; } 
       ...
    }

    public enum ReadingBooksGenre
    {
       Fiction,
       NonFiction
    }

    public enum TextBooksGenre
    {
        Math,
        Science
    } 

    abstract public class ReadingBook : Book
    { 
        public List<ReadingBooksGenre> Genres { get; set; }
    }

    abstract public class TextBook : Book
    {   
        public List<TextBooksGenre> Genres { get; set; }
    }

现在我想根据书籍类型保存折扣(没有双倍折扣,只计算最高折扣),所以我正在考虑制作两个字典,以保存每种类型的所有折扣,如​​下所示:

    Dictionary<ReadingBooksGenre, int> _readingBooksDiscounts;
    Dictionary<TextBooksGenre, int> _textBooksDiscounts;

所以现在我需要检查每本书的类型以找到最高的折扣,有没有比这更好的方法:

    private int GetDiscount(Book b)
    {
        int maxDiscount = 0;
        if (b is ReadingBook)
        {
            foreach (var genre in (b as ReadingBook).Genres)
            {
                // checking if the genre is in discount, and if its bigger than other discounts.
                if (_readingBooksDiscounts.ContainsKey(genre) && _readingBooksDiscounts[genere]>maxDiscount)
                {
                    maxDiscount = _readingBooksDiscounts[genere];
                }
            }
        }
        else if (b is TextBook)
        {
            foreach (var genre in (b as TextBook).Genres)
            {
                if (_textBooksDiscounts.ContainsKey(genre) && _textBooksDiscounts[genere]>maxDiscount)
                {
                    maxDiscount = _textBooksDiscounts[genere];
                }
            }
        }
        return maxDiscount;
    }

有没有办法在不检查类型的情况下选择正确的字典? 或者甚至可以在没有字典或使用字典的情况下完成它? 也许以某种方式将书籍类型与Enum连接?

很高兴听到任何改进建议。

(根据书籍名称,日期和作者有很多折扣。甚至更多的书籍类型,这就是为什么这种方式对我来说似乎不对)

谢谢。

3 个答案:

答案 0 :(得分:1)

您的GetDiscount方法是Open/Closed principle违规的典型示例。添加新图书类型时,您必须将新if块添加到GetDiscount

更好的方法是使用一些现有的技术,允许您添加新功能,而无需修改现有代码。例如,Composite pattern。我将写一些复合折扣评估器的草案实现。您可以轻松地根据任何图书标准(日期,价格等)添加新的折扣评估器。

另外,我将使用接口而不是继承。继承是两个实体之间非常强大的联系,在这种情况下它是过度的。

列表长167行,所以这里更舒适pastebin copy

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            var compositeDiscountEvaluator = ConfigureEvaluator();
            var scienceBook = new TextBook
                               {
                                   Date = DateTime.Now,
                                   Price = 100,
                                   Genres = new[] {TextBooksGenre.Math}
                               };
            var textBook = new TextBook
                               {
                                   Date = DateTime.Now,
                                   Price = 100,
                                   Genres = new[] {TextBooksGenre.Math, TextBooksGenre.Science}
                               };
            var fictionBook = new ReadingBook
                        {
                            Date = DateTime.Now,
                            Price = 200,
                            Genres = new[] {ReadingBooksGenre.Fiction}
                        };
            var readingBook = new ReadingBook
                                  {
                                      Date = DateTime.Now,
                                      Price = 300,
                                      Genres = new[] {ReadingBooksGenre.Fiction, ReadingBooksGenre.NonFiction}
                                  };
            Console.WriteLine(compositeDiscountEvaluator.GetDiscount(scienceBook));
            Console.WriteLine(compositeDiscountEvaluator.GetDiscount(textBook));
            Console.WriteLine(compositeDiscountEvaluator.GetDiscount(fictionBook));
            Console.WriteLine(compositeDiscountEvaluator.GetDiscount(readingBook));
        }

        private static IDiscountEvaluator ConfigureEvaluator()
        {
            var evaluator = new CompositeDiscountEvaluator();
            evaluator.AddEvaluator(new ReadingBookDiscountEvaluator());
            evaluator.AddEvaluator(new TextBookDiscountEvaluator());
            return evaluator;
        }
    }

    class CompositeDiscountEvaluator : IDiscountEvaluator
    {
        private readonly ICollection<IDiscountEvaluator> evaluators;

        public CompositeDiscountEvaluator()
        {
            evaluators = new List<IDiscountEvaluator>();
        }

        public void AddEvaluator(IDiscountEvaluator evaluator)
        {
            evaluators.Add(evaluator);
        }

        public bool CanEvaluate<TGenre>(IBook<TGenre> book)
        {
            return evaluators.Any(e => e.CanEvaluate(book));
        }

        public int GetDiscount<TGenre>(IBook<TGenre> book)
        {
            if (!CanEvaluate(book))
                throw new ArgumentException("No suitable evaluator");
            return evaluators.Where(e => e.CanEvaluate(book)).Select(e => e.GetDiscount(book)).Max();
        }
    }

    interface IDiscountEvaluator
    {
        bool CanEvaluate<TGenre>(IBook<TGenre> book);
        int GetDiscount<TGenre>(IBook<TGenre> book);
    }

    class ReadingBookDiscountEvaluator : IDiscountEvaluator
    {
        private readonly IDictionary<ReadingBooksGenre, int> discounts;

        public ReadingBookDiscountEvaluator()
        {
            discounts = new Dictionary<ReadingBooksGenre, int>
                            {
                                {ReadingBooksGenre.Fiction, 3},
                                {ReadingBooksGenre.NonFiction, 4}
                            };
        }

        public bool CanEvaluate<TGenre>(IBook<TGenre> book)
        {
            return book is ReadingBook;
        }

        public int GetDiscount<TGenre>(IBook<TGenre> book)
        {
            var readingBook = (ReadingBook) book;
            return readingBook.Genres.Select(g => discounts[g]).Max();
        }
    }

    class TextBookDiscountEvaluator : IDiscountEvaluator
    {
        private readonly IDictionary<TextBooksGenre, int> discounts;

        public TextBookDiscountEvaluator()
        {
            discounts = new Dictionary<TextBooksGenre, int>
                            {
                                {TextBooksGenre.Math, 1},
                                {TextBooksGenre.Science, 2}
                            };
        }

        public bool CanEvaluate<TGenre>(IBook<TGenre> book)
        {
            return book is TextBook;
        }

        public int GetDiscount<TGenre>(IBook<TGenre> book)
        {
            var textBook = (TextBook) book;
            return textBook.Genres.Select(g => discounts[g]).Max();
        }
    }

    interface IBook<TGenre>
    {
        int Price { get; set; }
        DateTime Date { get; set; }
        TGenre[] Genres { get; set; }
    }

    class ReadingBook : IBook<ReadingBooksGenre>
    {
        public int Price { get; set; }
        public DateTime Date { get; set; }
        public ReadingBooksGenre[] Genres { get; set; }
    }

    class TextBook : IBook<TextBooksGenre>
    {
        public int Price { get; set; }
        public DateTime Date { get; set; }
        public TextBooksGenre[] Genres { get; set; }
    }

    enum TextBooksGenre
    {
        Math,
        Science
    }

    public enum ReadingBooksGenre
    {
        Fiction,
        NonFiction
    }
}

答案 1 :(得分:0)

我会创建一个接受字典和相应类型书的通用方法。这样,您就可以获得足够通用的算法,并且您的代码非常干净。当然,这种方式GetDiscount也是通用的,但你不能以错误的方式混合它们。 (哦,是的,Book也是通用的类型返回正确的类型。) 我认为这段代码可以用一点LINQ来实现,但也许它不值得额外的努力。

答案 2 :(得分:0)

在我看来,系统中“类型”的概念对于简单的枚举来说太复杂了。我将概念推广到自己的类层次结构:

public class Genre
{
    public int Discount { get; set; }
}
public class ReadingBooksGenre : Genre { }
public class TextBooksGenre : Genre { }

abstract public class Book<T> where T : Genre
{
    public List<T> Genres { get; set; }
    public int Discount
    {
        get
        {
            return (Genres.Count == 0) ? 0 : Genres.Max(g => g.Discount);
        }
    }
}
abstract public class ReadingBook : Book<ReadingBooksGenre> { }
abstract public class TextBook : Book<TextBooksGenre> { }