ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Laravel的固体原理

2023-07-31 18:44:15  阅读:120  来源: 互联网

标签:Laravel  SOLID  switch


什么是SOLID原则?

SOLID原则是一套五项设计原则,旨在指导开发人员创建模块化、可维护和可扩展的软件系统。这些原则为编写干净、健壮和灵活的代码提供了指导方针。每项原则都侧重于软件设计的特定方面,并鼓励将关注点、灵活性和遵守良好的编码实践分开。通过遵循SOLID原则,开发人员可以构建更容易理解、测试和修改的软件,从而提高质量和长期可持续性。

SOLID原则的好处

  • 代码可维护性:SOLID原则促进干净和有组织的代码,使其更容易理解、修改和维护。
  • 代码可重用性:通过遵守SOLID原则,代码变得模块化和松散耦合,允许在应用程序的不同部分或未来项目中更容易重用。
  • 可测试性:SOLID原则鼓励易于单独测试的代码,从而实现更可靠和有效的单元测试。
  • 灵活性和适应性:遵循SOLID原则可以产生灵活的代码,并且可以轻松扩展或修改,以适应不断变化的需求或新功能。
  • 协作:SOLID原则使代码更容易理解和工作,促进团队成员之间更好的协作。
  • 可扩展性:SOLID原则通过创建松散耦合的模块化组件来帮助构建可扩展的系统,这些组件可以根据需要轻松向上或向下扩展。
  • 减少时间和成本:从项目一开始遵循SOLID原则,可以通过最大限度地减少错误、重构需求和在开发周期的后期进行更改来节省时间并降低成本。

通过解决这些领域,SOLID原则有助于整体软件质量、可维护性和开发人员生产力。


单一责任原则

单一责任原则(SRP)规定,一个类别应该只有一个改变的理由。这意味着一个班级应该只有一个责任或工作。

在Laravel应用程序的上下文中,让我们考虑一个场景,即我们有一个UserController类,该类可以处理与用户相关的操作,如创建新用户、更新用户信息和发送欢迎电子邮件。然而,这违反了SRP,因为该类有多重责任。

这里有一个违反SRP的例子

// UserController.php

class UserController
{
    public function create(Request $request)
    {
        // Validation and user creation logic

        $user = User::create($request->all());

        $this->sendWelcomeEmail($user); // Move this responsibility out of UserController
    }

    private function sendWelcomeEmail(User $user)
    {
        // Code to send the welcome email
    }
}

以下是遵循SRP的一个示例

// UserController.php

class UserController
{
    public function create(Request $request, EmailService $emailService)
    {
        // Validation and user creation logic

        $user = User::create($request->all());

        // Delegate the responsibility to the EmailService class
        $emailService->sendWelcomeEmail($user);
    }
}
// EmailService.php

class EmailService
{
    public function sendWelcomeEmail(User $user)
    {
        // Code to send the welcome email
    }

    public function sendEmailWithAttachment()
    {
        // Code to send email with attachment
    }
}

在重构的代码中,我们提取了将欢迎电子邮件发送到单独的EmailService类的责任。这区分了问题,允许UserController只专注于与用户相关的操作,而EmailService类则处理与电子邮件相关的任务。这使得其功能可以在其他需要邮件相关任务的控制器或服务上重用,而无需重复我们的代码。这符合SRP,因为每个类现在都有单一的责任,使代码更加模块化、可维护,并且将来更容易扩展或更改。


开放-封闭原则

开放-封闭原则(OCP)规定,软件实体(类、模块、功能等)应开放扩展,但关闭修改。简而言之,这意味着您应该能够在不修改现有代码的情况下向系统添加新功能。

让我们考虑Laravel应用程序中的一个例子,我们有一个处理不同付款方式的PaymentController:PayPal和Stripe。最初,控制器有一个switch语句来确定付款方式并执行相应的操作。

// PaymentController.php

class PaymentController
{
    public function processPayment(Request $request)
    {
        $paymentMethod = $request->input('payment_method');

        switch ($paymentMethod) {
            case 'paypal':
                $this->processPayPalPayment($request);
                break;
            case 'stripe':
                $this->processStripePayment($request);
                break;
            default:
                // Handle unsupported payment method
                break;
        }
    }

    private function processPayPalPayment(Request $request)
    {
        // Code for processing PayPal payment
    }

    private function processStripePayment(Request $request)
    {
        // Code for processing Stripe payment
    }
}

在上述代码中,添加新的付款方式需要通过在switch语句中添加另一个案例来修改PaymentController。这违反了OCP,因为我们正在修改现有代码,而不是扩展它。

为了遵守OCP,我们可以使用策略模式将支付处理逻辑与控制器解耦,并使其开放以供扩展。以下是更新的版本:

// PaymentController.php

class PaymentController
{
    private $paymentProcessor;

    public function __construct(PaymentProcessorInterface $paymentProcessor)
    {
        $this->paymentProcessor = $paymentProcessor;
    }

    public function processPayment(Request $request)
    {
        $this->paymentProcessor->processPayment($request);
    }
}
// PaymentProcessorInterface.php

interface PaymentProcessorInterface
{
    public function processPayment(Request $request);
}
// PayPalPaymentProcessor.php

class PayPalPaymentProcessor implements PaymentProcessorInterface
{
    public function processPayment(Request $request)
    {
        // Code for processing PayPal payment
    }
}
// StripePaymentProcessor.php

class StripePaymentProcessor implements PaymentProcessorInterface
{
    public function processPayment(Request $request)
    {
        // Code for processing Stripe payment
    }
}

现在,要根据用户输入动态选择支付处理器,您可以利用Laravel的容器和配置功能。这里有一个例子:

// config/payments.php

return [
    'default' => 'stripe',
    'processors' => [
        'paypal' => PayPalPaymentProcessor::class,
        'stripe' => StripePaymentProcessor::class,
    ],
];
// PaymentServiceProvider.php

use Illuminate\Support\Facades\App;
use Illuminate\Support\ServiceProvider;

class PaymentServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(PaymentProcessorInterface::class, function ($app) {
            $config = $app['config']->get('payments');
            $defaultProcessor = $config['default'];
            $processors = $config['processors'];

            $selectedProcessor = $request->input('payment_method', $defaultProcessor);
            $processorClass = $processors[$selectedProcessor];

            return $app->make($processorClass);
        });
    }
}

利斯科夫替代原则

Liskov替换原则(LSP)规定,超类的对象应该可以替换为其子类的对象,而不会影响程序的正确性。简而言之,这意味着子类应该能够与基类互换使用,而不会引起任何意外行为。

让我们考虑一个现实世界的例子,我们有一个名为车辆的基类和两个名为汽车和自行车的子类。它们中的每一个都有一个名为startEngine()的方法,它表示启动车辆的发动机。

以下是一个违反利斯科夫替代原则的例子:

class Vehicle {
    public function startEngine() {
        // Default implementation for starting the engine
        echo "Engine started!";
    }
}

class Car extends Vehicle {
    public function startEngine() {
        // Implementation specific to starting a car's engine
        echo "Car engine started!";
    }
}

class Bicycle extends Vehicle {
    public function startEngine() {
        // Bicycles don't have engines, so this violates LSP
        throw new Exception("Bicycles don't have engines!");
    }
}

在上述代码中,自行车类违反了Liskov替换原则,因为它在尝试启动发动机时会抛出异常。这种行为出乎意料,违反了原则。

以下是遵循Liskov替代原则的一个例子:

class Vehicle {
    // Common implementation for all vehicles
    public function startEngine() {
        // Default implementation for starting the engine
        echo "Engine started!";
    }
}

class Car extends Vehicle {
    public function startEngine() {
        // Implementation specific to starting a car's engine
        echo "Car engine started!";
    }
}

class Bicycle extends Vehicle {
    // Bicycles don't have engines, so we don't override the startEngine() method
}

在上述代码中,自行车类遵循Liskov替换原则,不覆盖startEngine()方法。由于自行车没有引擎,因此使用了基类的默认实现,这是可以接受的,并且不会引入意外行为。

通过遵循LSP,您可以确保您的代码更易于维护、更可扩展,并且更不容易出现错误,因为您可以在预期基类对象的地方安全地使用子类的对象。


接口隔离原则

接口隔离原则(ISP)规定,不应强迫客户端依赖他们不使用的接口。简而言之,这意味着不应强迫类实现它不需要的方法。

让我们考虑一个使用Laravel构建的在线商店应用程序的真实示例。

违反接口隔离原则:

interface PaymentGatewayInterface {
    public function processPayment($amount);
    public function refundPayment($transactionId);
    public function voidPayment($transactionId);
}

class PaymentGateway implements PaymentGatewayInterface {
    public function processPayment($amount) {
        // Process payment logic
    }

    public function refundPayment($transactionId) {
        // Refund payment logic
    }

    public function voidPayment($transactionId) {
        // Void payment logic
    }
}

class EcommerceService {
    private $paymentGateway;

    public function __construct(PaymentGatewayInterface $paymentGateway) {
        $this->paymentGateway = $paymentGateway;
    }

    public function processOrder($order) {
        // Process order logic

        $this->paymentGateway->processPayment($order->totalAmount);
    }

    public function refundOrder($order) {
        // Refund order logic

        $this->paymentGateway->refundPayment($order->transactionId);
    }

    public function voidOrder($order) {
        // Void order logic

        $this->paymentGateway->voidPayment($order->transactionId);
    }
}

在本例中,PaymentGatewayInterface定义了三种方法:processPayment()refundPayment(),voidPayment()然而,在EcommerceService类中,我们只需要使用processPayment()方法来处理与付款相关的操作。refundPayment()voidPayment()方法与EcommerceService无关,但我们仍然被迫依赖它们,因为接口强制执行它们的实现。

以下是遵循ISP的修改版本:

interface PaymentProcessorInterface {
    public function processPayment($amount);
}

interface RefundableInterface {
    public function refundPayment($transactionId);
}

interface VoidableInterface {
    public function voidPayment($transactionId);
}

class PaymentGateway implements PaymentProcessorInterface, RefundableInterface, VoidableInterface {
    public function processPayment($amount) {
        // Process payment logic
    }

    public function refundPayment($transactionId) {
        // Refund payment logic
    }

    public function voidPayment($transactionId) {
        // Void payment logic
    }
}

class EcommerceService {
    private $paymentProcessor;

    public function __construct(PaymentProcessorInterface $paymentProcessor) {
        $this->paymentProcessor = $paymentProcessor;
    }

    public function processOrder($order) {
        // Process order logic

        $this->paymentProcessor->processPayment($order->totalAmount);
    }
}

在这个更新的示例中,PaymentProcessorInterface仅定义了processPayment()方法,这是EcommerceService所需的唯一方法。RefundableInterfaceVoidableInterface是为需要这些特定功能的其他类创建的。通过分离接口,我们通过允许客户端仅依赖他们实际需要的接口来坚持ISP。

这种对ISP的遵守提高了代码库的可维护性,减少了不必要的依赖性,并使入门级开发人员更容易理解和处理代码。


依赖反转原则

依赖性反转原则(DIP)规定,高级模块不应依赖于低级模块,但两者都应依赖于抽象。换句话说,模块应该依赖接口或抽象类,而不是依赖于特定的实现。

使用存储立面在Laravel中违反DIP的代码示例:

class UserController extends Controller
{
    public function store(Request $request)
    {
        $avatar = $request->file('avatar');

        // Violation: The UserController depends directly on the Storage facade.
        // This makes it tightly coupled to the Laravel's file storage implementation.
        $path = Storage::disk('local')->put('avatars', $avatar);

        // ...
    }
}

UserController类依赖于存储门面,并直接调用其磁盘和put方法。通过直接依赖存储门面,UserController与Laravel实现的特定文件存储系统紧密耦合,因此在不修改UserController代码的情况下更难切换到不同的存储机制。

使用存储服务在Laravel中DIP之后的代码示例:

interface FileStorage
{
    public function storeFile($directory, $file);
}

class LocalFileStorage implements FileStorage
{
    public function storeFile($directory, $file)
    {
        return Storage::disk('local')->put($directory, $file);
    }
}

class S3FileStorage implements FileStorage
{
    public function storeFile($directory, $file)
    {
        return Storage::disk('s3')->put($directory, $file);
    }
}

class UserController extends Controller
{
    private $fileStorage;

    public function __construct(FileStorage $fileStorage)
    {
        $this->fileStorage = $fileStorage;
    }

    public function store(Request $request)
    {
        $avatar = $request->file('avatar');

        // The UserController depends on the FileStorage abstraction, which can be
        // implemented using different storage systems.
        $path = $this->fileStorage->storeFile('avatars', $avatar);

        // ...
    }
}

UserController类现在依赖于FileStorage接口,而不是直接依赖于存储门面或特定实现。此接口用作定义文件存储操作合同的抽象。

S3FileStorage类实现了FileStorage接口,并为在AWS S3中存储文件提供了具体实现。通过将FileStorage接口注入UserController构造函数,控制器与特定的存储机制解耦,仅依赖于抽象。
这种对依赖性反转原则的遵守使存储实现更容易互换。您可以轻松地引入FileStorage接口的新实现(例如,LocalFileStorage类),而无需修改UserController代码。存储机制的选择可以在运行时或通过配置(.env文件)确定,提供灵活性和可维护性。

标签:Laravel, SOLID, switch
来源:

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有