我的ASP.NET开发中已经开始注意到一些反模式。这让我感到困扰,因为保持良好的设计感觉是正确的,但同时又闻起来不错。
问题在于:我们有一个多层应用程序,底层是一个处理对服务的调用的类,它为我们提供数据。上面是一层可以转换,操作和检查数据的类。上面是ASP.NET页面。
在许多情况下,服务层中的方法在进入视图之前不需要任何更改,因此模型只是直接传递,如:
public List<IData> GetData(int id, string filter, bool check)
{
return DataService.GetData(id, filter, check);
}
这没有错,也不一定非常糟糕,但它创造了一种奇怪的复制/粘贴依赖性。我也在研究底层服务,它也复制了这个模式,并且整个界面都有。所以会发生什么,“我需要将int someotherID
添加到GetData
”所以我将它添加到模型,服务调用者,服务本身和接口。 GetData
实际上代表了几个使用相同签名但返回不同信息的方法,这没有任何帮助。接口有点重复,但它仍然会在这里和那里出现。
这个反模式有名字吗?有没有修复,或者是对架构的重大改变唯一真正的方法?听起来我需要展平我的对象模型,但有时数据层正在进行转换,因此它具有价值。我也喜欢在“调用外部服务”和“提供页面数据”之间保持代码分开。
答案 0 :(得分:3)
我建议您使用query object pattern来解决此问题。基本上,您的服务可能具有以下签名:
IEnumerable<IData> GetData(IQuery<IData> query);
在IQuery接口中,你可以有一个方法,它将一个工作单元作为输入,例如一个事务上下文或类似ISession,如果你正在使用一个ORM,如NHibernate,并返回一个IData对象列表。
public interface IQuery<T>
{
IEnumerable<T> DoQuery(IUnitOfWork unitOfWork);
}
这样,您可以创建符合要求的强类型查询对象,并为您的服务提供干净的界面。艾恩德的This article能够很好地阅读这个主题。
答案 1 :(得分:1)
听起来像你需要另一个界面,所以这个方法就像:
public List<IData> GetData(IDataRequest request)
答案 2 :(得分:0)
你要委托给另一个层,这根本不一定是坏事。
你可以在这里或在另一种方法中添加一些其他逻辑,这些逻辑只属于这一层,或者换成将层委托给另一个实现,所以它当然可以很好地利用这些层问题。
你可能有太多的图层,但我不会仅仅因为看到这一点而这么说,更多是因为没有看到任何其他图层。
答案 3 :(得分:0)
根据您所描述的内容,您只是听说您在应用程序中遇到了抽象的“折衷”。
考虑这些“呼叫链”不再“传递”数据但需要进行某些转换的情况。现在可能不需要它,当然可以为YAGNI提供案例。
然而,在这种情况下,处理能够轻易地在层之间引入数据变化的积极副作用似乎并不需要太多技术债务。
答案 4 :(得分:0)
我也使用这种模式。但是我使用它来将我的域模型对象与数据对象解耦。
在我的例子中,我不是像你在你的例子中那样“穿过”来自数据层的对象,而是将它“映射”到另一个存在于我的域层中的对象。我使用AutoMapper来消除手动操作的痛苦。
在大多数情况下,我的域对象看起来与它源自的数据对象完全相同。但有时候我需要压缩来自我的数据对象的信息......或者我可能对我的数据对象中的所有内容都不感兴趣。我将数据对象映射到只保存字段的自定义域对象我的域名图层感兴趣。
此外还有副作用,当我决定重新考虑或更改我的数据层时,它不必影响我的域对象,因为它们是使用映射技术解耦的。
以下是auto-mapper的描述,这是我认为这个设计模式试图实现的:
AutoMapper面向模型投影场景,将复杂对象模型展平为DTO和其他简单对象,其设计更适合序列化,通信,消息传递,或仅仅是域和应用层之间的反腐败层
答案 5 :(得分:0)
实际上,你选择的方式,就是拥有你拥有的东西的原因(我并不是说它很糟糕)。
首先,我要说你的做法很正常。
现在,让我开始考虑你的图层:
为了不混淆,这就是我所说的特殊种类:
public UserEntity GetUserByID(int userEntityID);
在这个例子中,你需要完全传递Int,同时调用GetUserByID,它将完全返回UserEntity
对象。
现在是另一种方法:
还记得SqlDataReader
的工作原理吗?不是非常强烈的类型,对吧?
在我看来,你在这里所称的是你缺少一些非强类型的层。
要实现这一点:您需要在图层中的某个位置从强类型切换到非强类型。
Example
:
public Entity SelectByID(IEntityID id);
public Entity SelectAll();
所以,如果你有这样的东西而不是服务访问层,那么你可以为你想要的任何参数调用它。
但是,这几乎创造了你自己的ORM,所以我不认为这是最好的方式。
答案 6 :(得分:0)
必须确定哪种责任归于哪一层,并将此类逻辑仅放在其所属的层中。
如果您不必在特定方法中添加任何逻辑,那么通过它是绝对正常的。在某些时候你可能需要这样做,抽象层将在那时得到回报。
拥有并行层次结构更好,而不仅仅是传递底层图层的对象,因此每个层都使用它自己的类层次结构,并且您可以使用类似AutoMapper的东西,以防您觉得层次结构没有太大区别。这为您提供了灵活性,您可以随时使用特定方法/类中的自定义映射代码替换自动化,以防层次结构不再匹配。
如果许多方法具有几乎相同的签名,那么您应该考虑查询规范模式。
IData GetData(IQuery<IData> query)
然后,在表示层中,您可以为自定义查询规范对象实现数据处理器,其中单个aspnet处理程序可以实现特定查询对象的创建,并将它们传递给单个服务方法,该方法将其传递给单个存储库方法,可以根据特定的查询类调度,也可以使用访问者模式。
IQuery<IData> BindRequest(IHttpRequest request)
通过这种自动映射和查询规范模式,您可以将重复减少到最小。