我正在构建一个图书库应用程序,我有一个抽象的图书类,两种派生图书和两个枚举,这将保存图书的类型。
每本书可以与一种或多种体裁相关。
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;
}
有没有办法在不检查类型的情况下选择正确的词典?
或者甚至可以不用字典,或者用字典?
也许可以用某种方式将book type与enum联系起来?
很高兴听到任何改进的建议。
(根据书名、日期和作者,有很多折扣。甚至更多的书籍类型这就是为什么我觉得这样做不对的原因)
谢谢您。
最佳答案
你的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
}
}