4

这是“分析瘫痪”似乎已经站稳脚跟的场景之一,所以请指教!

该项目

一个相当简单的汽车产品列表,其中包括零件参考、适合的车辆等详细信息。

前端是一个 asp.net MVC 应用程序。

后端是 SQL,使用 Subsonic 将产品投影到域对象中。

功能性

我们的屏幕之一是产品详细信息屏幕。ASP.NET MVC 控制器调用产品存储库以检索产品详细信息,将这些详细信息(通过一些自动映射到 viewModel)返回到视图。

现在的杀手锏是我们有两个或三个频道进入网站,根据频道的不同,用户需要看到不同的零件编号。

例如,如果是零售渠道,则零件编号与数据库中的零件编号相同,但如果用户通过贸易渠道访问该站点,则零件参考的开头将替换为替代编号。

例如,如果通过交易频道查看,则 0900876 将变为 1700876。

我正在努力的地方是决定在哪里封装关于部件引用(以及其他可能改变的细节)的“通道规则”。

我已经考虑过这些替代方案。

将逻辑直接写入领域对象

Product类上,我们可以有一个方法/属性来获取翻译后的部分参考。

public string TranslatedPartRef()
    {
        if (this.Channel == "Trade")
        {
            return PartRef.Replace("0900", "1700");
        }
        else
        {
            return PartRef;
        }
    }      

在这种情况下,Product 实例必须知道通道,这对我来说似乎是错误的。

将逻辑封装在另一个对象中

我们可以编写一个类来处理这部分参考翻译,或者创建一个包含此逻辑的Channel类。

我不明白的是如何协调这两个班级。

如果控制器调用存储库来检索产品,那么它是否应该确定使用了哪个通道并翻译部件参考?如果是这样,我如何将带有翻译部分参考的产品发送回视图?

还值得注意的是,这部分参考也必须出现在搜索结果和其他场景中,因此我认为它需要整齐地包含在某个域中。

4

5 回答 5

2

我不是 C# 人,但我想我会用 Java 中的装饰器来解决这个问题。

假设您有一个产品接口,那么您可以创建一个装饰器来管理部件号问题。

class Product implements IProduct {
    public String getProductCode();
    // etc
}

class ProductChannelDecorator implements IProduct
{
    // constructor, like this in C#?
    public ProductChannelDecorator(IProduct product, Channel channel) { 
        this.product = product;
        this.channel = channel;
    }
    public String getProductCode() {
        switch (this.channel) {
            case Channel.RETAIL:
                return this.decorated.getProductCode();
            case Channel.TRADE:
                return retailToTradeTransformer(this.product.getProductCode());
            // etc
        }
    }
    // etc
}
于 2009-09-15T07:58:29.287 回答
2

您需要问自己的第一个问题是 Channel 的概念是否是域概念。您的问题似乎表明它不是,但另一方面,我认为这听起来也不是特定于应用程序的。

您可能会问的另一个问题是:如果将来我需要在此域模型之上构建另一个应用程序(例如 Web 服务或富客户端),我还需要处理 Channel 的概念吗?

我的猜测是答案可能是肯定的。

据我了解您的问题, Channel 在某种程度上与请求上下文相关。也许它真的是用户的一个属性。或者也许 in 是应用程序配置本身的一个属性。

无论如何,我会认真考虑它是否真的不是一个领域概念。如果是,那么它可以很好地属于域对象。

如果没有,ptomli 建议的装饰器实现听起来是一个好方法。

于 2009-09-15T08:09:53.083 回答
0

会有多少种不同的零件编号。如果只是 Trade v Retail,我很想在 Product 对象中简单地包含两个数字,并让 UI 决定显示哪个。当作用于产品时,身份可以是“类型{Trade, Retail}, number”。

对于灵活的模式,我认为这是您的频道想法很好。但如果它有双向职责,将零售与贸易联系起来,这似乎可行。Channel 对象可以被视为一个适配器,能够进行其他转换和扩充。

作为一种实现,我将为每个 Channel 创建一个单独的 Channel 对象,试图避免 case 语句和 if else 逻辑。对于零售,Channel 对象可以是 Trade 的 NOOP 对象,它可以进行映射。工厂可以创建 approporaye Channel 对象。

于 2009-09-15T08:01:28.017 回答
0

如果零件编号映射可能发生变化怎么办?现在它是一个会发生变化的前缀,但您是否需要满足其他类型的变化?也许你不需要这个,但是:

在业务层面,您是说产品可以有不同的部件号,这取决于渠道(这毕竟是一个基本的业务概念)。这表明在数据库级别,某处可能有一个 PartNumber 表,其中包含 ProductId、ChannelId 和 PartNumber 列。这肯定会涵盖随着时间推移出现更多渠道的情况(今天是零售或贸易,明天他们可能会添加 Web、邮购等。所有这些都可能需要不同的部件号)。

在对象级别,这映射到Product具有 a 的实例,该实例Dictionary<Channel, PartNumber>可用于在给定 a 的情况下获取适当的零件号Channel

于 2009-09-15T08:20:20.170 回答
0

现在的杀手锏是我们有两个或三个频道进入网站,根据频道的不同,用户需要看到不同的零件编号。

一个直接的解决方案:

public interface IChannel
    function GetNumber(Part as IPart) as String
end interface

没有装饰器,没有开关,没有控制反转。

每次您需要特定通道的零件号时,您都会调用此方法。

dim Channel as IChannel = ...
dim Part as IPart = ...
dim PartNumber = Channel.GetNumber(Part)

每次您需要不同的零件编号计算方法时,您只需实现此接口即可。

public class TradeChannel
    implements IChannel

    public function GetNumber(Part as IPart) as String implements IChannel.GetNumber
        return Part.Number.Replace("0900", "1700")
    end function
end class
于 2015-10-13T11:41:40.753 回答