在软件开发中,模型的设计对于程序的可维护性、可扩展性和性能都有重要影响。本文将探讨两种常见的模型设计方法:贫血模型(Anemic Model)和富数据模型(Rich Data Model),并分析它们在面向对象编程(OOP)中的应用和争议。
贫血模型,顾名思义,是指那些只包含数据而不包含业务逻辑的模型。例如,下面是一个简单的“客户”模型,它只包含了数据(属性),而没有包含业务逻辑(行为)。
class Customer
{
private string _CustomerName;
public string CustomerName
{
get { return _CustomerName; }
set { _CustomerName = value; }
}
private string _CustomerCode;
public string CustomerCode
{
get { return _CustomerCode; }
set { _CustomerCode = value; }
}
}
在现实世界中,如果没有业务逻辑,这种简单的模型是没有用的,除非正在创建数据传输对象(DTO),它们是用于不同目的的。一些开发者更愿意将这些行为和方法放入不同的类中,这些类通常使用服务、外观、管理器、实用工具等不同的术语命名。然后,这些简单的模型被传递给这些类,由它们来执行行为。
富数据模型在同一类中既包含数据也包含行为。以下是之前定义的贫血模型的富数据模型。可以看到数据(CustomerCode和CustomerName)和行为(Validate)都是同一个模型的一部分。
class Customer
{
private string _CustomerName;
public string CustomerName
{
get { return _CustomerName; }
set { _CustomerName = value; }
}
private string _CustomerCode;
public string CustomerCode
{
get { return _CustomerCode; }
set { _CustomerCode = value; }
}
public bool Validate()
{
if (CustomerCode.Length == 0)
{
return false;
}
if (CustomerName.Length == 0)
{
return false;
}
return true;
}
}
富数据模型将数据和行为封装在同一个类中,这符合面向对象编程的原则,即对象应该同时拥有数据和行为。
许多开发者将技术交叉关注点与贫血模型混淆。在贫血模型中,服务类中的行为是现实世界模型的行为。许多开发者创建实用程序或服务类,将技术行为与模型解耦,这不是贫血模型。例如,有人可以创建一个“Dal”类,如下所示,并将“Customer”对象传递给数据库插入。许多人将这种方法称为“仓库”模式。
class Dal
{
public void InsertDb(Customer obj)
{
// ADO.NET代码在这里
}
}
这种模型不是贫血模型。这是将技术行为与业务行为分离,这是完全有意义的。在前面的例子中,有“validate”方法,它实际上属于“Customer”模型。所以贫血模型是那些将业务行为分离到不同类中的模型,而不是技术行为。所以上面的“Dal”类不是贫血模型。
许多开发者无意中遵循了贫血模型,因为它简单,但面向对象编程社区对贫血模型有以下担忧:
尽管面向对象编程开发者的观点,贫血模型在IOC和DI架构的伪装下非常美丽和谐地存在。个人发现说它是一种反模式是非常残酷的。
考虑以下场景,有一个客户类,它有不同类型的折扣计算。所以,而不是将折扣计算逻辑放在客户类内部,可以从构造函数中注入对象,如下所示。如果对依赖注入不熟悉,请阅读这个DI IOC在C#中的介绍。
class Customer
{
private IDiscount _Discount = null;
public Customer(IDiscount _Dis)
{
_Discount = _Dis;
}
private string _CustomerName;
public string CustomerName
{
get { return _CustomerName; }
set { _CustomerName = value; }
}
public string Region { get; set; }
public double Amount { get; set; }
public double CalculateDiscount()
{
return _Discount.Calculate(this);
}
}
折扣计算逻辑在另一个类中,它接受客户对象来计算折扣。
public interface IDiscount
{
double Calculate(Customer Cust);
}
以下是两种折扣计算,一种是周末折扣,另一种是特别折扣。周末折扣根据地区和工作日进行计算,而特别折扣是30%的固定折扣。
public class WeekEndDiscount : IDiscount
{
public double Calculate(Customer Cust)
{
if (Cust.Region == "Asia")
{
if (DateTime.Today.DayOfWeek == DayOfWeek.Sunday)
{
return (Cust.Amount * 0.20);
}
}
return (Cust.Amount * 0.10);
}
}
public class SpecialDiscount : IDiscount
{
public double Calculate(Customer Cust)
{
return (Cust.Amount * 0.30);
}
}
这些类也遵循贫血模型,因为动作在不同的类中,但它仍然完美地解耦、有效和面向对象。
贫血数据模型是一个简单的纯模型类,只有数据,行为位于不同的类中。富数据模型既包含数据也包含行为。贫血模型因不符合面向对象、可扩展性、一致性和遵循过程化编程方法而受到批评。贫血模型不应与技术类如日志记录、仓库等交叉关注点混淆。DI IOC间接地迫使贫血模型。所以,在今天的世界上,贫血模型在DI IOC的伪装下非常存在。