【问题标题】:Building Generic Apps - Allowing Customer-specific Options构建通用应用程序 - 允许客户特定的选项
【发布时间】:2012-11-23 18:59:50
【问题描述】:

我已经构建了一个开源应用程序,我很想知道其他人如何处理客户特定的请求。让应用程序保持简单对我来说很重要;我并不是想为所有人创造一切。以这种方式,应用程序可能会变得臃肿、复杂并且几乎无法使用。但是,有一些特定于客户的选项会很好(它只是不适用于所有客户)。比如……

假设我们有一个名为 Server 的域实体。在 UI 中,我们让客户从服务器列表中进行选择。对于一家公司,按位置(美国、德国、法国等)过滤服务器会很有帮助。添加这样的服务器属性很容易:

public class Server
{
    public Location Location { get; set; }
    // other properties here
}

我担心随着时间的推移,服务器可能会因属性而变得臃肿。即使我只添加位置,也不是所有客户都会关心该属性。

一种选择是允许用户定义字段:

public class Server
{
    public string UserField1 { get; set; }
    public string UserField2 { get; set; }
    public string UserField3 { get; set; }
    // etc...
    // other properties here
}

这是最好的处理方式吗?我不喜欢通过将所有内容都设为字符串来消除类型安全性的事实。人们是否有其他/更好的方式来处理此类问题?甚至有这样的设计模式吗?

【问题讨论】:

  • 那些UserFieldX字段除了被用户显示和编辑之外,是否真的需要用于其他用途?
  • @JeffBridgman 理想情况下,用户会根据这些字段过滤结果。

标签: c# oop design-patterns architecture


【解决方案1】:

我从 Python 接近,我认为会很好听的是字典。键是你的字段名,值是,errrrr...值;)

在数据库中表示也很简单。

【讨论】:

    【解决方案2】:

    我在这里可能错了,但看起来您想使用相同的代码库处理不同版本的软件。我可以想到两种方法:

    1. 实际上为它定义不同的版本并为每个客户端处理更改。从域建模的角度来看,这不会给您带来问题,但需要一个支持基础架构,该基础架构必须根据您的客户要求进行扩展。有一些相关的问题(例如thisthisthis)。
    2. 在域模型级别处理此问题,作为用户定义的配置。这种方法的优点是您不必合并软件的多个版本,但这是以使您的模型更通用且可能更复杂为代价的。此外,您的测试肯定必须适应处理不同的场景。如果您朝着这个方向前进,我将建模一个表示属性的对象(带有名称和值),并将Server 类视为具有属性的集合。这样一来,您的模型仍能以 OO 风格捕捉您的需求。

    HTH

    【讨论】:

    • +1 的建议。我不认为我想根据客户要求进入应用程序的多个版本。这似乎是相当多的开销。对于第 2 点,您是说让每个属性都有自己的类吗?
    • 我会先创建一个带有namevalue 实例变量的Attribute 类。这允许您在域模型中捕获您的需求。如果需要,您可以稍后将其专门用于新的子类(例如只读属性、集合属性等)。您可能还想在谷歌上搜索 EAV 模型(例如,参见 this thread
    【解决方案3】:

    选项 1:说“不”。 :-)

    虽然我这么说(一半)是开玩笑的,但还是有一定道理的。通常情况下,开发人员会通过允许一两个自定义功能来让自己接受无休止的自定义,从而让滚雪球滚滚而来。

    当然,这必须是平衡的,听起来你可能会在一定程度上做到这一点。但如果你真的想让你的应用简单,那就保持简单,避免添加这样的自定义。

    选项 2:继承。

    如果您确实需要添加自定义,我会倾向于构建具有所有“标准”选项的基类,然后构建包含客户特定优化的客户特定类。

    例如:

    public class Server
    {
        // all standard properties here
    }
    

    那么对于 Joe's Pizza,您可以:

    public class JoesPizzaServer : Server
    {
        public Location Location { get; set; }
    }
    

    这样做的附带好处是,它允许您基于特定于客户端(或基本)模型的演示视图。

    例如,在 MVC 中,您可以像这样设置视图模型,然后您可以为每个客户提供特定的视图。

    例如,Bob's Burgers 对基本模型有自己的看法:

    @model MyApp.Server
    @* implement the base form *@
    

    Joe's Pizza 的视图将使用自定义模型:

    @model MyApp.JoesPizza
    @* implement the base form -- a partial view -- with addtional custom fields
    

    MVC 在支持这种模式方面做得非常好。如果您不使用 MVC(可能是 WPF 或 Web 窗体),仍然有一些方法可以利用部分“视图”文件来完成类似的事情。

    当然,您的数据库可以(并且可能应该)支持类似的继承模型。 Entity Framework 甚至支持像这样的各种继承模型。

    【讨论】:

    • 我希望我能为您的选项 #1 投两票。我坚信拒绝。因此,也许客户没有得到他们想要的一切,但即使他们不知道,这也是一件好事。第2点很有趣。这可能是开始玩的一个很好的基础......
    【解决方案4】:

    当然,这始终取决于您要允许多少自定义。在我们的产品中,我们甚至使用户能够完全定义他们自己的实体以及它们之间的属性和关系。基本上,我们称之为实体的每个 EntityObject 最终都包含一个值集合和对描述其中值的元模型的引用。我们设计了自己的查询语言,允许我们查询数据库并使用可翻译成任何目标语言的表达式(尽管我们目前只使用 SQL 和 .net)。

    游戏并没有就此结束,您很快就会发现验证规则、权限、默认值等成为必须具备的。当然,所有这些都需要 UI 支持,至少对于元模型的执行而言。

    因此,这实际上取决于最终用户应该能够执行的调整量。我猜在大多数情况下,如您所描述的,简单的用户字段就足够了。在这种情况下,我将提供一个字段并在其中存储 JSON 文本。在 UI 中,您至少可以提供一个允许结构和可扩展性的半体面的 UI。

    【讨论】:

      【解决方案5】:

      早期的常用方法是使用配置 XML 文件来处理这类事情。但是针对客户特定需求进行编程需要围绕您的编程方式进行全面思考。类似问题请参考this answer

      【讨论】:

      • 指向您其他答案的链接很好。我特别喜欢关于依赖注入的部分。当某个客户端使用应用程序时,可以将其依赖项注入到容器中。我认为在业务逻辑中处理这个我感觉更舒服,但棘手的部分可能是 UI。虽然我猜这可以/应该以同样的方式处理。
      【解决方案6】:

      在我看来,一个好的设计模式是在数据库级别使用模式,然后在类级别使用基本继承。

      CREATE TABLE dbo.A (
          ColumnA INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
          ColumnB VARCHAR(50),
          ColumnC INT,
          etc.
      )
      

      现在我们有一个需要一些特定功能的客户端,所以让我们在不同的架构中为这个表创建一个扩展:

      CREATE TABLE CustomerA.A (
          ColumnA INT NOT NULL PRIMARY KEY,
          Location VARCHAR(50)
      )
      

      但现在我们有另一个客户需要以不同的方式扩展它:

      CREATE TABLE CustomerB.B (
          ColumnA INT NOT NULL PRIMARY KEY,
          DataCenterID INT
      )
      

      虽然字段可能不相关,但您明白了,所以现在我们需要在这里构建客户特定的域模型:

      public abstract class A
      {
          public int ColumnA { get; set; }
          public string ColumnB { get; set; }
          public int ColumnC { get; set; }
      }
      
      public class CustomerA_A : A
      {
          public string Location { get; set; }
      }
      
      public class CustomerB_A : A
      {
          public int DataCenterID { get; set; }
      }
      

      所以现在当我们需要为客户 A 构建某些东西时,我们将构建他们的子类,并为客户 B 构建他们的子类,依此类推。

      现在,仅供参考,这是一个非常动态的系统的开始。我之所以这么说,是因为缺少的那部分还不是动态的,是用户界面。有很多方法可以完成,但超出了这个问题的范围。这是你必须考虑的事情。我这么说是因为你管理接口的方式将决定你如何知道构建哪个子类。

      我希望这会有所帮助。

      【讨论】:

      • +1 良好、可靠的 OOP 方法。一个问题是 Location 不只是针对 CustomerA;它适用于任何想要使用位置的客户。但总的来说,我明白你的意思。也许最终用户可以将自己定义为一个或多个客户的集合体。 UI 可以根据客户是否选择了该功能来启用/禁用该功能。
      • @BobHorn,这是一个很好的思路。您可以在您认为必要的任何级别上分解这些模式。根据您的 cmets,客户实际上可能有点细。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多