我正在建立一个图书馆应用程序,我有一本抽象书类,两种类型的派生书和两本能够保存书籍类型的枚举。 每本书都可以与一种或多种类型相关。
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连接?
很高兴听到任何改进建议。
(根据书籍名称,日期和作者有很多折扣。甚至更多的书籍类型,这就是为什么这种方式对我来说似乎不对)
谢谢。
答案 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> { }