Laravel 面向接口編程(實(shí)踐)

Laravel 面向接口編程(實(shí)踐)

面向接口編程是編碼中的一種設(shè)計(jì)思想,這種方式基于接口而不是固定的類來(lái)構(gòu)建應(yīng)用程序。

如果您是一名程序員,那么您可能聽說過則這樣的說法,例如:面向接口編程、使用抽象類代替固定類等等。

這些都是說的同一件事,編寫應(yīng)用程序代碼時(shí),使其依賴抽象接口而不是具體的類。

為什么?

這是我第一次聽到這句話時(shí)的確切反應(yīng)。為什么要使用接口而不是類?即使創(chuàng)建了接口,我也需要?jiǎng)?chuàng)建一個(gè)實(shí)現(xiàn)該接口的類。這不是浪費(fèi)時(shí)間嗎?

當(dāng)然不是!!

這個(gè)世界上唯一不變的就是變化本身,也就是說,變化是永恒的。

就編程而言,這同樣沒有例外。業(yè)務(wù)需求隨著時(shí)間變化,我們的代碼也要隨之變化。

所以代碼必需保持靈活。

面向接口編程可以使代碼松散耦合且靈活。

怎么做?

觀察下面的代碼。

class?Logger?{ ????public?function?log($content)? ????{ ????????//日志保存到文件中. ????????echo?"Log?to?file"; ????} }

這是一個(gè)將日志記錄到文件的簡(jiǎn)單類。 我們可以在控制器中調(diào)用它。

class?LogController?extends?Controller { ????public?function?log() ????{ ????????$logger?=?new?Logger; ????????$logger->log('Log?this'); ????} }

但如果需要將日志記錄到多個(gè)地方 (如數(shù)據(jù)庫(kù),文件,云端等) 時(shí),我們又該怎么辦呢。

然后我們可以更改 LogController 和 Logger 類以適應(yīng)這些更改。

class?Logger?{ ????public?function?logToDb($content)? ????{ ????????//將日志記錄到?db. ????} ????public?function?logToFile($content)? ????{ ????????//將日志保存到?file. ????} ????public?function?logToCloud($content)? ????{ ????????//將日志存儲(chǔ)到?cloud. ????} }
class?LogController?extends?Controller { ????public?function?log() ????{ ????????$logger?=?new?Logger; ????????$target?=?config('log.target'); ????????if?($target?==?'db')?{ ????????????$logger->logToDb($content); ????????}?elseif?($target?==?'file')?{ ????????????$logger->logToFile($content); ????????}?else?{ ????????????$logger->logToCloud($content); ????????} ????} }

現(xiàn)在我們可以記錄不同的目標(biāo)了。但是,如果我們想將其他目標(biāo) (例如日志) 添加到 redis 服務(wù)器,該怎么辦?最后,我們將同時(shí)修改 Logger 類和 LogController 類。

如您所見,這很快就擺脫了我們的控制,并且代碼變得混亂。Logger 類很快成為一個(gè)整體。這是一場(chǎng)噩夢(mèng)。

因此,我們需要拆分事物。遵循 SOLID 原則,我們可以將職責(zé)移至相應(yīng)的類。

class?DBLogger { ????public?function?log() ????{ ????????//將日志記錄到?db ????} } class?FileLogger { ????public?function?log() ????{ ????????//將日志保存到?file ????} } class?CloudLogger { ????public?function?log() ????{ ????????//將日志存儲(chǔ)到?cloud ????} }

并且控制器更改為:

class?LogController?extends?Controller { ????public?function?log() ????{ ????????$target?=?config('log.target'); ????????if?($target?==?'db')?{ ????????????(new?DBLogger)->log($content); ????????}?elseif?($target?==?'file')?{ ????????????(new?FileLogger)->log($content); ????????}?else?{ ????????????(new?CloudLogger)->log($content); ????????} ????} }

這樣就好多了。現(xiàn)在如果要添加其他日志記錄目標(biāo),我們可以創(chuàng)建一個(gè)新類并將其添加到 Controller 中的 if-else。

但是,我們的控制器仍然負(fù)責(zé)選擇記錄器。對(duì)于控制器,不需要知道不同的記錄器并在它們之間進(jìn)行選擇。它只需要一個(gè)帶有 log() 方法的記錄器類來(lái)記錄內(nèi)容。

使用接口

這種情況就適合使用接口。那么什么是接口?

接口是對(duì)對(duì)象可以執(zhí)行的操作的描述。

以我們的示例為例,控制器僅需要帶有 log() 方法的記錄器類。因此,我們的接口必須描述它必須具有 log() 方法。

interface?Logger { ????public?function?log($content); }

如您所見,它僅包含函數(shù)聲明,而不包含其實(shí)現(xiàn),這就是為什么將其稱為抽象的原因。

實(shí)現(xiàn)接口時(shí),實(shí)現(xiàn)接口的類必須提供接口中定義的抽象方法的實(shí)現(xiàn)細(xì)節(jié)。

在我們的示例中,任何實(shí)現(xiàn) Logger 接口的類都必須提供抽象方法 log () 的實(shí)現(xiàn)細(xì)節(jié)。

然后,我們可以在控制器中注入此接口。

class?LogController?extends?Controller { ????public?function?log(Logger?$logger) ????{ ????????$logger->log($content); ????} }

現(xiàn)在,控制器不再關(guān)心傳遞給它的記錄器類型。它需要知道的是它必須實(shí)現(xiàn) Logger 接口。

因此,我們需要修改 Logger 類以實(shí)現(xiàn)此接口。

class?DBLogger?implements?Logger { ????public?function?log() ????{ ????????//將日志記錄到?db ????} } class?FileLogger?implements?Logger { ????public?function?log() ????{ ????????//將日志存儲(chǔ)到?file ????} } class?CloudLogger?implements?Logger { ????public?function?log() ????{ ????????//將日志保存到?cloud ????} }

現(xiàn)在,我們可以添加更多記錄器,而無(wú)需觸及現(xiàn)有代碼。我們要做的就是創(chuàng)建一個(gè)實(shí)現(xiàn) Logger 接口的新類。

class?RedisLogger?implements?Logger { ????public?function?log() ????{ ????????//將日志存儲(chǔ)到?redis ????} }

我們的代碼現(xiàn)在看起來(lái)就變得靈活,低耦合了,我們可以隨時(shí)改變實(shí)現(xiàn)方式而不用去改動(dòng)之前的代碼。

依賴注入

當(dāng)我們使用的是 laravel 框架,我們可以使用服務(wù)容器去自動(dòng)注冊(cè)接口的實(shí)現(xiàn)。

因?yàn)?Laravel 提供開箱即用的方法注入,所以我們只需要把接口和實(shí)現(xiàn)綁定起來(lái)。

首先我們需要?jiǎng)?chuàng)建一個(gè) logger 的配置文件。 就像這樣

<?php return [     &#39;default&#39; =>?env('LOG_TARGET',?'file'), ????'file'?=&gt;?[ ????????'class'?=&gt;?AppLogFileLogger::class, ????], ????'db'?=&gt;?[ ????????'class'?=&gt;?AppLogDBLogger::class, ????], ????'redis'?=&gt;?[ ????????'class'?=&gt;?AppLogRedisLogger::class, ????] ];

然后在 app/Providers 路徑下 AppServiceProvider.php 的文件添加如下代碼

class?AppServiceProvider?extends?ServiceProvider { ????public?function?register() ????{ ????????$default?=?config('log.default'); ????????$logger?=?config("log.{$default}.class"); ????????$this-&gt;app-&gt;bind( ????????????AppContractsLogger::class,?//?the?logger?interface ????????????$logger ????????); ????} }

這樣做的效果是,從 logger.php 配置文件讀取默認(rèn)的 logger ,然后綁定到 Logger interface 。這樣當(dāng)我們使用 Logger interface ,容器將會(huì)幫我們解析并返回默認(rèn)的 Logger 實(shí)例。

因?yàn)槟J(rèn)的 logger 是使用 env() 助手指定的,所以我們可以在不同的環(huán)境使用不同的 logger ,例如本地環(huán)境使用 file ,生產(chǎn)環(huán)境使用 db 。

總結(jié)

使用接口可以讓我們寫出低耦合的代碼,提供一個(gè)抽象層。它允許我們隨時(shí)改變實(shí)現(xiàn)方式。所以,盡可能將你的應(yīng)用中可變的部分用面向接口的方式實(shí)現(xiàn)。

推薦教程:《PHP教程

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊8 分享