设计问题 - OO 食品应用

发布于 2024-08-22 17:31:53 字数 781 浏览 6 评论 0原文

假设我有许多用户控件,每个用户控件位于一个窗口内的 tabitem 内。

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

实际的文本文件可能看起来像这样 (vegetable.txt):

Name        Carbs    Fat
Eggplant    2        1.1
Cucumber    3        0.5
etc

现在这是一个很大的列表,并且有一个加载方法可以提取所有内容 。

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

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

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

感谢您的阅读。

Say I have a number of usercontrols, each usercontrol inside a tabitem, inside a window.

For example, let say this is a food collection application. Then we have tabs Fruit, Vegetables and Snacks. Each tab will show a list of food of that subject, and allow the user to add, delete, modify the food in each section. The food is stored in seperate textfiles, i.e. Fruit.txt, Vegetable.txt, Snack.txt

The actual text files might look something like this (vegetable.txt):

Name        Carbs    Fat
Eggplant    2        1.1
Cucumber    3        0.5
etc

Now this is a large list and there is a load method which pulls all the vegetables out into a List

The question I have is this loadVegetables method is in the code behind file, and I end up repeating this load method all over the place, because I have another of other screens like ReviewAllFood, AddVegetable, etc. along with all the other load methods for fruit and snacks.

This is more of a design question, I'm wondering how I set this up to not repeat this code. I could have a VegetableManager (or something) class where the load method is, but does this actually mean less repeated code? Then in each screen I have to create object of VegetableManager and call its load method anyway. So I guess efficiency wise its no better, but I do achieve a better design.

I think I'm missing something here. It's been a while since I studied cohesion and coupling and I think i'm confusing myself with these concepts at the moment. Appreciate if someone could suggest a design for this situation and explain why they chose it and why its better than how i'm doing it at the moment.

Thanks for reading.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

守护在此方 2024-08-29 17:31:53

我可以有一个 VegetableManager (或者
某事)加载方法所在的类
是,但这实际上意味着更少
重复代码?然后在每个屏幕中我
必须创建对象
VegetableManager 并调用其负载
无论如何,方法。

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

用户控件的代码隐藏:

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

VegetableManager.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静态,因此内存中只有一组蔬菜,尽管多个调用者将实例化 VegetableManager。但因为它是静态的,所以如果您有一个多线程应用程序(例如网站),您必须在所有线程之间同步对该字段的访问(因此需要)。

这只是良好面向对象的冰山一角。我建议仔细阅读 UncleBob 的 SOLID 原则领域驱动设计 (免费电子书)。

所以,是的,您正在重复某些内容,但您重复的只是方法调用,并且可以重复。 DRY意味着减少“逻辑”代码的重复,即决策和算法;简单的方法调用不属于此范围。但是,如果您愿意,您可以将逻辑合并到基类中,从而有效地将用户控件与必须了解 VegetableManager 的情况隔离开来,尽管我认为这是面向对象的杀伤力,或者 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 中加载。

I could have a VegetableManager (or
something) class where the load method
is, but does this actually mean less
repeated code? Then in each screen I
have to create object of
VegetableManager and call its load
method anyway.

The point of doing this is not efficiency (i.e. performance). The point is to encapsulate the details of loading that data into a single isolated object. Say for example that your site gets really big and you decide to move the data storage to a database for scalability and performance. In the existing code as you described, you'll have to go through each user control or page and change the logic of the load method. At the best this is a pain, and at the worst you miss some or copy-paste incorrectly. If the logic is encapsulated into a dedicated object, whose only responsibility is to know how to load the data from somewhere, then you only have to make the change once.

codebehind of user control:

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

VegetableManager.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
    }
  }
}

Because _veggies is static, there is only one collection of veggies in memory, despite the fact that multiple callers will instantiate VegetableManager. But because it's static, if you have a multi-threaded application (e.g. a website) you must synchronize access to that field across all threads (hence the locks).

This is the tip of the iceberg in terms of good object-orientation. I recommend perusing UncleBob's SOLID principles, and Domain-Driven Design (free e-book).

So, yes you are repeating something, but all you're repeating is a method call, and that is ok to repeat. DRY means to mitigate the duplication of "logical" code, i.e. decision-making and algorithms; simple method calls do not fall under this. However, if you want, you can consolidate logic into a base class do this, effectively isolating the user controls from having to know about VegetableManager, though I think this is object-orientation overkill, or OOO :-)

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

Then your actual controls would derive from this instead of from UserControl.

Update

Eager-loading 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
    }
  }
}

Notice this eager-loading version doesn't have to do double-checked locking around the load code in the constructor. Also notice that the load code is in a static constructor, since this code initializes a static field (otherwise, you'd be reloading the data from the file on every construction into the same shared static field). Because veggies are eager-loaded, you don't need to load in GetAll or Add.

嘿嘿嘿 2024-08-29 17:31:53

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

编辑:添加代码

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 would suggest pulling the vegetables (or whatever it is you're loading) out once when you read the file. Then you store them in some underlying data model. You can bind the list, and whatever other controls you need to, to the underlying data model. The data gets loaded once, but various views can display it.

EDIT: Adding code

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

EDIT 2: Data model

class FoodModel {
   List<Vegetable> getVegetables();
   List<Fruit> getFruit();
   // etc
}
你好,陌生人 2024-08-29 17:31:53

我将为此使用 存储库模式。首先,创建一个包含从每个文本文件中检索对象的方法的类:

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

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

一旦您开始工作,您可能需要考虑缓存常用数据以提高性能。

I would use the repository pattern for this. As a start, create one class containing methods to retrieve the objects from each text file:

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

This class should be the only class in your application that is aware that foods are actually stored in text files.

Once you get that working you might want to consider caching frequently used data to improve performance.

许你一世情深 2024-08-29 17:31:53
    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
    }
    .
    .
    .

使用上面的设计需要考虑一些事情

并且必须阅读:

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

There are several things you need to consider for using a design like above

and a must read:

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文