我想问您有关Laravel中的SOLID原则,特别是关于接口隔离原则的问题。
假设我们有一个应用程序,用户可以在其中发送订单。但是对于每个用户,我们都有不同的发送订单的方式。例如,用户一-通过电子邮件发送订单。用户二-通过xml发送的命令上传到ftp。在php中,我们可以创建界面:
<?php
interface ISendOrder
{
public function sendOrder();
}
然后为每个特定用户实施它。但是在Laravel中,我们只有一个类User。假设用户具有用户类型的属性,从中可以找到sendOrder类型。在Laravel中执行此操作的最佳方法是什么?也许是一个开关/如果找到并返回正确的实现,或者有更好的方法?
答案 0 :(得分:4)
这对于Strategy pattern来说是一个很好的用例。在您的示例中,用户可以通过多种方式发送订单。换句话说,多种策略。现在也是考虑将您的订单逻辑添加到Service Layer的好时机。
诚然,您的问题有点含糊,但是我将尝试举例说明如何完成此任务。因此,假设您的App\User
模型将实现您的接口ISendOrder
。
use App/Services/Order/OrderService;
use App/Services/Order/ISendOrder;
class User extends Authenticatable implements ISendOrder
{
protected $orderStrategies = [
'standard' => App/Services/Order/Strategies/StandardStrategy::class,
'vip' => App/Services/Order/Strategies/VipStrategy::class
];
public function sendOrder(Order $order, OrderService $service)
{
$service->setStrategy($this->orderStrategies[$this->type]);
return $service->sendOrder($this, $order);
}
}
您的OrderService
的一个非常简单的示例如下:
class OrderService
{
protected $strategy;
public function setStrategy(OrderStrategy $strategy)
{
$this->strategy = $strategy;
}
public function sendOrder(ISendOrder $user, Order $order)
{
// logic to send order
// $this->strategy holds the strategy to be used
}
}
此步骤纯粹是可选的,但我可能会将订单发送逻辑封装为特征。您的模型看起来会干净很多。另外,您可以轻松地将此逻辑添加到将来可能发送订单的任何其他models
上。
class User extends Authenticatable implements ISendOrder
{
use InteractsWithOrder;
}
您的特质看起来像这样:
trait InteractsWithOrder
{
protected $orderStrategies = [
'standard' => App/Services/Order/Strategies/StandardStrategy::class,
'vip' => App/Services/Order/Strategies/VipStrategy::class
];
public function sendOrder(Order $order, OrderService $service)
{
$strategy = new $this->orderStrategies[$this->type];
$service->setStrategy($strategy);
return $service->sendOrder($this, $order);
}
}
这是一个非常简单的示例,用于说明如何可能完成此操作。
答案 1 :(得分:1)
请注意诸如“什么是最好的方法……”之类的主观问题,该站点实际上是针对特定问题的。但是关于您的问题,有些事情可能会有所帮助。
S 单一责任:一堂课只有一个改变的理由。如果遇到多个,请将其重构为单独的类。即,不要在同一类中融合“用户”和“消息驱动程序”的概念。
O 笔关闭:应该打开一个类以扩展或覆盖子类,但不能修改一个类本身中现有方法的输入或输出。即,不要从可能已经在使用该类的东西中抽出来。功能测试是确保重构不会意外执行的好方法。
L iskov替代:同级类应可互换。您谈到拥有不同类型的传输驱动程序。电子邮件与FTP。您的EmailDriver和FtpDriver类都应实现一个定义send()
方法的接口。因此,任何使用一个的人也应该能够应付另一个。您的Adapter类既消耗了要与之通信的User
和定义了通信机制的Driver
,则不必关心具体细节。
我界面隔离:简而言之,请保持界面苗条。如果一个类愿意,它总是可以实现多个。如果单个Interface要求对象提供太多支持,而实际需要的支持可能更多,则可能导致不必要的复杂性。
D 倾向倒置:与Liskov Substitution紧密相关。高级类应该依赖于Interfaces而不是Concrete类,以便在需要时以最少的重构就更容易交换出组件。
Laravel的Service Container完成了很多工作。 Service Providers将具体类绑定到抽象。通常,这些抽象是字符串接口名称,但实际上它们可以是任何字符串。 Facades的工作方式部分是这样。
Laravel实例化新对象的方式应始终使用Dependency Injection。通过Type Hinting(如果适用)或使用app()
Helper Function。
当您要求Laravel以这种方式实例化对象时,您正在做的事情就是使Container有机会与Provider中定义的类交换出您要的类。
不过,回到您的特定问题上,当涉及到指示您的代码向哪个传输驱动程序供谁使用时,如果没有更多信息,我不能肯定地说,这只是一种意见,但是您可能想看看是否Factory Pattern适合您的情况。
答案 2 :(得分:0)
我写这篇文章是希望它能清楚地阐明某人关于接口隔离的概念。
我认为,@mozammil 为您提供了 Laravel 特定案例的最佳答案。
但是如果您对这种接口隔离有清晰的概念,那么在任何编程案例中都可以使用它一样简单 - Java
、PHP
、C#
、Python
或使用任何编程语言。
接口隔离:
客户/用户不应依赖于他们不能使用的接口,而应将接口分离为几个。
喜欢如果我们这样做,请检查一切是否正确。
interface Payment{
public function initiatePayments();
public function getPayment();
}
public class BankPayment implements Payment{
public function initiatePayments(){
// Do initialization
}
public function getPayment(){
// Make Payments Stuff
}
}
BankPayment
类使用两个与其相关且必要的函数。但是,如果有新的东西到来。喜欢需要添加贷款计算和检查。
interface Payment{
public function initiatePayments();
public function getPayment();
public function getLoanHistory();
public function printLoanPayments();
}
public class LoanPayment implements Payment{
public function initiatePayments(){
// Do initialization
}
public function getPayment(){
// Make Payments Stuff
}
// Ok for this class
public function getLoanHistory(){
}
// Ok for this class
public function printLoanPayments(){
}
}
public class BankPayment implements Payment{
public function initiatePayments(){
// Do initialization
}
public function getPayment(){
// Make Payments Stuff
}
// Not Necessary for this class but have to implement
public function getLoanHistory(){
}
// Not Necessary this class but have to implement
public function printLoanPayments(){
}
}
看看这个 LoanPayment
类,一切正常,但对于 BankPayment
类,新的两个函数是不必要的,应该在另一个接口中隔离。
这就是 SOLID 中的接口隔离原则。
要获得有关 SOLID 原则的完整概念和实际实现,您可以查看此内容 - https://devsenv.com/tutorials/solid-principle-in-depth-with-practical-example