週末小挑戰:後端工程師的日期範圍生成器實作日

閱讀時間約 18 分鐘

在這個放鬆的週末裡,我和幾位同事決定抽空來點不一樣的活動,目標是為了提升我們後端開發的實戰能力。這次我們挑選的題目是「日期範圍生成器」,一個看似簡單但實則藏著不少挑戰的任務。作為團隊裡資歷稍深的一員,我負責引領這次的實作旅程。首先,我產出了一些基礎的程式碼,為這個項目打下了初步的框架。這部分還算是水到渠成,畢竟是些基本的設定,沒什麼大問題。


一開始初步構想php 日期列印動作 ,例如 04/01, 04/02, 04/03, 04/04.... 04/30 的列印格式,於是使用DateTimeDateInterval類。以下是一個示例代碼,展示如何列印出4月份的每一天,格式為 04/01, 04/02, ..., 04/30

<?php

// 設定起始日期為4月1日
$start = new DateTime('2024-04-01');
// 設定結束日期為4月30日
$end = new DateTime('2024-04-30');

// DateInterval設定為1天,P1D表示每次增加1天
$interval = new DateInterval('P1D');
// DatePeriod將會使用上述的起始日期、間隔和結束日期來創建一個範圍
$period = new DatePeriod($start, $interval, $end->modify('+1 day')); // 在結束日期加一天是為了包含4月30日

foreach ($period as $date) {
// 格式化日期並列印,格式為 04/01, 04/02, ..., 04/30
echo $date->format("m/d") . PHP_EOL;
}


隨著基礎結構完成,我們開始著手進行結構上的調整,這是為了讓整個程式碼更加模組化、更易於維護。不過,這個階段我們遇到了不少挑戰。為了提升程式碼的靈活性和重用性,我決定引入物件導向的概念。這對於一些同事們來說是個稍微陌生的領域,我們在實作過程中不斷地討論、嘗試,甚至是爭論。這個過程雖然充滿挑戰,但也讓團隊的合作更加緊密,大家的技能也得到了提升。

於是有了第二段的結構,這邊開始把功能分別置放。

<?php

class DatePrinter {
private $startDate;
private $endDate;

// 構造函數初始化起始和結束日期
public function __construct($start, $end) {
$this->startDate = new DateTime($start);
$this->endDate = new DateTime($end);
}

// 生成並列印日期範圍
public function printDates() {
$interval = new DateInterval('P1D'); // 設置間隔為1天
$end = $this->endDate->modify('+1 day'); // 包括結束日期
$period = new DatePeriod($this->startDate, $interval, $end);

foreach ($period as $date) {
echo $date->format("m/d") . PHP_EOL;
}
}
}

// 創建DatePrinter對象並列印4月份的日期
$datePrinter = new DatePrinter('2024-04-01', '2024-04-30');
$datePrinter->printDates();


我定義了一個名為 DatePrinter 的類別,目的是要處理跟列印日期範圍有關的功能。在這個類別裡,我設定了兩個私有屬性,$startDate 和 $endDate,用來存放起始日期和結束日期。

透過構造函數 __construct,當我建立 DatePrinter 類別的實例時,可以傳入起始和結束日期,並將這兩個日期轉換為 PHP 的 DateTime 物件,這樣方便後續的日期處理。

接著,我實作了一個名為 printDates 的方法。這個方法首先建立了一個每次增加一天的日期間隔 DateInterval 物件,並將結束日期擴展一天(這麼做是為了包含結束日期在內的日期範圍)。然後,我使用了 DatePeriod 來根據起始日期、間隔,以及修改過的結束日期來生成一個日期範圍。

最後,我使用 foreach 迴圈來遍歷這個日期範圍,並透過 echo 與 PHP_EOL (代表換行符號)來列印出每一天的日期,格式是月/日。

雖然這樣可以重複使用DatePrinter物件,但這樣還不夠好....,而且「日期範圍生成器」這題目,如果只是列印「日期」那好像也僅此而已。


在這情境下探討設計模式

當物件的結構逐步成型之後,我提出了進一步提升程式的可擴充性。我們想讓這個「日期範圍生成器」不僅僅適用於目前的需求,還能夠輕鬆地應對未來可能增加的新功能。然而,這一提議雖好,實施起來卻不是那麼容易。我們在實作中遇到了不少思維上的瓶頸,有時甚至覺得這個目標似乎有些過於理想化。但透過不斷地回顧程式碼、優化架構,甚至是重新設計某些模組,我們終於找到了解決方案。

這邊我先分兩個部分「資料參數」與「業務邏輯」

  • 資料參數:就是我們的「日期範圍」
  • 業務邏輯:就是我們的「列印」

所以這時候我們必須吧參數與邏輯先分開,要將列印細節從DatePrinter類中分離出來,我們可以考慮使用策略模式來實現。這樣,DatePrinter類專注於生成指定範圍內的日期,而將日期列印的責任委託給另一個類別來處理。這樣做不僅使DatePrinter類更加專注於它的主要職責,也提高了代碼的可重用性和可擴展性。

以下是根據這個思路重構後的代碼示例:

首先,我們定義一個列印策略的介面:

<?php

interface DatePrintStrategy {
public function print(array $dates);
}

接下來,實現一個簡單的列印策略,用於列印日期列表:

<?php

class SimpleDatePrint implements DatePrintStrategy {
public function print(array $dates) {
foreach ($dates as $date) {
echo $date->format("m/d") . PHP_EOL;
}
}
}


然後是重構後的DatePrinter類,它現在接收一個列印策略物件,並在生成日期後使用該策略進行列印:

<?php

/**
* Class DatePrinter
* @package Rewrite\Practice\DataRange
* @version 1.0
*/
class DatePrinter {
private $startDate;
private $endDate;
private $printStrategy;

// 在構造函數中接收一個列印策略對象
public function __construct($start, $end, DatePrintStrategy $printStrategy) {
$this->startDate = new DateTime($start);
$this->endDate = new DateTime($end);
$this->printStrategy = $printStrategy;
}

public function generateDates() {
$dates = [];
$interval = new DateInterval('P1D');
$end = $this->endDate->modify('+1 day');
$period = new DatePeriod($this->startDate, $interval, $end);

foreach ($period as $date) {
$dates[] = $date;
}

return $dates;
}

// 使用策略列印日期
public function printDates() {
$dates = $this->generateDates();
$this->printStrategy->print($dates);
}
}


使用端:

<?php

$printStrategy = new SimpleDatePrint();
$datePrinter = new DatePrinter('2024-03-01', '2024-03-31', $printStrategy);
$datePrinter->printDates();


到這裡也接近中午吃飯了,但大夥們這時似乎意猶未盡,而且由於思路上的改變,我們很快的就生出了第二版。


這時的物件也分離的很清楚:

DatePrinter類專注於執行列印動作


DateRange類別專注於生成指定範圍內的日期


DatePrintStrategy 介面決定執行的方法


SimpleDatePrint擁有DatePrintStrategy 介面的實際「列印」業務邏輯


好了~那我們就不多廢話,上code !!!

執行列印類別

<?php

namespace Rewrite\Practice\DataRange;

/**
* Class DatePrinter
* @package Rewrite\Practice\DataRange
* @version 2.0
*/
class DatePrinter
{
/** @var DateRange */
private $dateRange;

/** @var DatePrintStrategy */
private $printStrategy;

/**
* @param DateRange $dateRange
* @param DatePrintStrategy $printStrategy
*/
public function __construct(DateRange $dateRange, DatePrintStrategy $printStrategy) {
$this->dateRange = $dateRange;
$this->printStrategy = $printStrategy;
}

/**
* Print the date range
*/
public function print() {
$this->printStrategy->print($this->dateRange);
}
}


生成日期範圍類別

<?php

namespace Rewrite\Practice\DataRange;

/**
* Class DateRange
* @package Rewrite\Practice\DataRange
* @version 2.0
*/
class DateRange
{
/** @var \DateTime */
private $startDate;

/** @var \DateTime */
private $endDate;

/**
* DateRange constructor.
* @param $year
* @param $month
*/
public function __construct(int $year, int $month) {
// 確保月份始終是兩位數
$monthPadded = str_pad($month, 2, '0', STR_PAD_LEFT);

// 設置月份的第一天為起始日期
$start = "{$year}-{$monthPadded}-01";
$this->startDate = new \DateTime($start);

// 設置月份的最後一天為結束日期
$this->endDate = new \DateTime($start);
$this->endDate->modify('last day of this month');
}

/**
* 生成日期範圍
*
* @return array
*/
public function generateDates() {
$dates = [];
$interval = new \DateInterval('P1D');
$end = $this->endDate->modify('+1 day'); // 包含月份的最後一天
$period = new \DatePeriod($this->startDate, $interval, $end);

foreach ($period as $date) {
$dates[] = $date;
}

return $dates;
}
}


介面

<?php

namespace Rewrite\Practice\DataRange;

interface DatePrintStrategy {
public function print(DateRange $dateGenerator);
}


「基礎列印」業務邏輯

<?php

namespace Rewrite\Practice\DataRange\Method;

use Rewrite\Practice\DataRange\DatePrintStrategy;
use Rewrite\Practice\DataRange\DateRange;

class SimpleDatePrint implements DatePrintStrategy
{
/** @var string */
const DATE_FORMAT = "Y-m-d";

/** @var string */
private $format;

public function __construct($format = self::DATE_FORMAT)
{
$this->format = $format;
}

public function print(DateRange $dateGenerator)
{
$dates = $dateGenerator->generateDates();
foreach ($dates as $date) {
echo $date->format($this->format) . PHP_EOL;
}
}
}


raw-image


使用端

<?php

$datePrinter = new \Rewrite\Practice\DataRange\DatePrinter(
new \Rewrite\Practice\DataRange\DateRange(2024, 4),
new \Rewrite\Practice\DataRange\Method\SimpleDatePrint("Y/m/d")
);
$datePrinter->print();
raw-image


經過一番努力,我們成功地克服了所有的挑戰,「日期範圍生成器」不僅能夠滿足我們當下的需求,還具備了良好的擴充性,能夠迎接未來的挑戰。這次的實作不僅僅提升了團隊的技術能力,更重要的是增強了我們之間的合作與溝通。作為團隊中的一員,我深感欣慰,也期待著我們下一次的技術挑戰。

就準備帶著燒盡的腦袋吃下午茶....

我:嘿~各位,如果這時候需要你們開發一個「月曆」格式的列印功能,你們需要多久....啊~~~(無情的會議室門就這樣關上了...


raw-image


<= To Be Continued...

這是一系列以軟體開發為主題的輕鬆分享,內容涵蓋了技術選擇、開發經驗、實戰應用等多方面的議題。無論是如何在眾多框架中做出選擇,還是如何應對技術轉移的挑戰,作者都用幽默、有趣的對話風格,將複雜的技術問題轉化為易懂的故事。
留言0
查看全部
發表第一個留言支持創作者!
在程式世界裡,if 條件句是我們的好朋友,幫我們做各種決策。如果不注意可能會讓我們掉進小陷阱。文中透過幾個例子,在使用 if 時可能會遇到的一些常見問題,像是不必要的 if、過於複雜的條件、忘了用嚴格比較,還有嵌套太深的 if。透過這篇文章,你將學到如何避免這些小錯誤,寫出更乾淨、更有效率的程式碼。
在開發前後端分離架構時,使用兩個不同網域所遇到跨域請求問題。特別是在POST請求時行為差異大,揭示了「簡單請求」與「預檢請求」的關鍵差異。簡單請求不需預檢,但application/json會觸發預檢請求,需透過特定設定解決。分享這篇文章希望幫助開發者有效處理跨域問題。
在程式世界裡,if 條件句是我們的好朋友,幫我們做各種決策。如果不注意可能會讓我們掉進小陷阱。文中透過幾個例子,在使用 if 時可能會遇到的一些常見問題,像是不必要的 if、過於複雜的條件、忘了用嚴格比較,還有嵌套太深的 if。透過這篇文章,你將學到如何避免這些小錯誤,寫出更乾淨、更有效率的程式碼。
在開發前後端分離架構時,使用兩個不同網域所遇到跨域請求問題。特別是在POST請求時行為差異大,揭示了「簡單請求」與「預檢請求」的關鍵差異。簡單請求不需預檢,但application/json會觸發預檢請求,需透過特定設定解決。分享這篇文章希望幫助開發者有效處理跨域問題。
你可能也想看
Thumbnail
重點摘要: 1.9 月降息 2 碼、進一步暗示年內還有 50 bp 降息 2.SEP 上修失業率預期,但快速的降息速率將有助失業率觸頂 3.未來幾個月經濟數據將繼續轉弱,經濟復甦的時點或是 1Q25 季底附近
Thumbnail
近期的「貼文發佈流程 & 版型大更新」功能大家使用了嗎? 新版式整體視覺上「更加凸顯圖片」,為了搭配這次的更新,我們推出首次貼文策展 ❤️ 使用貼文功能並完成這次的指定任務,還有機會獲得富士即可拍,讓你的美好回憶都可以用即可拍珍藏!
Thumbnail
「每個人生命中,其實都需要這麼幾個可以打動你的作品。一個好作品,會是世界對你最極致的挽留。」 「《藝妓回憶錄》裡有這樣一幕,章子怡在竹林裡,跑著跑著,一瞬間長大了。那麼若是回頭走,大概也能變年輕吧。不過呢,我已經不用更年輕了,我擁有過了,媽媽說。」、「我已經不用年輕了,我擁有過了,不必回頭。」
Thumbnail
因工作變動而決定開始自營路線,透過同學的咖啡館開設週末小市集,提供主打商品琺瑯小碟、礦石項鍊及接金工類商品訂做,以及感謝同學大力支持的故事。
Thumbnail
週末帶了點兒去了秦皇島的阿那亞。 如果是以結論來說的話,整體的旅遊體驗不能夠算是太好。 主要的問題,一個是來自於我們報名的寵物旅遊團的安排有點凌亂,另外一個就是碰上景點的旅遊淡季,幾乎所有的商店、餐廳這些周邊設施都沒有營業,如果誇張一點形容的話,那就是去了一個鳥不生蛋的地方。 哈哈哈!
昨天的零食有「兩種喜餅」一種是「大餅」這是表哥的喜餅,口味是「芝麻外皮加上內餡是紅豆麻糬」比較不郝思就亂掰;另一種是不之誰的喜餅,是很多種的小餅,昨天吃到的是「苦巧克力醬填充在橢圓形餅乾上的挖洞中」也是田麗的滋味。此外還有上週說道的蚌殼餅以及常吃的蔓越莓乾。 除上述外,還有吃一種餅乾「Oreo
這周因為扁桃腺發炎導致大多時間在睡眠中渡過,但還是有吃點零食與飲料。 首先是吃了幾個小熊餅乾,「煉乳口味」吃的到煉乳縣,之後喝了「蔓越莓牛奶麥片,這是自製飲品」將「蔓越莓乾與配方奶粉、泉脈麥片攪勻在溫開水中」就喝得愜意又引下滿滿幸福,碗底不餵鯨魚得一乾二淨,之後又吃了小熊餅乾以及另外一種炳,等一
也許不是通通都在「小周末,周三」這一天吃,不過就一起來開箱了。 首先,昨天從冰箱一個盒子找到一種很像「牛奶高」的零食,他應該是「綠豆糕」我猜,把她泡入牛奶砸引,還真是令一番滋味。 今天半夜的時候,把蔓越莓乾「這大頓發賣場也有賣,不是只有『小班長的家有』」加入黑糖塊「來源於蒜頭糖廠的黑糖塊」加
週三有「小周末」支撐,於是來說說今天享用的上午茶時光零食與飲料。 首先介紹零食與飲料來自「小班長的家福利社」者,一個是蔓越莓乾「一個大夾鏈袋裡面有許多蔓越莓乾」這是健康的零嘴;另一個是「哈密瓜果汁條」這個兵入冷凍會變成「冰棍」如不把它冰冷凍庫,就是外觀像果凍條,用剪刀剪開卻是無果粒的哈密瓜果汁;這
Thumbnail
昨晚睡前,突然有個靈感,看著外面綿綿細雨,不如趁著假日,出門到有陽光的地方走走吧!不做規劃、不先買車票,全憑當下心情隨意決定。
Thumbnail
戴上耳機,點上會散發玫瑰香的蠟燭,再把房間的燈換到黃光頻道,整個世界就是我的了。 我在這個世界裡可以慢慢的悠閒、慢慢的跟自己玩,看看書、彈彈琴、斷捨離雜物,感到累就撲到床上小睡一點,週末就應該這樣,每個週末我都會約自己陪自己一下。 持續寫作。
Thumbnail
台北周末終於出太陽啦,暖暖假日午後,出發去個看感覺人少的僻靜小山丘,公館的寶藏巖聚落。 連日多雨陰天的放晴日,寶藏巖原來有在固定導覽,所以人其實也沒有很少….哈哈😅但是相對比小坡下面的汀洲路公館夜市區,已經完全是一個鬧中取靜的小世外桃源了。。。
Thumbnail
重點摘要: 1.9 月降息 2 碼、進一步暗示年內還有 50 bp 降息 2.SEP 上修失業率預期,但快速的降息速率將有助失業率觸頂 3.未來幾個月經濟數據將繼續轉弱,經濟復甦的時點或是 1Q25 季底附近
Thumbnail
近期的「貼文發佈流程 & 版型大更新」功能大家使用了嗎? 新版式整體視覺上「更加凸顯圖片」,為了搭配這次的更新,我們推出首次貼文策展 ❤️ 使用貼文功能並完成這次的指定任務,還有機會獲得富士即可拍,讓你的美好回憶都可以用即可拍珍藏!
Thumbnail
「每個人生命中,其實都需要這麼幾個可以打動你的作品。一個好作品,會是世界對你最極致的挽留。」 「《藝妓回憶錄》裡有這樣一幕,章子怡在竹林裡,跑著跑著,一瞬間長大了。那麼若是回頭走,大概也能變年輕吧。不過呢,我已經不用更年輕了,我擁有過了,媽媽說。」、「我已經不用年輕了,我擁有過了,不必回頭。」
Thumbnail
因工作變動而決定開始自營路線,透過同學的咖啡館開設週末小市集,提供主打商品琺瑯小碟、礦石項鍊及接金工類商品訂做,以及感謝同學大力支持的故事。
Thumbnail
週末帶了點兒去了秦皇島的阿那亞。 如果是以結論來說的話,整體的旅遊體驗不能夠算是太好。 主要的問題,一個是來自於我們報名的寵物旅遊團的安排有點凌亂,另外一個就是碰上景點的旅遊淡季,幾乎所有的商店、餐廳這些周邊設施都沒有營業,如果誇張一點形容的話,那就是去了一個鳥不生蛋的地方。 哈哈哈!
昨天的零食有「兩種喜餅」一種是「大餅」這是表哥的喜餅,口味是「芝麻外皮加上內餡是紅豆麻糬」比較不郝思就亂掰;另一種是不之誰的喜餅,是很多種的小餅,昨天吃到的是「苦巧克力醬填充在橢圓形餅乾上的挖洞中」也是田麗的滋味。此外還有上週說道的蚌殼餅以及常吃的蔓越莓乾。 除上述外,還有吃一種餅乾「Oreo
這周因為扁桃腺發炎導致大多時間在睡眠中渡過,但還是有吃點零食與飲料。 首先是吃了幾個小熊餅乾,「煉乳口味」吃的到煉乳縣,之後喝了「蔓越莓牛奶麥片,這是自製飲品」將「蔓越莓乾與配方奶粉、泉脈麥片攪勻在溫開水中」就喝得愜意又引下滿滿幸福,碗底不餵鯨魚得一乾二淨,之後又吃了小熊餅乾以及另外一種炳,等一
也許不是通通都在「小周末,周三」這一天吃,不過就一起來開箱了。 首先,昨天從冰箱一個盒子找到一種很像「牛奶高」的零食,他應該是「綠豆糕」我猜,把她泡入牛奶砸引,還真是令一番滋味。 今天半夜的時候,把蔓越莓乾「這大頓發賣場也有賣,不是只有『小班長的家有』」加入黑糖塊「來源於蒜頭糖廠的黑糖塊」加
週三有「小周末」支撐,於是來說說今天享用的上午茶時光零食與飲料。 首先介紹零食與飲料來自「小班長的家福利社」者,一個是蔓越莓乾「一個大夾鏈袋裡面有許多蔓越莓乾」這是健康的零嘴;另一個是「哈密瓜果汁條」這個兵入冷凍會變成「冰棍」如不把它冰冷凍庫,就是外觀像果凍條,用剪刀剪開卻是無果粒的哈密瓜果汁;這
Thumbnail
昨晚睡前,突然有個靈感,看著外面綿綿細雨,不如趁著假日,出門到有陽光的地方走走吧!不做規劃、不先買車票,全憑當下心情隨意決定。
Thumbnail
戴上耳機,點上會散發玫瑰香的蠟燭,再把房間的燈換到黃光頻道,整個世界就是我的了。 我在這個世界裡可以慢慢的悠閒、慢慢的跟自己玩,看看書、彈彈琴、斷捨離雜物,感到累就撲到床上小睡一點,週末就應該這樣,每個週末我都會約自己陪自己一下。 持續寫作。
Thumbnail
台北周末終於出太陽啦,暖暖假日午後,出發去個看感覺人少的僻靜小山丘,公館的寶藏巖聚落。 連日多雨陰天的放晴日,寶藏巖原來有在固定導覽,所以人其實也沒有很少….哈哈😅但是相對比小坡下面的汀洲路公館夜市區,已經完全是一個鬧中取靜的小世外桃源了。。。