如何设计一个通用的商业实体,仍然是OO?

Jag*_*mag 18 oop design-patterns product business-objects

我正在研究一种打包产品,该产品应该满足不同要求(在一定程度上)的多个客户,因此应该以足够灵活的方式构建,以便每个特定客户可以定制.我们在这里讨论的那种定制是不同的客户端可能对某些关键业务对象具有不同的属性.此外,他们也可以将不同的业务逻辑与其附加属性联系起来

作为一个非常简单的例子:将 "汽车"视为系统中的商业实体,因此有4个关键属性,即VehicleNumber,YearOfManufacture,Price和Color.

使用该系统的其中一个客户端可能会向Automobile添加另外两个属性,即ChassisNumber和EngineCapacity.此客户端需要与这些字段关联的一些业务逻辑,以验证添加新汽车时系统中不存在相同的chassisNumber.

另一个客户端只需要一个名为SaleDate的附加属性.SaleDate有自己的业务逻辑检查,当输入销售日期时,该检查验证某些警方记录中的车辆是否作为被盗车辆存在

我的大多数经验主要是为单个客户端制作企业应用程序,我真的很难看到我如何处理其属性是动态的业务实体,并且还具有在面向对象范例中具有动态业务逻辑的能力.

关键问题

  • 是否有任何通用的OO原则/模式可以帮助我解决这种设计问题?

我相信从事通用/包装产品的人在大多数情况下都会面临类似的情况.任何建议/指针/一般指导也不胜感激.

我的技术是.NET 3.5/C#,该项目有一个分层架构,其业务层由包含业务逻辑的业务实体组成

Str*_*ior 13

这是我们面临的最大挑战之一,因为我们有多个客户端都使用相同的代码库,但需求也各不相同.让我与您分享我们的进化故事:

我们公司最初只有一个客户,当我们开始接触其他客户时,你会在代码中看到这样的事情:

if(clientName == "ABC") {
    // do it the way ABC client likes
} else {
    // do it the way most clients like.
}
Run Code Online (Sandbox Code Playgroud)

最终我们明智地知道这使得代码变得非常丑陋和无法管理.如果另一个客户希望他们在一个地方表现得像ABC一样,而在另一个地方表现CBA,我们就被困住了.因此,我们转向使用一堆配置点的.properties文件.

if((bool)configProps.get("LastNameFirst")) {
    // output the last name first
} else {
    // output the first name first
}
Run Code Online (Sandbox Code Playgroud)

这是一个改进,但仍然非常笨重."神奇的弦乐"很丰富.围绕各种属性没有真正的组织或文档.如果没有在正确的组合中使用,许多属性依赖于其他属性并且不会做任何事情(或者甚至会破坏某些东西!).我们在一些迭代中花费了很多(甚至可能是大部分)时间用于修复因为我们已经"修复"某个客户端而破坏了另一个客户端配置的错误而引发的错误.当我们得到一个新的客户端时,我们只会从另一个客户端的属性文件开始,该客户端的配置"最喜欢"这个客户端想要的那个,然后尝试调整内容,直到它们看起来正确.

我们尝试使用各种技术来使这些配置点不那么笨重,但只取得了适度的进展:

if(userDisplayConfigBean.showLastNameFirst())) {
    // output the last name first
} else {
    // output the first name first
}
Run Code Online (Sandbox Code Playgroud)

有一些项目可以控制这些配置.其中一个涉及编写基于XML的视图引擎,以便我们可以更好地自定义每个客户端的显示.

<client name="ABC">
    <field name="last_name" />
    <field name="first_name" />
</client>
Run Code Online (Sandbox Code Playgroud)

另一个项目涉及编写配置管理系统以整合我们的配置代码,强制每个配置点都有详细记录,允许超级用户在运行时更改配置值,并允许代码验证每个更改以避免获得无效组合配置值.

这些不同的变化确实使每个新客户的生活变得更加轻松,但大多数都未能解决我们问题的根源.真正让我们受益的变化是,当我们停止将我们的产品视为一系列修复工具以使某些东西能够为更多客户工作时,我们开始将我们的产品视为"产品".当客户要求新功能时,我们开始仔细考虑以下问题:

  • 现在还是将来,有多少其他客户能够使用此功能?
  • 是否可以以不使我们的代码易于管理的方式实现?
  • 我们能否实现一个与他们所要求的不同的功能,哪些仍然可以满足他们的需求,同时更适合其他客户重用?

在实现功能时,我们会采用长远的观点.我们可以创建一个允许任何客户端定义任意数量的自定义字段的全新表,而不是创建仅由一个客户端使用的新数据库字段.这需要更多的工作,但我们可以允许每个客户端以极大的灵活性定制自己的产品,而无需程序员更改任何代码.

也就是说,如果没有在复杂的规则引擎等方面付出巨大努力,有时会有一些你无法真正实现的定制.当你只需要为一个客户端以一种方式工作而另一种方式为另一个客户端工作时,我发现你最好的选择是编程接口利用依赖注入.如果您遵循"SOLID"原则来确保您的代码以模块化方式编写并具有良好的"关注点分离"等,那么为特定客户端更改代码的特定部分的实现并不是那么痛苦:

public FirstLastNameGenerator : INameDisplayGenerator
{
    IPersonRepository _personRepository;
    public FirstLastNameGenerator(IPersonRepository personRepository)
    {
        _personRepository = personRepository;
    }
    public string GenerateDisplayNameForPerson(int personId)
    {
        Person person = _personRepository.GetById(personId);
        return person.FirstName + " " + person.LastName;
    }
}

public AbcModule : NinjectModule
{
     public override void Load()
     {
         Rebind<INameDisplayGenerator>().To<FirstLastNameGenerator>();
     }
}
Run Code Online (Sandbox Code Playgroud)

我之前提到的其他技术增强了这种方法.例如,我没有写一个,AbcNameGenerator因为其他客户可能希望在他们的程序中有类似的行为.但是使用这种方法,您可以非常灵活和可扩展地轻松定义覆盖特定客户端默认设置的模块.

因为这样的系统本质上是脆弱的,所以重点关注自动化测试:单个类的单元测试,集成测试以确保(例如)您的注入绑定都正常工作,并进行系统测试以确保一切一起工作而不退步.

PS:我在整个故事中使用"我们",即使我在公司的大部分历史中都没有真正工作过.

PPS:请原谅C#和Java的混合体.

  • @Stephan Eggermont:是的,您会注意到随着产品的发展,我们最终放弃了许多`if`语句,转而支持XML /数据库配置或依赖注入.当然,虽然良好的OO实践经常避免"如果"过度使用,OO本身并不是"反对"(http://stackoverflow.com/questions/1167589/anti-if-campaign/1167628#1167628) (3认同)