5

私は本のライブラリアプリを構築しています。抽象的な本のクラス、2種類の派生本、および本のジャンルを保存する2つの列挙型があります。各本は、1つ以上のジャンルに関連付けることができます。

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

今、私は本のジャンルに基づいて割引を保存したいので(二重割引はなく、最高の割引のみが計算されます)、次のように、各ジャンルのすべての割引を保存する2つの辞書を作成することを考えています。

    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方法は、Open/Closed 原則違反の典型的な例です。新しい本の種類を追加するときは、新しいifブロックをに追加する必要がありますGetDiscount

より良い方法は、既存のコードを変更する必要なく新しい機能を追加できる既存の技術を使用することです。たとえば、複合パターン。複合割引エバリュエーターの実装のドラフトを書きます。書籍のプロパティ (日付、価格など) に基づいて、新しい割引評価者を簡単に追加できます。

また、継承の代わりにインターフェイスを使用します。継承は 2 つのエンティティ間の非常に強力なリンクであり、この場合は過剰です。

リストの長さは 167 行です

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 に答える