Oop 构造函数中的业务逻辑是个好主意吗?

Oop 构造函数中的业务逻辑是个好主意吗?,oop,Oop,我目前正在工作中重建一个专门的票务系统(主要用于支持遥感硬件出现故障的人…)。无论如何,我想知道在对象的构造函数中执行大量工作流类型的活动是否是一个好主意 例如,目前存在以下情况: $ticket = new SupportTicket( $customer, $title, $start_ticket_now, $mail_customer ); 一旦创建了该对象,它将在数据库中放入一行,向客户发送确认电子邮件,可能会向最近的技术人员发送文本消息,等等 构造器

我目前正在工作中重建一个专门的票务系统(主要用于支持遥感硬件出现故障的人…)。无论如何,我想知道在对象的构造函数中执行大量工作流类型的活动是否是一个好主意

例如,目前存在以下情况:

$ticket = new SupportTicket(
    $customer,
    $title,
    $start_ticket_now,
    $mail_customer
);
一旦创建了该对象,它将在数据库中放入一行,向客户发送确认电子邮件,可能会向最近的技术人员发送文本消息,等等

构造器应该完成所有这些工作,还是更像下面这样

$ticket = new SupportTicket($customer, $title);
$customer->confirmTicketMailed($ticket);
$helpdesk->alertNewTicket($ticket);
如果有帮助,这些对象都基于ActiveRecord样式


我想这可能是一个意见问题,但你认为什么是最好的做法?

把事情分开。真的,你不想让一张票知道它应该如何发送电子邮件等。这是像class这样的服务的工作

[更新]我认为建议的工厂模式不适用于此。如果您希望创建不同的票证实现,而不将此逻辑放入票证本身(例如通过重载构造函数),则工厂非常有用

看看域驱动设计中提出的服务概念

服务:当操作在概念上不属于任何对象时。按照问题的自然轮廓,您可以在服务中实现这些操作。服务理念在理解上被称为“纯制造”


从OO设计的角度来看,问题不在于该功能是否应该在构造函数中实现(而不是在该类上的其他方法中实现),而是SupportTicket类是否应该知道如何完成所有这些事情

简而言之,SupportTicket类应该建模一个支持票证,并且只建模一个支持票证。创建电子邮件、知道如何将电子邮件发送给客户、将票据排队等待处理等都是您应该从SupportTicket类转移到别处封装的功能集。这样做的好处包括更低的耦合性、更高的内聚性和更好的可测试性


请看一看,这解释了这样做的好处。特别是,博客是阅读SRP和良好OO设计的其他关键原则的好地方。

如果构造函数完成了所有这些工作,那么构造函数就知道许多其他域对象。这就产生了依赖性问题。
票据
是否真的应该了解
客户
帮助热线
?当添加新功能时,是否有可能将新的域对象添加到工作流中,这难道不意味着我们可怜的
票证
将不得不了解域对象数量的不断增加

像这样的依赖蜘蛛网的问题是,对任何一个域对象的源代码更改都会对我们可怜的
票据产生影响。
ticket
对系统的了解非常多,因此无论发生什么情况,
ticket
都将参与其中。您将发现讨厌的
if
语句聚集在该构造函数中,检查配置数据库和会话状态,以及其他许多事情。
ticket
将成长为神级

我不喜欢做事情的构造函数的另一个原因是它使它们周围的对象很难测试。我喜欢写很多模拟对象。当我针对
客户编写测试时
我想通过模拟的
票据
。如果
ticket
的构造函数控制工作流,以及
customer
和其他域对象之间的舞蹈,那么我不太可能模拟它来测试
customer


我建议您阅读几年前我写的一篇关于管理面向对象设计中的依赖关系的论文

简单的答案是否定的

在硬件设计中,我们曾经有一句谚语,“不要在时钟或重置线上放置门-它会模糊逻辑。”出于同样的原因,同样的道理也适用于这里


较长的答案需要等待,但请参见“”。

事实上,我从未对任何可用的答案感到满意,但让我们看看它们。选择围绕两个评估问题:

E1.业务逻辑的知识属于哪里

E2.下一个代码读取器将在哪里查找?()

一些选择:

  • 在客户机代码中(执行“new SupportTicket”的对象)。显然,它可能不知道/不应该知道业务逻辑,否则您就不想创建那个奇特的构造函数。如果它是业务逻辑的正确位置,那么它应该说:

    $ticket = new SupportTicket($customer, $title);
    
    handleNewSupportTicket($ticket, ...other parameters...)
    
    其中,为了保护E2,“handlenewSupportTicket”是定义业务逻辑的地方(下一个程序员可以很容易地找到它)

  • 支持票证对象中,作为单独的业务调用。就我个人而言,我对此并不满意,因为这是来自客户端代码的两个调用,其中思维是一回事

    $ticket = new SupportTicket($customer, $title);
    
    $ticket -> handleNewSupportTicket(...other parameters...)
    
  • 在支持票证的类中。在这里,业务逻辑应该驻留在Support Ticket区域,但由于新的Support Ticket绝对需要立即处理,而不是稍后处理,因此这项重要任务不能留给任何人想象或键入错误,也就是说,具体来说,不能留给客户机代码。我只知道如何在中编写类方法,但我将尝试使用伪代码:

    $ticket = SupportTicket.createAndHandleNewSupportTicket(...other parameters...)
    
    假设客户端代码出于其他目的需要新票证的句柄(否则“$ticket=”将消失)

    我不太喜欢这个,因为其他程序员觉得在类或静态方法中寻找业务逻辑并不那么自然。但这是第三种选择。

    $ticket = SupportTicketBusinessLogic.createAndHandleNewSupportTicket(...other params...)