【问题标题】:Design Question - OO food application设计题——OO食品应用
【发布时间】:2011-01-18 03:02:54
【问题描述】:

假设我有许多用户控件,每个用户控件都在一个 tabitem 内,在一个窗口内。

例如,假设这是一个食物收集应用程序。然后我们有标签水果、蔬菜和零食。每个选项卡将显示该主题的食物列表,并允许用户添加、删除、修改每个部分中的食物。食物存储在单独的文本文件中,即 Fruit.txt、Vegetable.txt、Snack.txt

实际的文本文件可能如下所示(vegetable.txt):

Name        Carbs    Fat
Eggplant    2        1.1
Cucumber    3        0.5
etc

现在这是一个大列表,并且有一个加载方法可以将所有蔬菜拉到一个列表中

我的问题是这个 loadVegetables 方法在文件后面的代码中,我最终在整个地方重复这个加载方法,因为我还有另一个屏幕,比如 ReviewAllFood、AddVegetable 等等。水果和零食的加载方法。

这更像是一个设计问题,我想知道如何设置它以不重复此代码。我可以在 load 方法所在的位置有一个 VegetableManager(或其他东西)类,但这实际上意味着更少的重复代码吗?然后在每个屏幕中,我必须创建 VegetarianManager 的对象并调用它的加载方法。所以我认为效率方面并没有更好,但我确实实现了更好的设计。

我想我在这里遗漏了一些东西。自从我学习内聚和耦合已经有一段时间了,我想我现在对这些概念感到困惑。感谢有人可以针对这种情况提出设计建议,并解释他们为什么选择它以及为什么它比我目前的做法更好。

感谢阅读。

【问题讨论】:

    标签: c# wpf oop coupling


    【解决方案1】:

    我建议您在阅读文件时将蔬菜(或您正在加载的任何东西)拉出一次。然后将它们存储在一些底层数据模型中。您可以将列表以及您需要的任何其他控件绑定到底层数据模型。数据被加载一次,但各种视图都可以显示它。

    编辑:添加代码

    List<T> loadObjects(File file, ILineConversionStrategy strategy) {
       // read eaqch line of the file
       // for each line
       T object = strategy.readLine(line);
       list.add(object);
       return listOfObjects;
    }
    

    编辑 2:数据模型

    class FoodModel {
       List<Vegetable> getVegetables();
       List<Fruit> getFruit();
       // etc
    }
    

    【讨论】:

    • 底层数据模型是什么(建议)?我想另一个问题是,因为作为应用程序的一部分,用户可以添加蔬菜等......目前这些都被直接写回文件,所以我不断地重新加载,因为已经做出了潜在的改变。
    • 您的模型存储应用程序所需的任何数据。它可以像具有一堆绑定属性的对象一样简单。我建议不要在每次更改时写回文件,而是写入模型,因为 I/O 慢得多。您可以在应用关闭时(或根据需要定期)将模型刷新到文件中。
    • 我不确定我是否完全理解你。目前,它们存储在许多 List、List、List 中——但这在很多地方后面的代码中重复出现。我可以像我提到的那样创建一个单独的类,但是我只是在各处创建该类的一个对象。你的解释会是什么样子?你能展示一些示例/示例代码吗?
    • 所以我想我们在这里讨论两个问题。我已经发布了一些示例代码,展示了如何避免在任何地方为每种类型的对象重复加载代码。它使用策略模式来确定如何将文件中的行转换为适当的对象 - 蔬菜、水果等。这样可以使用相同的代码来加载每种类型的文件,只需将文本行转换为对象是不同的(代码是java,因为我是java开发人员)。对于到处调用代码的另一个问题,它应该只有一次,当你从文件中加载它时。
    • 那么如何从其他类访问该集合?
    【解决方案2】:
        public interface IEatable {}
    
        class Vegitable : IEatable 
        { string Name { get; set; } }
        class Fruit : IEatable 
        { string Name { get; set; } }
    
        public interface IEatableManager
        {
            List<Vegitables> LoadEatables(string filePath);
        }
        public class VetabaleManager : IEatableManager
        {
            #region IEatableManagerMembers    
            public List<Vegitable> LoadVegs(string filePath)
            {
                throw new NotImplementedException();
            }    
            #endregion
        }
        .
        .
        .
    

    使用上述设计需要考虑几件事

    必读:

    【讨论】:

      【解决方案3】:

      我可以有一个 VegetableManager(或 东西) 加载方法所在的类 是,但这实际上是否意味着更少 重复代码?然后在每个屏幕中我 必须创建对象 蔬菜管理器并调用它的负载 反正方法。

      这样做的重点不是效率(即性能)。关键是将加载该数据的细节封装到单个隔离对象中。例如,假设您的站点变得非常大,并且您决定将数据存储移动到数据库以实现可伸缩性和性能。在您描述的现有代码中,您必须遍历每个用户控件或页面并更改加载方法的逻辑。最好的情况是这很痛苦,最坏的情况是你错过了一些或复制粘贴不正确。如果逻辑被封装到一个专用对象中,其唯一职责就是知道如何从某个地方加载数据,那么您只需进行一次更改。

      用户控件的代码隐藏:

      protected void Page_Load(object sender, EventArgs e) {
        var veggieManager = new VegetableManager();
        VeggieListControl.DataSource = veggieManager.GetAll();
        VeggieListControl.DataBind();
      }
      

      蔬菜管理器.cs:

      public class VegetableManager {
        private static Collection<Vegetable> _veggies;
        private static object _veggieLock;
      
        public ReadOnlyCollection<Vegetable> GetAll() {
          if (_veggies == null) {
            lock(_veggieLock) { //synchronize access to shared data
              if (_veggies == null) { // double-checked lock
                // logic to load the data into _veggies
              }
            }
          }
      
          return new ReadOnlyCollection(_veggies);
        }
      
        public void Add(Vegetable veggie) {
          GetAll(); // call this to ensure that the data is loaded into _veggies
          lock(_veggieLock) { //synchronize access to shared data
            _veggies.Add(veggie);
            // logic to write out the updated list of _veggies to the file
          }
        }
      }
      

      因为_veggies 是static,所以内存中只有一个蔬菜集合,尽管多个调用者会实例化VegetableManager。但是因为它是静态的,所以如果您有一个多线程应用程序(例如网站),您必须在所有线程中同步对该字段的访问(因此是 locks)。

      就良好的面向对象而言,这只是冰山一角。我建议仔细阅读 UncleBob's SOLID principles 和 Domain-Driven Design (free e-book)。

      所以,是的,您正在重复某些事情,但您所重复的只是一个方法调用,并且可以重复。 DRY 意味着减少“逻辑”代码的重复,即决策和算法;简单的方法调用不属于此范围。但是,如果您愿意,您可以将逻辑合并到一个基类中来执行此操作,从而有效地将用户控件与必须了解蔬菜管理器的情况隔离开来,尽管我认为这是面向对象的过度杀伤力,或者 OOO :-)

      public abstract class FoodUserControl : UserControl {
        protected List<Vegetable> GetVeggies() {
          return new VegetableManager().GetAll();
        }
      }
      

      那么您的实际控件将来自此而不是来自 UserControl。

      更新

      急切加载的 VegetableManager.cs:

      public class VegetableManager {
        private static Collection<Vegetable> _veggies;
        private static object _veggieLock;
      
        static VegetableManager() {
          // logic to load veggies from file
        }
      
        public ReadOnlyCollection<Vegetable> GetAll() {
          return new ReadOnlyCollection(_veggies);
        }
      
        public void Add(Vegetable veggie) {
          lock(_veggieLock) { //synchronize access to shared data
            _veggies.Add(veggie);
            // logic to write out the updated list of _veggies to the file
          }
        }
      }
      

      请注意,这个预加载版本不必在构造函数中围绕加载代码进行双重检查锁定。另请注意,加载代码位于static 构造函数中,因为此代码初始化static 字段(否则,您将在每次构造时将文件中的数据重新加载到相同的共享static 字段中)。因为蔬菜是预先加载的,所以您不需要在 GetAll 或 Add 中加载。

      【讨论】:

      • 我知道有多种方法可以对持久性进行建模。我正在努力解决的是以下问题。使用你的例子。我最初的想法是在 FoodUserControl 内部,假设我有带有文本字段和添加按钮的 ui。我加了一种蔬菜。我之前所做的是将蔬菜添加到本地列表中。但实际上我应该在蔬菜管理器 addVeggie 中有一个方法。然后我们有类似 var veggieManager = new VegetableManager(); veggieManager.AddVeggie(txt.text, txt2.text);将其添加到蔬菜经理列表中,
      • 不是我在课堂上的列表的本地副本。然后唯一的问题是当 veggiemangers 列表发生变化时更新 UI。 AS 反对使用 var veggieManager = new VegetableManager();列表 蔬菜 = veggieManager.GetVeggies(); veggies.Add(txt.text, txt2.text);
      • 为什么每种食物都有单独的经理?你最终会遇到很多不同的经理。然后,如果某物想要访问水果和蔬菜,则需要蔬菜和水果经理。
      • 好吧,在处理 Add 事件时,首先您调用 VegetableManager.Add 将蔬菜添加到系统中,以便所有其他请求都可以访问它。其次,您调用 VeggieManager.GetAll 并将您的列表用户控件重新绑定到此更新的蔬菜列表。
      • @gWiz,对于简化来说是有意义的,尽管您不需要为不同的文件使用 switch 语句。策略模式是根据对象类型(通过将对象类型映射到文件写入器类型)选择文件写入器到通用存储库的好方法。但是,这实际上取决于要写出多少种不同的对象(显然,该模式比其他解决方案具有更好的扩展性,但对于较小的问题,它可能是矫枉过正的)。
      【解决方案4】:

      我会为此使用repository pattern。首先,创建一个包含从每个文本文件中检索对象的方法的类:

      public class FoodRepository
      {
          public IList<Vegetable> GetVegetables() { ... }
          public IList<Fruit> GetFruit() { ... }
          // etc.
      }
      

      这个类应该是您的应用程序中唯一知道食物实际上存储在文本文件中的类。

      一旦你开始工作,你可能需要考虑caching frequently used data 来提高性能。

      【讨论】:

      • AddVegetable 方法会去哪里?所以我可以从其他类中访问它——只是说我想遍历蔬菜——foreach(FoodRepository.Vegetables 中的蔬菜)。其实我猜这个方法会在那个班上。我想我正在努力解决的是我如何拥有一个主集合,而不是在任何地方创建 FoodRepository 的实例 - 每个都有不同的集合等。比如我们有 foodviewer 和 addfood 用户控件。在 addfood 中,我将食物添加到该实例集合中,但在 foodviewer 中,没有将食物添加到该实例集合中。
      • 除非我告诉它重新加载食物并告诉 addfood 方法写回文件。 (希望这是有道理的)
      • 有多种方法可以对持久性进行建模,这只是其中一种。但在我看来,提问者需要一个解释为什么任何持久性建模都是有益的核心。
      • @gWiz -- 我有相反的看法,重新阅读后仍然如此。该网站对简单问题的复杂答案。问题是关于在小型 Web 应用程序中集中数据访问的第一步,接受的答案提到了 SOLID、DRY、多线程和那本令人痛苦的 DDD 书。
      • @baron -- 作为第一步,我会将所有数据访问权限(包括 AddVegetable)放入一个存储库类中,除非它变得笨拙。我不确定我是否理解问题的其余部分,但您可以将类设为静态并使用缓存,这样您就只有一个实例。
      猜你喜欢
      • 2011-01-16
      • 2020-06-29
      • 1970-01-01
      • 2016-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-04
      相关资源
      最近更新 更多