5

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

    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;
    }

有没有办法在不检查类型的情况下选择正确的字典?或者甚至是一种不用字典或使用字典的方法?也许以某种方式将书籍类型与枚举联系起来?

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

(根据书名、日期和作者,还有很多折扣。甚至更多的书类型,这就是为什么这种方式对我来说不合适的原因)

谢谢你。

4

3 回答 3

1

您的GetDiscount方法是违反开放/封闭原则的经典示例。当您添加新书类型时,您必须将新if块添加到GetDiscount.

更好的方法是使用一些现有的技术,这些技术允许您添加新功能而无需修改现有代码。例如,复合模式。我将编写一些复合折扣评估器的实现草案。您可以轻松地添加基于任何书籍属性(日期、价格等)的新折扣评估器。

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

清单有 167 行,所以这里是更舒适的pastebin 复制

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
    }
}
于 2012-12-01T18:59:30.483 回答
0

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

于 2012-12-01T17:57:00.170 回答
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> { }
于 2012-12-01T19:41:39.770 回答