dev*_*yst 16 c# oop design-patterns
最近我们讨论了类中的数据和行为分离.通过将域模型及其行为放入单独的类中来实现数据和行为分离的概念.
但是,我不相信这种方法的假定好处.虽然它可能是由一个"伟大的"创造的(我认为它是Martin Fowler,虽然我不确定).我在这里举一个简单的例子.假设我有一个Person类,其中包含Person及其方法(行为)的数据.
class Person
{
string Name;
DateTime BirthDate;
//constructor
Person(string Name, DateTime BirthDate)
{
this.Name = Name;
this.BirthDate = BirthDate;
}
int GetAge()
{
return Today - BirthDate; //for illustration only
}
}
Run Code Online (Sandbox Code Playgroud)
现在,将行为和数据分离到单独的类中.
class Person
{
string Name;
DateTime BirthDate;
//constructor
Person(string Name, DateTime BirthDate)
{
this.Name = Name;
this.BirthDate = BirthDate;
}
}
class PersonService
{
Person personObject;
//constructor
PersonService(string Name, DateTime BirthDate)
{
this.personObject = new Person(Name, BirthDate);
}
//overloaded constructor
PersonService(Person personObject)
{
this.personObject = personObject;
}
int GetAge()
{
return personObject.Today - personObject.BirthDate; //for illustration only
}
}
Run Code Online (Sandbox Code Playgroud)
这应该是有益的并且提高灵活性并提供松耦合.我不知道怎么样.据我所知,这引入了额外的编码和性能损失,每次我们必须初始化两个类对象.我在扩展此代码时看到了更多问题.考虑一下我们在上面的例子中引入继承时会发生什么.我们必须继承这两个类
class Employee: Person
{
Double Salary;
Employee(string Name, DateTime BirthDate, Double Salary): base(Name, BirthDate)
{
this.Salary = Salary;
}
}
class EmployeeService: PersonService
{
Employee employeeObject;
//constructor
EmployeeService(string Name, DateTime BirthDate, Double Salary)
{
this.employeeObject = new Employee(Name, BirthDate, Salary);
}
//overloaded constructor
EmployeeService(Employee employeeObject)
{
this.employeeObject = employeeObject;
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,即使我们在单独的类中分离出行为,我们仍然需要Data类的对象来处理Behavior类方法.所以最后我们的Behavior类包含数据和行为,尽管我们有模型对象形式的数据.
您可能会说可以添加一些接口,因此我们可以使用IPersonService和IEmployeeService.但我认为为每个类引入接口并从接口引入接口似乎不太好.
那么你能告诉我,通过分离上述案例中的数据和行为,我得到了什么,而这些数据和行为是通过将它们放在同一个类中而无法实现的?
Dan*_*Eli 11
我同意,你实施的分离很麻烦.但还有其他选择.那个有方法getAge(person p)的ageCalculator对象怎么样?或者person.getAge(IAgeCalculator calc).或者更好的是calc.getAge(IAgeble a)
分离这些问题可以带来一些好处.假设你打算让你的实施回归多年,如果一个人/宝宝只有3个月大了怎么办?你回0吗?0.25?抛出异常?如果我想要一只狗的年龄怎么办?几十年或几小时的年龄?如果我想要某个日期的年龄怎么办?如果这个人死了怎么办?如果我想在一年中使用火星轨道怎么办?还是希伯来语的calander?
这些都不会影响使用person界面但不使用birthdate或age的类.通过将年龄计算与其消耗的数据分离,您可以获得更高的灵活性并增加重用的可能性.(甚至可以计算奶酪的年龄和相同代码的人!)
通常情况下,最佳设计会因环境而异.然而,这种情况很少见,表现会影响我在这类问题上的决定.系统的其他部分可能会有几个数量级的因素,例如浏览器和服务器之间的光速或数据库检索或序列化.与理论性能问题相比,时间/美元更倾向于重构简单性和可维护性.为此,我发现分离数据和域模型的行为是有帮助的.毕竟,它们是分开的问题,不是吗?
即使有这样的优先事项,事情也会混乱.现在,想要人年龄的类有另一个依赖,即calc类.理想情况下,需要较少的类依赖性.另外,谁负责实例化calc?我们注射吗?创建一个calcFactory?或者它应该是静态方法?该决定如何影响可测试性?简化的驱动力实际上增加了复杂性吗?
OO关于将行为与数据相结合的实例与单一责任原则之间似乎存在脱节.当所有其他方法都失败时,请双向写下,然后问同事,"哪一个更简单?"
小智 5
我意识到我回复这个问题晚了大约一年,但无论如何......哈哈
我之前已经将行为分开,但不是按照您所展示的方式。
当您的行为应该具有通用接口但允许不同对象进行不同(唯一)实现时,分离行为才有意义。
例如,如果我正在制作游戏,则对象可用的一些行为可能是行走、飞行、跳跃等的能力。
通过定义 IWalkable、IFlyable 和 IJumpable 等接口,然后基于这些接口创建具体的类,它为您提供了极大的灵活性和代码重用性。
对于 IWalkable,您可能有...
无法行走:IWalkableBehavior
受限行走:IWalkableBehavior
无限行走:IWalkableBehavior
IFlyableBehavior 和 IJumpableBehavior 的模式类似。
这些具体类将实现 CannotWalk、LimitedWalking 和 UnlimitedWalking 的行为。
在对象(例如敌人)的具体类中,您将拥有这些行为的本地实例。例如:
IWalkableBehavior _walking = new CannotWalk();
其他人可能会使用 new LimitedWalking() 或 new UnlimitedWalking();
当需要处理敌人的行为时,假设 AI 发现玩家在敌人的一定范围内(这也可能是一种行为,比如 IReactsToPlayerProximity),那么它可能会自然地尝试将敌人移近“与“敌人交战”。
所需要的只是调用 _walking.Walk(int xdist) 方法,它将自动排序。如果对象使用 CannotWalk,则不会发生任何事情,因为 Walk() 方法将被定义为简单返回且不执行任何操作。如果使用“有限行走”,敌人可能会向玩家移动很短的距离,如果使用“无限行走”,敌人可能会直接向玩家移动。
我可能没有解释得很清楚,但基本上我的意思是从相反的角度来看待它。不是将对象(此处称为数据)封装到行为类中,而是使用接口将行为封装到对象中,这为您提供了“松散耦合”,允许您细化行为并轻松扩展每个“行为基础” (行走、飞行、跳跃等)使用新的实现,但你的对象本身没有任何区别。他们只是有“行走”行为,即使该行为被定义为“不能行走”。