diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml index bb0ed7e116ae..800441be73fb 100644 --- a/.github/workflows/presubmit.yml +++ b/.github/workflows/presubmit.yml @@ -21,8 +21,8 @@ jobs: echo "Error: LLM_API_KEY secret is not configured" exit 1 fi - - uses: presubmit/ai-reviewer@latest + - uses: presubmit/ai-reviewer@v0.2.5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} LLM_API_KEY: ${{ secrets.LLM_API_KEY }} - LLM_MODEL: "gpt-5-nano" \ No newline at end of file + # LLM_MODEL removed to avoid model-specific schema/validation issues diff --git a/localization/zh-TW/abstract-document/README.md b/localization/zh-TW/abstract-document/README.md index f71ff845a97f..33db74114f5e 100644 --- a/localization/zh-TW/abstract-document/README.md +++ b/localization/zh-TW/abstract-document/README.md @@ -13,11 +13,11 @@ tag: ## 解釋 -抽象文件模式 (Abstract Document Pattern) 使您能夠處理其他非靜態屬性。此模式使用特性 (trait) 的概念來實現型別安全,並將不同類別的屬性分離為一組介面。 +抽象文件模式 (Abstract Document Pattern) 使你能夠處理其他非靜態屬性。此模式使用特性 (trait) 的概念來實現型別安全,並將不同類別的屬性分離為一組介面。 真實世界範例 -> 考慮由多個部分組成的汽車。但是,我們不知道特定汽車是否真的擁有所有零件,或者僅僅是零件中的一部分。我們的汽車是動態而且非常靈活的。 +> 考慮由多個部分組成的汽車。但,我們不知道特定汽車是否真的擁有所有零件,或者僅僅是零件中的一部分。我們的汽車是動態而且非常靈活的。 簡單的說 diff --git a/localization/zh-TW/abstract-factory/README.md b/localization/zh-TW/abstract-factory/README.md index 414c1a8f0100..12e0e0332aa5 100644 --- a/localization/zh-TW/abstract-factory/README.md +++ b/localization/zh-TW/abstract-factory/README.md @@ -164,21 +164,21 @@ LOGGER.info(kingdom.getKing().getDescription()); ``` ## 在 Java 中何時使用抽象工廠模式 -在 Java 中,當您遇到以下情況時,請使用抽象工廠模式:: +在 Java 中,當你遇到以下情況時,請使用抽象工廠模式:: * 系統應與其產品的創建、組合和表示方式無關。 -* 您需要用多個產品家族中的其中一個來配置系統。 +* 你需要用多個產品家族中的其中一個來配置系統。 * 必須同時使用一系列相關的產品物件,以強制保持一致性。 -* 您希望提供一個產品的類別庫,只暴露它們的介面,而不是它們的實作。 +* 你希望提供一個產品的類別庫,只暴露它們的介面,而不是它們的實作。 * 相依物件的生命週期比消費者的生命週期短。 * 相依物件需要使用執行時期的值或參數來建構。 -* 您需要在執行時期從一個家族中選擇要使用的產品。 +* 你需要在執行時期從一個家族中選擇要使用的產品。 * 新增新產品或家族時,不應要求修改現有程式碼。 diff --git a/localization/zh-TW/actor-model/README.md b/localization/zh-TW/actor-model/README.md index 652696ce8931..5b4f047d0815 100644 --- a/localization/zh-TW/actor-model/README.md +++ b/localization/zh-TW/actor-model/README.md @@ -139,7 +139,7 @@ public class App { ## 何時在 Java 中使用 Actor Model 模式 - 建立 **並行或分散式系統**時 -- 您希望 **沒有共享的可變狀態**時 +- 你希望 **沒有共享的可變狀態**時 - 你需要 **非同步、訊息驅動的通訊**時 - 元件應 **隔離且鬆散耦合** diff --git a/localization/zh-TW/acyclic-visitor/README.md b/localization/zh-TW/acyclic-visitor/README.md new file mode 100644 index 000000000000..0e92526e6978 --- /dev/null +++ b/localization/zh-TW/acyclic-visitor/README.md @@ -0,0 +1,156 @@ +--- +title: Acyclic Visitor +shortTitle: Acyclic Visitor +category: Behavioral +language: zh +tag: + - Extensibility +--- + +## 目的 + +允許將新功能新增到現有的類層次結構中,而不會影響這些層次結構,也不會有四人幫訪客模式中那樣迴圈依賴的問題。 + +## 解釋 + +真實世界例子 + +> 我們有一個調變解調器類的層次結構。 需要使用基於過濾條件的外部演算法(是Unix或DOS相容的調變解調器)來訪問此層次結構中的調變解調器。 + +通俗地說 + +> 非迴圈訪問者允許將功能新增到現有的類層次結構中,而無需修改層次結構 + +[WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor) 上說 + +> 非迴圈訪客模式允許將新功能新增到現有的類層次結構中,而不會影響這些層次結構,也不會建立四人幫訪客模式中固有的迴圈依賴問題。 + +**程式示例** + +這是調變解調器的層次結構。 + +```java +public abstract class Modem { + public abstract void accept(ModemVisitor modemVisitor); +} + +public class Zoom extends Modem { + ... + @Override + public void accept(ModemVisitor modemVisitor) { + if (modemVisitor instanceof ZoomVisitor) { + ((ZoomVisitor) modemVisitor).visit(this); + } else { + LOGGER.info("Only ZoomVisitor is allowed to visit Zoom modem"); + } + } +} + +public class Hayes extends Modem { + ... + @Override + public void accept(ModemVisitor modemVisitor) { + if (modemVisitor instanceof HayesVisitor) { + ((HayesVisitor) modemVisitor).visit(this); + } else { + LOGGER.info("Only HayesVisitor is allowed to visit Hayes modem"); + } + } +} +``` + +下面我們介紹`調變解調器訪問者`類結構。 + +```java +public interface ModemVisitor { +} + +public interface HayesVisitor extends ModemVisitor { + void visit(Hayes hayes); +} + +public interface ZoomVisitor extends ModemVisitor { + void visit(Zoom zoom); +} + +public interface AllModemVisitor extends ZoomVisitor, HayesVisitor { +} + +public class ConfigureForDosVisitor implements AllModemVisitor { + ... + @Override + public void visit(Hayes hayes) { + LOGGER.info(hayes + " used with Dos configurator."); + } + @Override + public void visit(Zoom zoom) { + LOGGER.info(zoom + " used with Dos configurator."); + } +} + +public class ConfigureForUnixVisitor implements ZoomVisitor { + ... + @Override + public void visit(Zoom zoom) { + LOGGER.info(zoom + " used with Unix configurator."); + } +} +``` + +最後,這裡是訪問者的實踐。 + +```java + var conUnix = new ConfigureForUnixVisitor(); + var conDos = new ConfigureForDosVisitor(); + var zoom = new Zoom(); + var hayes = new Hayes(); + hayes.accept(conDos); + zoom.accept(conDos); + hayes.accept(conUnix); + zoom.accept(conUnix); +``` + +程式輸出: + +``` + // Hayes modem used with Dos configurator. + // Zoom modem used with Dos configurator. + // Only HayesVisitor is allowed to visit Hayes modem + // Zoom modem used with Unix configurator. +``` + +## 類圖 + +![alt text](./etc/acyclic-visitor.png "Acyclic Visitor") + +## 適用性 + +以下情況可以使用此模式: + +* 需要在現有層次結構中新增新功能而無需更改或影響該層次結構時。 +* 當某些功能在層次結構上執行,但不屬於層次結構本身時。 例如 ConfigureForDOS / ConfigureForUnix / ConfigureForX問題。 +* 當你需要根據物件的型別對物件執行非常不同的操作時。 +* 當訪問的類層次結構將經常使用元素類的新派生進行擴充套件時。 +* 當重新編譯,重新連結,重新測試或重新分發派生元素非常昂貴時。 + +## 結果 + +好處: + +* 類層次結構之間沒有依賴關係迴圈。 +* 如果新增了新訪客,則無需重新編譯所有訪客。 +* 如果類層次結構具有新成員,則不會導致現有訪問者中的編譯失敗。 + +壞處: + +* 透過證明它可以接受所有訪客,但實際上僅對特定訪客感興趣,從而違反了[Liskov的替代原則](https://java-design-patterns.com/principles/#liskov-substitution-principle) +* 必須為可訪問的類層次結構中的所有成員建立訪問者的並行層次結構。 + +## 相關的模式 + +* [Visitor Pattern](https://java-design-patterns.com/patterns/visitor/) + +## 鳴謝 + +* [Acyclic Visitor by Robert C. Martin](http://condor.depaul.edu/dmumaugh/OOT/Design-Principles/acv.pdf) +* [Acyclic Visitor in WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor) diff --git a/localization/zh-TW/acyclic-visitor/etc/acyclic-visitor.png b/localization/zh-TW/acyclic-visitor/etc/acyclic-visitor.png new file mode 100644 index 000000000000..7b4df13d80f8 Binary files /dev/null and b/localization/zh-TW/acyclic-visitor/etc/acyclic-visitor.png differ diff --git a/localization/zh-TW/adapter/README.md b/localization/zh-TW/adapter/README.md new file mode 100644 index 000000000000..a9f6787b6b13 --- /dev/null +++ b/localization/zh-TW/adapter/README.md @@ -0,0 +1,136 @@ +--- +title: Adapter +shortTitle: Adapter +category: Structural +language: zh +tag: + - Gang of Four +--- + +## 又被稱為 +包裝器 + +## 目的 +將一個介面轉換成另一個客戶所期望的介面。介面卡讓那些本來因為介面不相容的類可以合作無間。 + +## 解釋 + +現實世界例子 + +> 考慮有這麼一種情況,在你的儲存卡中有一些照片,你想將其傳到你的電腦中。為了傳送資料,你需要某種能夠相容你電腦介面的介面卡以便你的儲存卡能連上你的電腦。在這種情況下,讀卡器就是一個介面卡。 +> 另一個例子就是註明的電源介面卡;三腳插頭不能插在兩腳插座上,需要一個電源介面卡來使其能夠插在兩腳插座上。 +> 還有一個例子就是翻譯官,他翻譯一個人對另一個人說的話。 + +用直白的話來說 + +> 介面卡模式讓你可以把不相容的物件包在介面卡中,以讓其相容其他類。 + +維基百科中說 + +> 在軟體工程中,介面卡模式是一種可以讓現有類的介面把其作為其他介面來使用的設計模式。它經常用來使現有的類和其他類能夠工作並且不用修改其他類的原始碼。 + +**程式設計樣例(物件介面卡)** + +假如有一個船長他只會划船,但不會航行。 + +首先我們有介面`RowingBoat`和`FishingBoat` + +```java +public interface RowingBoat { + void row(); +} + +@Slf4j +public class FishingBoat { + public void sail() { + LOGGER.info("The fishing boat is sailing"); + } +} +``` +船長希望有一個`RowingBoat`介面的實現,這樣就可以移動 + +```java +public class Captain { + + private final RowingBoat rowingBoat; + // default constructor and setter for rowingBoat + public Captain(RowingBoat rowingBoat) { + this.rowingBoat = rowingBoat; + } + + public void row() { + rowingBoat.row(); + } +} +``` + +現在海盜來了,我們的船長需要逃跑但是隻有一個漁船可用。我們需要建立一個可以讓船長使用其划船技能來操作漁船的介面卡。 + +```java +@Slf4j +public class FishingBoatAdapter implements RowingBoat { + + private final FishingBoat boat; + + public FishingBoatAdapter() { + boat = new FishingBoat(); + } + + @Override + public void row() { + boat.sail(); + } +} + +``` + +現在 `船長` 可以使用`FishingBoat`介面來逃離海盜了。 + +```java +var captain = new Captain(new FishingBoatAdapter()); +captain.row(); +``` + +## 類圖 +![alt text](./etc/adapter.urm.png "Adapter class diagram") + + +## 應用 +使用介面卡模式當 + +* 你想使用一個已有類,但是它的介面不能和你需要的所匹配 +* 你需要建立一個可重用類,該類與不相關或不可預見的類進行協作,即不一定具有相容介面的類 +* 你需要使用一些現有的子類,但是子類化他們每一個的子類來進行介面的適配是不現實的。一個物件介面卡可以適配他們父類的介面。 +* 大多數使用第三方類庫的應用使用介面卡作為一個在應用和第三方類庫間的中間層來使應用和類庫解耦。如果必須使用另一個庫,則只需使用一個新庫的介面卡而無需改變應用程式的程式碼。 + +## 後果: +類和物件介面卡有不同的權衡取捨。一個類介面卡 + +* 適配被適配者到目標介面,需要保證只有一個具體的被適配者類。作為結果,當我們想適配一個類和它所有的子類時,類介面卡將不會起作用。 +* 可以讓介面卡重寫一些被適配者的行為,因為介面卡是被適配者的子類。 +* 只引入了一個物件,並且不需要其他指標間接訪問被適配者。 + +物件介面卡 + +* 一個介面卡可以和許多被適配者工作,也就是被適配者自己和所有它的子類。介面卡同時可以為所有被適配者新增功能。 +* 覆蓋被適配者的行為變得更難。需要子類化被適配者然後讓介面卡引用這個子類不是被適配者。 + + +## 現實世界的案例 + +* [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29) +* [java.util.Collections#list()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#list-java.util.Enumeration-) +* [java.util.Collections#enumeration()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#enumeration-java.util.Collection-) +* [javax.xml.bind.annotation.adapters.XMLAdapter](http://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/adapters/XmlAdapter.html#marshal-BoundType-) + + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) + +``` + +``` \ No newline at end of file diff --git a/localization/zh-TW/adapter/etc/adapter.urm.png b/localization/zh-TW/adapter/etc/adapter.urm.png new file mode 100644 index 000000000000..341ad67699d9 Binary files /dev/null and b/localization/zh-TW/adapter/etc/adapter.urm.png differ diff --git a/localization/zh-TW/aggregator-microservices/README.md b/localization/zh-TW/aggregator-microservices/README.md new file mode 100644 index 000000000000..4c4c1d4887ac --- /dev/null +++ b/localization/zh-TW/aggregator-microservices/README.md @@ -0,0 +1,106 @@ +--- +title: Aggregator Microservices +shortTitle: Aggregator Microservices +category: Architectural +language: zh +tag: +- Cloud distributed +- Decoupling +- Microservices +--- + +## 意圖 + +使用者對聚合器服務進行一次呼叫,然後聚合器將呼叫每個相關的微服務。 + +## 解釋 + +真實世界例子 + +> 我們的網路市場需要有關產品及其當前庫存的資訊。 它呼叫聚合服務,聚合服務依次呼叫產品資訊微服務和產品庫存微服務,返回組合資訊。 + +通俗地說 + +> 聚合器微服務從各種微服務中收集資料,並返回一個聚合資料以進行處理。 + +Stack Overflow上說 + +> 聚合器微服務呼叫多個服務以實現應用程式所需的功能。 + +**程式示例** + +讓我們從資料模型開始。 這是我們的`產品`。 + +```java +public class Product { + private String title; + private int productInventories; + // getters and setters -> + ... +} +``` + +接下來,我們將介紹我們的聚合器微服務。 它包含用於呼叫相應微服務的客戶端`ProductInformationClient`和` ProductInventoryClient`。 + +```java +@RestController +public class Aggregator { + + @Resource + private ProductInformationClient informationClient; + + @Resource + private ProductInventoryClient inventoryClient; + + @RequestMapping(path = "/product", method = RequestMethod.GET) + public Product getProduct() { + + var product = new Product(); + var productTitle = informationClient.getProductTitle(); + var productInventory = inventoryClient.getProductInventories(); + + //Fallback to error message + product.setTitle(requireNonNullElse(productTitle, "Error: Fetching Product Title Failed")); + + //Fallback to default error inventory + product.setProductInventories(requireNonNullElse(productInventory, -1)); + + return product; + } +} +``` + +這是產品資訊微服務的精華實現。 庫存微服務類似,它只返回庫存計數。 + +```java +@RestController +public class InformationController { + @RequestMapping(value = "/information", method = RequestMethod.GET) + public String getProductTitle() { + return "The Product Title."; + } +} +``` + +Now calling our `Aggregator` REST API returns the product information. + +現在呼叫我們的聚合器 REST API會返回產品資訊。 + +```bash +curl http://localhost:50004/product +{"title":"The Product Title.","productInventories":5} +``` + +## 類圖 + +![alt text](./etc/aggregator-service.png "Aggregator Microservice") + +## 適用性 + +當需要各種微服務的統一API時,無論客戶端裝置如何,都可以使用Aggregator微服務模式。 + +## 鳴謝 + +* [Microservice Design Patterns](http://web.archive.org/web/20190705163602/http://blog.arungupta.me/microservice-design-patterns/) +* [Microservices Patterns: With examples in Java](https://www.amazon.com/gp/product/1617294543/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617294543&linkId=8b4e570267bc5fb8b8189917b461dc60) +* [Architectural Patterns: Uncover essential patterns in the most indispensable realm of enterprise architecture](https://www.amazon.com/gp/product/B077T7V8RC/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=B077T7V8RC&linkId=c34d204bfe1b277914b420189f09c1a4) diff --git a/localization/zh-TW/aggregator-microservices/etc/aggregator-service.png b/localization/zh-TW/aggregator-microservices/etc/aggregator-service.png new file mode 100644 index 000000000000..75ee82328bbe Binary files /dev/null and b/localization/zh-TW/aggregator-microservices/etc/aggregator-service.png differ diff --git a/localization/zh-TW/ambassador/README.md b/localization/zh-TW/ambassador/README.md new file mode 100644 index 000000000000..b79ca5889e1b --- /dev/null +++ b/localization/zh-TW/ambassador/README.md @@ -0,0 +1,195 @@ +--- +title: Ambassador +shortTitle: Ambassador +category: Structural +language: zh +tag: + - Decoupling + - Cloud distributed +--- + +## 目的 + +在客戶端上提供幫助程式服務例項,並從共享資源上轉移常用功能。 + +## 解釋 + +真實世界例子 + +> 遠端服務有許多客戶端訪問它提供的功能。 該服務是舊版應用程式,無法更新。 使用者的大量請求導致連線問題。新的請求頻率規則需要同時實現延遲檢測和客戶端日誌功能。 + +通俗的說 + +> 使用“大使”模式,我們可以實現來自客戶端的頻率較低的輪詢以及延遲檢查和日誌記錄。 + +微軟文件做了如下闡述 + +> 可以將大使服務視為與客戶端位於同一位置的程序外代理。 此模式對於以語言不可知的方式減輕常見的客戶端連線任務(例如監視,日誌記錄,路由,安全性(如TLS)和彈性模式)的工作很有用。 它通常與舊版應用程式或其他難以修改的應用程式一起使用,以擴充套件其網路功能。 它還可以使專業團隊實現這些功能。 + +**程式示例** + +有了上面的介紹我們將在這個例子中模仿功能。我們有一個用遠端服務實現的介面,同時也是大使服務。 + +```java +interface RemoteServiceInterface { + long doRemoteFunction(int value) throws Exception; +} +``` + +表示為單例的遠端服務。 + +```java +public class RemoteService implements RemoteServiceInterface { + + private static final Logger LOGGER = LoggerFactory.getLogger(RemoteService.class); + private static RemoteService service = null; + + static synchronized RemoteService getRemoteService() { + if (service == null) { + service = new RemoteService(); + } + return service; + } + + private RemoteService() {} + + @Override + public long doRemoteFunction(int value) { + long waitTime = (long) Math.floor(Math.random() * 1000); + + try { + sleep(waitTime); + } catch (InterruptedException e) { + LOGGER.error("Thread sleep interrupted", e); + } + + return waitTime >= 200 ? value * 10 : -1; + } +} +``` + +服務大使新增了像日誌和延遲檢測的額外功能 + +```java +public class ServiceAmbassador implements RemoteServiceInterface { + + private static final Logger LOGGER = LoggerFactory.getLogger(ServiceAmbassador.class); + private static final int RETRIES = 3; + private static final int DELAY_MS = 3000; + + ServiceAmbassador() { + } + + @Override + public long doRemoteFunction(int value) { + return safeCall(value); + } + + private long checkLatency(int value) { + var startTime = System.currentTimeMillis(); + var result = RemoteService.getRemoteService().doRemoteFunction(value); + var timeTaken = System.currentTimeMillis() - startTime; + + LOGGER.info("Time taken (ms): " + timeTaken); + return result; + } + + private long safeCall(int value) { + var retries = 0; + var result = (long) FAILURE; + + for (int i = 0; i < RETRIES; i++) { + if (retries >= RETRIES) { + return FAILURE; + } + + if ((result = checkLatency(value)) == FAILURE) { + LOGGER.info("Failed to reach remote: (" + (i + 1) + ")"); + retries++; + try { + sleep(DELAY_MS); + } catch (InterruptedException e) { + LOGGER.error("Thread sleep state interrupted", e); + } + } else { + break; + } + } + return result; + } +} +``` + +客戶端具有用於與遠端服務進行互動的本地服務大使: + +```java +public class Client { + + private static final Logger LOGGER = LoggerFactory.getLogger(Client.class); + private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador(); + + long useService(int value) { + var result = serviceAmbassador.doRemoteFunction(value); + LOGGER.info("Service result: " + result); + return result; + } +} +``` + +這是兩個使用該服務的客戶端。 + +```java +public class App { + public static void main(String[] args) { + var host1 = new Client(); + var host2 = new Client(); + host1.useService(12); + host2.useService(73); + } +} +``` + +Here's the output for running the example: + +```java +Time taken (ms): 111 +Service result: 120 +Time taken (ms): 931 +Failed to reach remote: (1) +Time taken (ms): 665 +Failed to reach remote: (2) +Time taken (ms): 538 +Failed to reach remote: (3) +Service result: -1 +``` + +## 類圖 + +![alt text](./etc/ambassador.urm.png "Ambassador class diagram") + +## 適用性 + +大使適用於無法修改或極難修改的舊式遠端服務。 可以在客戶端上實現連線性的功能,而無需更改遠端服務。 + +* 大使提供了用於遠端服務的本地介面。 +* 大使在客戶端上提供日誌記錄,斷路,重試和安全性。 + +## 典型用例 + +* 控制對另一個物件的訪問 +* 實現日誌 +* 解除安裝遠端服務任務 +* 簡化網路連線 + +## 已知使用 + +* [Kubernetes-native API gateway for microservices](https://github.com/datawire/ambassador) + +## 相關模式 + +* [Proxy](https://java-design-patterns.com/patterns/proxy/) + +## 鳴謝 + +* [Ambassador pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/ambassador) +* [Designing Distributed Systems: Patterns and Paradigms for Scalable, Reliable Services](https://books.google.co.uk/books?id=6BJNDwAAQBAJ&pg=PT35&lpg=PT35&dq=ambassador+pattern+in+real+world&source=bl&ots=d2e7GhYdHi&sig=Lfl_MDnCgn6lUcjzOg4GXrN13bQ&hl=en&sa=X&ved=0ahUKEwjk9L_18rrbAhVpKcAKHX_KA7EQ6AEIWTAI#v=onepage&q=ambassador%20pattern%20in%20real%20world&f=false) diff --git a/localization/zh-TW/ambassador/etc/ambassador.urm.png b/localization/zh-TW/ambassador/etc/ambassador.urm.png new file mode 100644 index 000000000000..9b50a02ad356 Binary files /dev/null and b/localization/zh-TW/ambassador/etc/ambassador.urm.png differ diff --git a/localization/zh-TW/anti-corruption-layer/README.md b/localization/zh-TW/anti-corruption-layer/README.md new file mode 100644 index 000000000000..bdc05348a925 --- /dev/null +++ b/localization/zh-TW/anti-corruption-layer/README.md @@ -0,0 +1,179 @@ +--- +title: "Java 防腐層模式:在舊系統中維持系統完整性" +shortTitle: 防腐層 +description: "了解防腐層設計模式如何解耦子系統、防止資料污染,並促進 Java 應用程式的順暢整合。" +category: Integration +language: zh-TW +tag: + - Architecture + - Decoupling + - Integration + - Isolation + - Layered architecture + - Migration + - Modernization + - Refactoring + - Wrapping +--- + +## 又稱為 + +* ACL +* 介面層 +* 翻譯層 + +## 防腐層模式的目的 + +防腐層(ACL)是 Java 開發中很重要的設計模式,特別適合系統整合與維持資料完整性。在語意不同的子系統之間建立外觀或介面卡層,轉換不同的資料格式與系統模型,確保整合不會污染商業邏輯或資料完整性。 + +## 防腐層模式的詳細說明與真實世界範例 + +真實世界範例 + +> 這個範例展示防腐層如何讓舊系統與現代平台順利整合,並在系統遷移期間維持商業邏輯的完整性。 +> +> 想像一家大型零售公司要把庫存管理系統從使用多年的舊軟體遷移到現代平台。舊系統包含複雜的商業規則與新系統不相容的資料格式。公司不直接連接兩個系統,而是在中間建立防腐層(ACL)。 +> +> ACL 會在兩個系統之間轉換與調整資料。當新系統要求庫存資料時,ACL 將要求轉成舊系統能理解的格式,取得資料後再轉成適合新系統的格式。如此新系統不會受到舊系統內部細節影響,也能避免資料與商業邏輯被污染。 + +簡單來說 + +> 防腐層透過提供中介翻譯層,保護系統免受外部系統的複雜性與變動影響。 + +[Microsoft 文件](https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer)指出: + +> 在語意不同的子系統之間實作外觀或介面卡層。此層會翻譯其中一個子系統對另一個子系統提出的要求,確保應用程式的設計不受外部子系統相依性限制。此模式最早由 Eric Evans 在《領域驅動設計》中描述。 + +序列圖 + +![防腐層序列圖](./etc/anti-corruption-layer-sequence-diagram.png "防腐層序列圖") + +## Java 防腐層模式的程式範例 + +Java 中的防腐層會提供一個中介層來轉換資料格式,確保不同系統的整合不會造成資料污染。 + +以下有兩個商店訂單系統:`Legacy` 與 `Modern`。兩者使用不同的領域模型,且必須同時運作。訂單可能來自任一系統,因此接收 `legacyOrder` 的系統需要確認訂單有效,且不存在於另一個系統中。防腐層負責隱藏另一個系統的領域模型與操作。 + +`Legacy` 系統的領域模型: + +```java +public class LegacyOrder { + private String id; + private String customer; + private String item; + private String qty; + private String price; +} +``` + +`Modern` 系統的領域模型: + +```java +public class ModernOrder { + private String id; + private Customer customer; + + private Shipment shipment; + + private String extra; +} + +public class Customer { + private String address; +} + +public class Shipment { + private String item; + private String qty; + private String price; +} +``` + +防腐層: + +```java +public class AntiCorruptionLayer { + + @Autowired + private ModernShop modernShop; + + @Autowired + private LegacyShop legacyShop; + + public Optional findOrderInModernSystem(String id) { + return modernShop.findOrder(id).map(o -> /* map to legacyOrder*/); + } + + public Optional findOrderInLegacySystem(String id) { + return legacyShop.findOrder(id).map(o -> /* map to modernOrder*/); + } + +} +``` + +無論 `Legacy` 或 `Modern` 系統需要與對方溝通,都應使用 ACL,以避免污染目前的領域模型。以下範例展示 `Legacy` 系統如何透過 `Modern` 系統驗證訂單後下單。 + +```java +public class LegacyShop { + @Autowired + private AntiCorruptionLayer acl; + + public void placeOrder(LegacyOrder legacyOrder) throws ShopException { + + String id = legacyOrder.getId(); + + Optional orderInModernSystem = acl.findOrderInModernSystem(id); + + if (orderInModernSystem.isPresent()) { + // order is already in the modern system + } else { + // place order in the current system + } + } +} +``` + +## 何時在 Java 中使用防腐層模式 + +* 遷移計畫分成多個階段,但新舊系統之間仍需持續整合。 +* 兩個以上的子系統語意不同,卻仍需要互相溝通。 +* 整合舊系統或外部系統時,直接整合可能污染新系統的領域模型。 +* 大型系統中的不同子系統使用不同的資料格式或結構。 +* 需要讓子系統或外部服務保持鬆散耦合,以便維護與擴充。 + +## Java 防腐層模式教學 + +* [防腐層(Microsoft)](https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer) +* [防腐層模式(Amazon)](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/acl.html) + +## Java 防腐層模式的實際應用 + +* 微服務架構中的服務需要互相溝通,但不應緊密相依彼此的資料結構。 +* 企業系統整合,尤其是現代系統與舊系統的整合。 +* 領域驅動設計(DDD)的限界上下文中,與外部系統互動時維持領域模型完整性。 + +## 防腐層模式的優點與取捨 + +優點: + +* 提供清楚的邊界,保護領域模型的完整性。 +* 促進系統鬆散耦合,提高面對外部變更的韌性。 +* 將整合程式碼與商業邏輯隔離,使程式更乾淨且容易維護。 + +取捨: + +* 翻譯過程會增加複雜度與可能的效能負擔。 +* 需要額外設計與實作,避免此層變成瓶頸。 +* 管理不當時可能造成模型重複。 + +## 相關 Java 設計模式 + +* [介面卡](https://java-design-patterns.com/patterns/adapter/):可使用介面卡模式轉換不同的資料格式或結構。 +* [外觀](https://java-design-patterns.com/patterns/facade/):防腐層可以視為用來隔離子系統的專用外觀模式。 +* [閘道](https://java-design-patterns.com/patterns/gateway/):可作為外部系統的閘道,提供統一介面。 + +## 參考資料與致謝 + +* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3vptcJz) +* [Implementing Domain-Driven Design](https://amzn.to/3ISOSRA) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) \ No newline at end of file diff --git a/localization/zh-TW/anti-corruption-layer/etc/anti-corruption-layer-sequence-diagram.png b/localization/zh-TW/anti-corruption-layer/etc/anti-corruption-layer-sequence-diagram.png new file mode 100644 index 000000000000..835ff4e84a0f Binary files /dev/null and b/localization/zh-TW/anti-corruption-layer/etc/anti-corruption-layer-sequence-diagram.png differ diff --git a/localization/zh-TW/api-gateway/README.md b/localization/zh-TW/api-gateway/README.md new file mode 100644 index 000000000000..7e728a26591a --- /dev/null +++ b/localization/zh-TW/api-gateway/README.md @@ -0,0 +1,137 @@ +--- +title: API Gateway +shortTitle: API Gateway +category: Architectural +language: zh +tag: + - Cloud distributed + - Decoupling + - Microservices +--- + +## 目的 + +API閘道器將所有對微服務的呼叫聚合到一起。使用者對API閘道器進行一次呼叫,然後API閘道器呼叫每個相關的微服務。 + +## 解釋 + +使用微服務模式,客戶端可能需要來自多個不同微服務的資料。 如果客戶端直接呼叫每個微服務,則可能會導致更長的載入時間,因為客戶端將不得不為每個呼叫的微服務發出網路請求。此外,讓客戶端呼叫每個微服務會直接將客戶端與該微服務相關聯-如果微服務的內部實現發生了變化(例如,如果將來某個時候合併了兩個微服務),或者微服務的位置(主機和埠) 更改,則必須更新使用這些微服務的每個客戶端。 + +API閘道器模式的目的是緩解其中的一些問題。 在API閘道器模式中,在客戶端和微服務之間放置了一個附加實體(API閘道器)。API閘道器的工作是將對微服務的呼叫進行聚合。 客戶端不是一次單獨呼叫每個微服務,而是一次呼叫API閘道器。 然後,API閘道器呼叫客戶端所需的每個微服務。 + +真實世界例子 + +> 我們正在為電子商務站點實現微服務和API閘道器模式。 在此係統中,API閘道器呼叫Image和Price微服務。 + +通俗地說 + +> 對於使用微服務架構實現的系統,API是聚合微服務呼叫的入口點。 + +維基百科說 + +> API閘道器是充當API前置,接收API請求,執行限制和安全策略,將請求傳遞到後端服務,然後將響應傳遞迴請求者的伺服器。閘道器通常包括一個轉換引擎,以實時地編排和修改請求和響應。 閘道器可以提供收集分析資料和提供快取等功能。閘道器還可以提供支援身份驗證,授權,安全性,審計和法規遵從性的功能。 + +**程式示例** + +此實現展示了電子商務站點的API閘道器模式。` ApiGateway`分別使用` ImageClientImpl`和` PriceClientImpl`來呼叫Image和Price微服務。 在桌面裝置上檢視該網站的客戶可以看到價格資訊和產品圖片,因此` ApiGateway`會呼叫這兩種微服務並在`DesktopProduct`模型中彙總資料。 但是,移動使用者只能看到價格資訊。 他們看不到產品圖片。 對於移動使用者,`ApiGateway`僅檢索價格資訊,並將其用於填充`MobileProduct`模型。 + +這個是影象微服務的實現。 + +```java +public interface ImageClient { + String getImagePath(); +} + +public class ImageClientImpl implements ImageClient { + @Override + public String getImagePath() { + var httpClient = HttpClient.newHttpClient(); + var httpGet = HttpRequest.newBuilder() + .GET() + .uri(URI.create("http://localhost:50005/image-path")) + .build(); + + try { + var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString()); + return httpResponse.body(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + + return null; + } +} +``` + +這裡是價格服務的實現。 + +```java +public interface PriceClient { + String getPrice(); +} + +public class PriceClientImpl implements PriceClient { + + @Override + public String getPrice() { + var httpClient = HttpClient.newHttpClient(); + var httpGet = HttpRequest.newBuilder() + .GET() + .uri(URI.create("http://localhost:50006/price")) + .build(); + + try { + var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString()); + return httpResponse.body(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + + return null; + } +} +``` + +在這裡,我們可以看到API閘道器如何將請求對映到微服務。 + +```java +public class ApiGateway { + + @Resource + private ImageClient imageClient; + + @Resource + private PriceClient priceClient; + + @RequestMapping(path = "/desktop", method = RequestMethod.GET) + public DesktopProduct getProductDesktop() { + var desktopProduct = new DesktopProduct(); + desktopProduct.setImagePath(imageClient.getImagePath()); + desktopProduct.setPrice(priceClient.getPrice()); + return desktopProduct; + } + + @RequestMapping(path = "/mobile", method = RequestMethod.GET) + public MobileProduct getProductMobile() { + var mobileProduct = new MobileProduct(); + mobileProduct.setPrice(priceClient.getPrice()); + return mobileProduct; + } +} +``` + +## 類圖 +![alt text](./etc/api-gateway.png "API Gateway") + +## 適用性 + +在以下情況下使用API閘道器模式 + +* 你正在使用微服務架構,並且需要聚合單點來進行微服務呼叫。 + +## 鳴謝 + +* [microservices.io - API Gateway](http://microservices.io/patterns/apigateway.html) +* [NGINX - Building Microservices: Using an API Gateway](https://www.nginx.com/blog/building-microservices-using-an-api-gateway/) +* [Microservices Patterns: With examples in Java](https://www.amazon.com/gp/product/1617294543/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617294543&linkId=ac7b6a57f866ac006a309d9086e8cfbd) +* [Building Microservices: Designing Fine-Grained Systems](https://www.amazon.com/gp/product/1491950358/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1491950358&linkId=4c95ca9831e05e3f0dadb08841d77bf1) diff --git a/localization/zh-TW/api-gateway/etc/api-gateway.png b/localization/zh-TW/api-gateway/etc/api-gateway.png new file mode 100644 index 000000000000..bb3ec2e2e1a8 Binary files /dev/null and b/localization/zh-TW/api-gateway/etc/api-gateway.png differ diff --git a/localization/zh-TW/arrange-act-assert/README.md b/localization/zh-TW/arrange-act-assert/README.md new file mode 100644 index 000000000000..e98c986e822c --- /dev/null +++ b/localization/zh-TW/arrange-act-assert/README.md @@ -0,0 +1,138 @@ +--- +title: Arrange/Act/Assert +shortTitle: Arrange/Act/Assert +category: Idiom +language: zh +tag: + - Testing +--- + +## 或稱 + +Given/When/Then + +## 意圖 + +安排/執行/斷言(AAA)是組織單元測試的一種模式。 + +它將測試分為三個清晰而獨特的步驟: + +1. 安排:執行測試所需的設定和初始化。 +2. 執行:採取測試所需的行動。 +3. 斷言:驗證測試結果。 + +## 解釋 + +這種模式有幾個明顯的好處。 它在測試的設定,操作和結果之間建立了清晰的分隔。 這種結構使程式碼更易於閱讀和理解。 如果按順序排列步驟並格式化程式碼以將它們分開,則可以掃描測試並快速瞭解其功能。 + +當你編寫測試時,它還會強制執行一定程度的紀律。 你必須清楚地考慮你的測試將執行的三個步驟。 由於你已經有了大綱,因此可以使同時編寫測試變得更加自然。 + +真實世界例子 + +> 我們需要為一個類編寫全面而清晰的單元測試套件。 + +通俗地說 + +> 安排/執行/斷言是一種測試模式,將測試分為三個清晰的步驟以方便維護。 + +WikiWikiWeb 上說 + +> 安排/執行/斷言是用於在單元測試方法中排列和格式化程式碼的模式。 + +**程式示例** + +讓我們首先介紹要進行單元測試的`Cash`類。 + +```java +public class Cash { + + private int amount; + + Cash(int amount) { + this.amount = amount; + } + + void plus(int addend) { + amount += addend; + } + + boolean minus(int subtrahend) { + if (amount >= subtrahend) { + amount -= subtrahend; + return true; + } else { + return false; + } + } + + int count() { + return amount; + } +} +``` + +然後我們根據安排/ 執行 / 斷言模式編寫單元測試。 注意每個單元測試的步驟是分開的清晰的。 + +```java +class CashAAATest { + + @Test + void testPlus() { + //Arrange + var cash = new Cash(3); + //Act + cash.plus(4); + //Assert + assertEquals(7, cash.count()); + } + + @Test + void testMinus() { + //Arrange + var cash = new Cash(8); + //Act + var result = cash.minus(5); + //Assert + assertTrue(result); + assertEquals(3, cash.count()); + } + + @Test + void testInsufficientMinus() { + //Arrange + var cash = new Cash(1); + //Act + var result = cash.minus(6); + //Assert + assertFalse(result); + assertEquals(1, cash.count()); + } + + @Test + void testUpdate() { + //Arrange + var cash = new Cash(5); + //Act + cash.plus(6); + var result = cash.minus(3); + //Assert + assertTrue(result); + assertEquals(8, cash.count()); + } +} +``` + +## 適用性 + +使用 安排/執行/斷言 模式當 + +* 你需要結構化你的單元測試程式碼這樣它們可以更好的閱讀,維護和增強。 + +## 鳴謝 + +* [Arrange, Act, Assert: What is AAA Testing?](https://blog.ncrunch.net/post/arrange-act-assert-aaa-testing.aspx) +* [Bill Wake: 3A – Arrange, Act, Assert](https://xp123.com/articles/3a-arrange-act-assert/) +* [Martin Fowler: GivenWhenThen](https://martinfowler.com/bliki/GivenWhenThen.html) +* [xUnit Test Patterns: Refactoring Test Code](https://www.amazon.com/gp/product/0131495054/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0131495054&linkId=99701e8f4af2f7e8dd50d720c9b63dbf) +* [Unit Testing Principles, Practices, and Patterns](https://www.amazon.com/gp/product/1617296279/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617296279&linkId=74c75cf22a63c3e4758ae08aa0a0cc35) +* [Test Driven Development: By Example](https://www.amazon.com/gp/product/0321146530/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321146530&linkId=5c63a93d8c1175b84ca5087472ef0e05) diff --git a/localization/zh-TW/async-method-invocation/README.md b/localization/zh-TW/async-method-invocation/README.md new file mode 100644 index 000000000000..51a7e39e876a --- /dev/null +++ b/localization/zh-TW/async-method-invocation/README.md @@ -0,0 +1,158 @@ +--- +title: Async Method Invocation +shortTitle: Async Method Invocation +category: Concurrency +language: zh +tag: + - Reactive +--- + +## 意圖 + +非同步方法呼叫是一個呼叫執行緒在等待任務結果時不會阻塞的模式。模式為多個獨立的任務提供並行的處理方式並且透過回撥或等到它們全部完成來接收任務結果。 + +## 解釋 + +真實世界例子 + +> 發射火箭是一項令人激動的事務。任務指揮官發出了發射命令,經過一段不確定的時間後,火箭要麼成功發射,要麼慘遭失敗。 + +通俗地說 + +> 非同步方法呼叫開始任務處理,並在任務完成之前立即返回。 任務處理的結果稍後返回給呼叫方。 + +維基百科說 + +> 在多執行緒計算機程式設計中,非同步方法呼叫(AMI),也稱為非同步方法呼叫或非同步模式,是一種設計模式,其中在等待被呼叫的程式碼完成時不會阻塞呼叫站點。 而是在執行結果到達時通知呼叫執行緒。輪詢呼叫結果是不希望的選項。 + +**程式示例** + +在此示例中,我們正在發射太空火箭並部署月球漫遊車。該應用演示了非同步方法呼叫模式。 模式的關鍵部分是`AsyncResult`(用於非同步評估值的中間容器),`AsyncCallback`(可以在任務完成時被執行)和`AsyncExecutor`(用於管理非同步任務的執行)。 + +```java +public interface AsyncResult { + boolean isCompleted(); + T getValue() throws ExecutionException; + void await() throws InterruptedException; +} +``` + +```java +public interface AsyncCallback { + void onComplete(T value, Optional ex); +} +``` + +```java +public interface AsyncExecutor { + AsyncResult startProcess(Callable task); + AsyncResult startProcess(Callable task, AsyncCallback callback); + T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException; +} +``` + +`ThreadAsyncExecutor`是`AsyncExecutor`的實現。 接下來將突出顯示其一些關鍵部分。 + +```java +public class ThreadAsyncExecutor implements AsyncExecutor { + + @Override + public AsyncResult startProcess(Callable task) { + return startProcess(task, null); + } + + @Override + public AsyncResult startProcess(Callable task, AsyncCallback callback) { + var result = new CompletableResult<>(callback); + new Thread( + () -> { + try { + result.setValue(task.call()); + } catch (Exception ex) { + result.setException(ex); + } + }, + "executor-" + idx.incrementAndGet()) + .start(); + return result; + } + + @Override + public T endProcess(AsyncResult asyncResult) + throws ExecutionException, InterruptedException { + if (!asyncResult.isCompleted()) { + asyncResult.await(); + } + return asyncResult.getValue(); + } +} +``` + +然後,我們準備發射一些火箭,看看一切是如何協同工作的。 + +```java +public static void main(String[] args) throws Exception { + // 構造一個將執行非同步任務的新執行程式 + var executor = new ThreadAsyncExecutor(); + + // 以不同的處理時間開始一些非同步任務,最後兩個使用回撥處理程式 + final var asyncResult1 = executor.startProcess(lazyval(10, 500)); + final var asyncResult2 = executor.startProcess(lazyval("test", 300)); + final var asyncResult3 = executor.startProcess(lazyval(50L, 700)); + final var asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Deploying lunar rover")); + final var asyncResult5 = + executor.startProcess(lazyval("callback", 600), callback("Deploying lunar rover")); + + // 在當前執行緒中模擬非同步任務正在它們自己的執行緒中執行 + Thread.sleep(350); // 哦,兄弟,我們在這很辛苦 + log("Mission command is sipping coffee"); + + // 等待任務完成 + final var result1 = executor.endProcess(asyncResult1); + final var result2 = executor.endProcess(asyncResult2); + final var result3 = executor.endProcess(asyncResult3); + asyncResult4.await(); + asyncResult5.await(); + + // log the results of the tasks, callbacks log immediately when complete + // 記錄任務結果的日誌, 回撥的日誌會在回撥完成時立刻記錄 + log("Space rocket <" + result1 + "> launch complete"); + log("Space rocket <" + result2 + "> launch complete"); + log("Space rocket <" + result3 + "> launch complete"); +} +``` + +這是程式控制臺的輸出。 + +```java +21:47:08.227 [executor-2] INFO com.iluwatar.async.method.invocation.App - Space rocket launched successfully +21:47:08.269 [main] INFO com.iluwatar.async.method.invocation.App - Mission command is sipping coffee +21:47:08.318 [executor-4] INFO com.iluwatar.async.method.invocation.App - Space rocket <20> launched successfully +21:47:08.335 [executor-4] INFO com.iluwatar.async.method.invocation.App - Deploying lunar rover <20> +21:47:08.414 [executor-1] INFO com.iluwatar.async.method.invocation.App - Space rocket <10> launched successfully +21:47:08.519 [executor-5] INFO com.iluwatar.async.method.invocation.App - Space rocket launched successfully +21:47:08.519 [executor-5] INFO com.iluwatar.async.method.invocation.App - Deploying lunar rover +21:47:08.616 [executor-3] INFO com.iluwatar.async.method.invocation.App - Space rocket <50> launched successfully +21:47:08.617 [main] INFO com.iluwatar.async.method.invocation.App - Space rocket <10> launch complete +21:47:08.617 [main] INFO com.iluwatar.async.method.invocation.App - Space rocket launch complete +21:47:08.618 [main] INFO com.iluwatar.async.method.invocation.App - Space rocket <50> launch complete +``` + +# 類圖 + +![alt text](./etc/async-method-invocation.png "Async Method Invocation") + +## 適用性 + +在以下情況下使用非同步方法呼叫模式 + +* 你有多個可以並行執行的獨立任務 +* 你需要提高一組順序任務的效能 +* 你的處理能力或長時間執行的任務數量有限,並且呼叫方不應等待任務執行完畢 + +## 真實世界例子 + +* [FutureTask](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/FutureTask.html) +* [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) +* [ExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html) +* [Task-based Asynchronous Pattern](https://msdn.microsoft.com/en-us/library/hh873175.aspx) diff --git a/localization/zh-TW/async-method-invocation/etc/async-method-invocation.png b/localization/zh-TW/async-method-invocation/etc/async-method-invocation.png new file mode 100644 index 000000000000..764895d7a217 Binary files /dev/null and b/localization/zh-TW/async-method-invocation/etc/async-method-invocation.png differ diff --git a/localization/zh-TW/backends-for-frontends/README.md b/localization/zh-TW/backends-for-frontends/README.md new file mode 100644 index 000000000000..7196d0014055 --- /dev/null +++ b/localization/zh-TW/backends-for-frontends/README.md @@ -0,0 +1,124 @@ +--- +title: "Java 前端專用後端模式:依照用戶端需求打造 API" +shortTitle: 前端專用後端 +description: "了解 Java 的前端專用後端(BFF)設計模式,讓每種用戶端擁有專屬後端服務。" +category: Architectural +language: zh-TW +tag: + - API design + - Architecture + - Client-server + - Decoupling + - Microservices +--- + +## 又稱為 + +* Backend For Frontend +* BFF 模式 + +## 前端專用後端模式的目的 + +為每個用戶端應用程式(行動裝置、桌面程式、聊天機器人等)提供專屬後端服務,讓每個用戶端都能取得完全符合自身需求的 API,而不是讓所有用戶端共用一個通用後端。 + +## 前端專用後端模式的詳細說明與真實世界範例 + +真實世界範例 + +> 想像一家零售公司有行動 App、桌面後台工具與客服聊天機器人。它們都需要客戶、購物車、訂單與供應商資料,但手機畫面只需要簡短摘要,桌面工具卻需要完整的訂單與庫存細節。公司不提供一個讓所有用戶端自行篩選或過度取得資料的共用 API,而是為行動用戶端與內部網路用戶端各建立一個小型 BFF。每個 BFF 只呼叫用戶端需要的下游微服務,並回傳符合該用戶端的資料內容。 + +簡單來說 + +> 讓每種類型的用戶端都有量身打造的後端,而不是強迫所有用戶端使用一個大小通吃的 API。 + +廣泛推廣此模式的 Sam Newman 表示: + +> 建立供特定前端應用程式或介面使用的獨立後端服務。 + +## 架構圖 + +``` +node mobile{ + component iosapp as "ios app" + component androidapp as "android app" +} +node intranet{ + component desktop as "desktop app" + component chatbot +} +component bff as "BFF server"{ + component iosbff as "ios BFF" + component androidbff as "android BFF" + component chatbotbff as "chatbot BFF" + component desktopbff as "desktop BFF" +} +node intranetserv as "intranet services server"{ + component ss as "supplier service API" +} +cloud onlypublic as "public cloud"{ + component cas as "customer authentication service API" + component cs as "cart service API" +} +cloud cloudserv as "managed cloud"{ + component os as "order service API" +} +iosapp -- iosbff +androidapp -- androidbff +chatbot -- chatbotbff +desktop -- desktopbff +iosbff -- cas +androidbff -- cas +iosbff -- cs +androidbff -- cs +iosbff -- os +androidbff -- os +chatbotbff -- os +desktopbff -- os +chatbotbff -- ss +desktopbff -- ss +``` + +為了讓示範保持聚焦,本範例以兩個面向用戶端的 BFF 取代圖中的四個:代表 iOS/Android BFF 的 **Mobile BFF**,以及代表桌面程式/聊天機器人 BFF 的 **Desktop BFF**。兩者都呼叫共用的 `AuthService` 與 `OrderService`;`CartService` 只有 Mobile BFF 使用,`SupplierService` 只有 Desktop BFF 可以存取,與圖中的扇出關係一致。 + +## 類別圖 + +![前端專用後端類別圖](./etc/backends-for-frontends.png) + +## 何時使用前端專用後端模式 + +* 不同用戶端需要同一份底層資料的不同形狀、粒度或聚合方式。 +* 共用 API 已累積大量用戶端專屬的條件分支、選擇性欄位或查詢參數。 +* 不同用戶端團隊需要獨立演進 API,不希望每次變更都協調同一個後端團隊。 +* 某些用戶端(例如行動裝置)需要大幅縮減資料量以降低頻寬與延遲,而其他用戶端需要更豐富的資料。 + +## 優點與取捨 + +優點: + +* 每個用戶端都取得針對需求最佳化的 API,讓用戶端更簡單、效能更好。 +* 用戶端團隊可以獨立演進自己的 BFF,減少跨團隊協調。 +* 下游微服務維持通用且可重複使用,用戶端專屬邏輯集中在 BFF 層。 + +取捨: + +* 需要建立、部署與維運更多服務。 +* 若沒有妥善抽取,共用邏輯可能在不同 BFF 間重複。 +* 用戶端與下游服務之間多了一次網路跳轉。 + +## 如何在 Java 中實作前端專用後端模式 + +1. 找出需要不同資料形狀的用戶端類型。 +2. 定義每個用戶端真正依賴的下游服務,例如 `AuthService`、`CartService`、`OrderService` 與 `SupplierService`。 +3. 為每種用戶端建立一個 BFF,實作共用的 `ClientBff` 契約,且只依賴該用戶端需要的下游服務。 +4. 讓每個 BFF 將下游資料聚合並重新整理成用戶端專用的回應 DTO,例如 `MobileDashboardResponse`、`DesktopDashboardResponse`。 +5. 讓用戶端應用程式呼叫自己的 BFF,而不是直接呼叫下游服務。 + +## 原始碼 + +* [模式:前端專用後端(Sam Newman)](https://samnewman.io/patterns/architectural/bff/) +* [Microservices Patterns: With examples in Java(Chris Richardson)](https://www.amazon.com/Microservices-Patterns-examples-Chris-Richardson/dp/1617294543) + +## 參考資料與致謝 + +* [Building Microservices](https://www.oreilly.com/library/view/building-microservices-2nd/9781492034018/) +* [Backend for frontend(microservices.io)](https://microservices.io/patterns/apigateway.html) \ No newline at end of file diff --git a/localization/zh-TW/backpressure/README.md b/localization/zh-TW/backpressure/README.md new file mode 100644 index 000000000000..fb1de34a9dbf --- /dev/null +++ b/localization/zh-TW/backpressure/README.md @@ -0,0 +1,142 @@ +--- +title: "Java 背壓模式:調節生產者到消費者的資料流以避免過載" +shortTitle: 背壓 +description: "透過實例了解 Java 背壓設計模式,讓資料流符合消費者能力,在避免過載的同時維持系統穩定與效能。" +category: Concurrency +language: zh-TW +tag: + - Asynchronous + - Event-driven + - Reactive + - Resilience +--- + +## 又稱為 + +* 流量控制 +* 速率限制機制 + +## 背壓模式的目的 + +控制資料產生的速率,避免下游消費者承受過量負載。 + +## 背壓模式的詳細說明與真實世界範例 + +真實世界範例 + +> 想像一家忙碌的咖啡店有多位咖啡師負責沖泡飲料(生產者),但只有一位咖啡師負責製作拉花(消費者)。如果沖泡速度超過拉花速度,飲料就會堆積,造成品質問題或必須丟棄。加入節奏控制後,只有在拉花咖啡師準備好時才送出下一杯,所有人便能保持同步,減少浪費並維持一致的體驗。 + +簡單來說 + +> 背壓是一種流量控制機制,會依照消費者的處理能力調節資料產生速度,避免系統過載。 + +維基百科指出: + +> 背壓是對流體通過管道時理想流動的阻力。在分散式系統,尤其是事件驅動架構中,背壓是一種調節資料流量的技術,可避免元件承受過量負載。 + +序列圖 + +![背壓序列圖](./etc/backpressure-sequence-diagram.png) + +## Java 背壓模式的程式範例 + +本範例使用 Project Reactor 實作背壓。首先建立會產生整數串流的發佈者,並加入短暫延遲來模擬較慢的產生速率: + +```java +public class Publisher { + public static Flux publish(int start, int count, int delay) { + return Flux.range(start, count).delayElements(Duration.ofMillis(delay)).log(); + } +} +``` + +接著透過 Reactor 的 `BaseSubscriber` 建立自訂訂閱者。它每個項目休眠 500 毫秒來模擬慢速處理;一開始要求 10 個項目,每處理 5 個就再要求 5 個: + +```java +public class Subscriber extends BaseSubscriber { + + private static final Logger logger = LoggerFactory.getLogger(Subscriber.class); + + @Override + protected void hookOnSubscribe(@NonNull Subscription subscription) { + logger.info("subscribe()"); + request(10); // 一開始要求 10 個項目 + } + + @Override + protected void hookOnNext(@NonNull Integer value) { + processItem(); + logger.info("process({})", value); + if (value % 5 == 0) { + // 處理前 5 個項目後要求下一批 5 個 + request(5); + } + } + + @Override + protected void hookOnComplete() { + // 處理完成 + } + + private void processItem() { + try { + Thread.sleep(500); // 模擬慢速處理 + } catch (InterruptedException e) { + logger.error(e.getMessage(), e); + } + } +} +``` + +最後在 `main` 方法中發佈一段整數範圍,並使用自訂訂閱者訂閱。主執行緒的短暫休眠讓資料發佈、背壓要求與處理過程能完整呈現: + +```java +public static void main(String[] args) throws InterruptedException { + Subscriber sub = new Subscriber(); + Publisher.publish(1, 8, 200).subscribe(sub); + Thread.sleep(5000); // 等待執行完成 +} +``` + +以下是程式輸出範例,包含訂閱者要求更多資料,以及每個整數被處理的紀錄: + +``` +23:09:55.746 [main] DEBUG reactor.util.Loggers -- Using Slf4j logging framework +23:09:55.762 [main] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onSubscribe(FluxConcatMapNoPrefetch.FluxConcatMapNoPrefetchSubscriber) +23:09:55.762 [main] INFO com.iluwatar.backpressure.Subscriber -- subscribe() +23:09:55.763 [main] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- request(10) +23:09:55.969 [parallel-1] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(1) +23:09:56.475 [parallel-1] INFO com.iluwatar.backpressure.Subscriber -- process(1) +23:09:59.311 [parallel-5] INFO com.iluwatar.backpressure.Subscriber -- process(5) +23:09:59.311 [parallel-5] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- request(5) +23:10:01.437 [parallel-8] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onComplete() +``` + +## 何時在 Java 中使用背壓模式 + +* 資料高速產生、消費者有過載風險的 Java 系統。 +* 需要在負載變動時維持穩定性的反應式或事件驅動架構。 + +## 背壓模式的優點與取捨 + +優點: + +* 保護消費者免於飽和與資源耗盡。 + +取捨: + +* 生產速度必須配合消費能力時,可能引入延遲。 +* 多個並行資料來源的複雜系統需要仔細協調。 + +## 相關 Java 設計模式 + +* [觀察者模式](https://java-design-patterns.com/patterns/observer/):兩者都涉及生產者通知消費者;觀察者通常是同步且緊密耦合的。 +* [發佈訂閱模式](https://java-design-patterns.com/patterns/publish-subscribe/):兩者都處理非同步資料流,可以一起管理訊息的分發與消費。 + +## 參考資料與致謝 + +* [Backpressure Explained(RedHat Developers Blog)](https://developers.redhat.com/articles/backpressure-explained) +* [Hands-On Reactive Programming in Spring 5](https://amzn.to/3YuYfyO) +* [Reactive Programming with RxJava: Creating Asynchronous, Event-Based Applications](https://amzn.to/42negbf) +* [Reactive Streams in Java](https://amzn.to/3RJjUzA) +* [Reactive Streams Specification](https://www.reactive-streams.org/) \ No newline at end of file diff --git a/localization/zh-TW/balking/README.md b/localization/zh-TW/balking/README.md new file mode 100644 index 000000000000..3b5b8b3c5070 --- /dev/null +++ b/localization/zh-TW/balking/README.md @@ -0,0 +1,129 @@ +--- +title: Balking +shortTitle: Balking +category: Concurrency +language: zh +tag: + - Decoupling +--- + +## 意圖 + +止步模式用於防止物件在不完整或不合適的狀態下執行某些程式碼。 + +## 解釋 + +真實世界例子 + +> 洗衣機中有一個開始按鈕,用於啟動衣物洗滌。當洗衣機處於非活動狀態時,按鈕將按預期工作,但是如果已經在洗滌,則按鈕將不起任何作用。 + +通俗地說 + +> 使用止步模式,僅當物件處於特定狀態時才執行特定程式碼。 + +維基百科說 + +> 禁止模式是一種軟體設計模式,僅當物件處於特定狀態時才對物件執行操作。例如,一個物件讀取zip壓縮檔案並在壓縮檔案沒開啟的時候呼叫get方法,物件將在請求的時候”止步“。 + +**程式示例** + +在此示例中,` WashingMachine`是一個具有兩個狀態的物件,可以處於兩種狀態:ENABLED和WASHING。 如果機器已啟用,則使用執行緒安全方法將狀態更改為WASHING。 另一方面,如果已經進行了清洗並且任何其他執行緒執行`wash()`,則它將不執行該操作,而是不執行任何操作而返回。 + +這裡是`WashingMachine` 類相關的部分。 + +```java +@Slf4j +public class WashingMachine { + + private final DelayProvider delayProvider; + private WashingMachineState washingMachineState; + + public WashingMachine(DelayProvider delayProvider) { + this.delayProvider = delayProvider; + this.washingMachineState = WashingMachineState.ENABLED; + } + + public WashingMachineState getWashingMachineState() { + return washingMachineState; + } + + public void wash() { + synchronized (this) { + var machineState = getWashingMachineState(); + LOGGER.info("{}: Actual machine state: {}", Thread.currentThread().getName(), machineState); + if (this.washingMachineState == WashingMachineState.WASHING) { + LOGGER.error("Cannot wash if the machine has been already washing!"); + return; + } + this.washingMachineState = WashingMachineState.WASHING; + } + LOGGER.info("{}: Doing the washing", Thread.currentThread().getName()); + this.delayProvider.executeAfterDelay(50, TimeUnit.MILLISECONDS, this::endOfWashing); + } + + public synchronized void endOfWashing() { + washingMachineState = WashingMachineState.ENABLED; + LOGGER.info("{}: Washing completed.", Thread.currentThread().getId()); + } +} +``` + +這裡是一個`WashingMachine`所使用的`DelayProvider`簡單介面。 + +```java +public interface DelayProvider { + void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task); +} +``` + +現在,我們使用`WashingMachine`介紹該應用程式。 + +```java + public static void main(String... args) { + final var washingMachine = new WashingMachine(); + var executorService = Executors.newFixedThreadPool(3); + for (int i = 0; i < 3; i++) { + executorService.execute(washingMachine::wash); + } + executorService.shutdown(); + try { + executorService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException ie) { + LOGGER.error("ERROR: Waiting on executor service shutdown!"); + Thread.currentThread().interrupt(); + } + } +``` + +下面是程式的輸出。 + +``` +14:02:52.268 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-2: Actual machine state: ENABLED +14:02:52.272 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-2: Doing the washing +14:02:52.272 [pool-1-thread-3] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-3: Actual machine state: WASHING +14:02:52.273 [pool-1-thread-3] ERROR com.iluwatar.balking.WashingMachine - Cannot wash if the machine has been already washing! +14:02:52.273 [pool-1-thread-1] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-1: Actual machine state: WASHING +14:02:52.273 [pool-1-thread-1] ERROR com.iluwatar.balking.WashingMachine - Cannot wash if the machine has been already washing! +14:02:52.324 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - 14: Washing completed. +``` + +## 類圖 + +![alt text](./etc/balking.png "Balking") + +## 適用性 + + +使用止步模式當 + +* 你只想在物件處於特定狀態時才對其呼叫操作 +* 物件通常僅處於容易暫時停止但狀態未知的狀態 + +## 相關模式 + +* [保護性暫掛模式](https://java-design-patterns.com/patterns/guarded-suspension/) +* [雙重檢查鎖模式](https://java-design-patterns.com/patterns/double-checked-locking/) + +## 鳴謝 + +* [Patterns in Java: A Catalog of Reusable Design Patterns Illustrated with UML, 2nd Edition, Volume 1](https://www.amazon.com/gp/product/0471227293/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0471227293&linkId=0e39a59ffaab93fb476036fecb637b99) diff --git a/localization/zh-TW/balking/etc/balking.png b/localization/zh-TW/balking/etc/balking.png new file mode 100644 index 000000000000..f409eaacbb95 Binary files /dev/null and b/localization/zh-TW/balking/etc/balking.png differ diff --git a/localization/zh-TW/bloc/README.md b/localization/zh-TW/bloc/README.md new file mode 100644 index 000000000000..a413d120b480 --- /dev/null +++ b/localization/zh-TW/bloc/README.md @@ -0,0 +1,210 @@ +--- +title: "Java Bloc 模式:簡化狀態管理" +shortTitle: Bloc +description: "了解 Bloc 模式如何管理 Java 應用程式的狀態變更,以及如何動態管理監聽者。" +category: Architectural +language: zh-TW +tag: + - Abstraction + - Data binding + - Decoupling + - Event-driven + - Presentation + - Reactive + - Reusability + - State tracking +--- + +## 又稱為 + +* 商業邏輯元件 +* 商業邏輯控制器 + +## Bloc 模式的目的 + +Bloc 模式管理物件狀態,並在狀態變更時動態通知感興趣的監聽者。它將狀態管理邏輯與應用程式其他部分分離,使程式更有組織且更具彈性。 + +## Bloc 模式的詳細說明與真實世界範例 + +真實世界範例 + +> 考慮一個數位計數器應用程式,每當計數器變更時,畫面上的多個部分都需要更新,例如顯示數值的標籤與記錄變更的活動紀錄。Bloc 模式不直接修改這些 UI 元件,而是管理計數器狀態,並通知所有已註冊的監聽者。監聽者也可以動態訂閱或取消訂閱更新。 + +簡單來說 + +> Bloc 模式管理單一狀態物件,並在狀態變更時動態通知已註冊的監聽者。 + +維基百科指出 + +> Bloc 並不是正式的四人幫設計模式,但廣泛用於狀態驅動的應用程式。它集中管理狀態,並將狀態變更傳播給已註冊的觀察者,遵循關注點分離原則。 + +序列圖 + +![Bloc 序列圖](./etc/bloc-sequence-diagram.png) + +## Java Bloc 模式的程式範例 + +本範例使用 Java 與 Swing 實作 Bloc。此模式將應用程式狀態與 UI 元件分離,以反應式、事件驅動的方式管理更新。 + +Bloc 的核心元件包括 `State` 狀態物件、負責管理與更新狀態的 `Bloc` 類別,以及用來訂閱變更的 `StateListener` 與 `ListenerManager` 介面。 + +`State` 類別代表應用程式在特定時間的資料。 + +```java +public record State(int value) {} +``` + +`ListenerManager` 介面宣告新增、移除與取得監聽者的方法。 + +```java +public interface ListenerManager { + void addListener(StateListener listener); + void removeListener(StateListener listener); + List> getListeners(); +} +``` + +`StateListener` 定義監聽者如何回應狀態變更。 + +```java +public interface StateListener { + void onStateChange(T state); +} +``` + +`Bloc` 維護目前狀態並通知監聽者。`increment` 與 `decrement` 會更新狀態並自動發出通知。 + +```java +public class Bloc implements ListenerManager { + + private State currentState; + private final List> listeners = new ArrayList<>(); + + public Bloc() { + this.currentState = new State(0); + } + + @Override + public void addListener(StateListener listener) { + listeners.add(listener); + listener.onStateChange(currentState); + } + + @Override + public void removeListener(StateListener listener) { + listeners.remove(listener); + } + + @Override + public List> getListeners() { + return Collections.unmodifiableList(listeners); + } + + private void emitState(State newState) { + currentState = newState; + for (StateListener listener : listeners) { + listener.onStateChange(currentState); + } + } + + public void increment() { + emitState(new State(currentState.value() + 1)); + } + + public void decrement() { + emitState(new State(currentState.value() - 1)); + } +} +``` + +以下類別展示如何將 Bloc 整合到簡單的 Swing GUI,包含計數器、修改狀態的按鈕,以及動態啟用或停用監聽者的切換按鈕。 + +```java +public class Main { + public static void main(String[] args) { + BlocUi blocUi = new BlocUi(); + blocUi.createAndShowUi(); + } +} + +public class BlocUi { + public void createAndShowUi() { + final Bloc bloc = new Bloc(); + JFrame frame = new JFrame("BloC example"); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.setSize(400, 300); + JLabel counterLabel = new JLabel("Counter: 0", SwingConstants.CENTER); + JButton decrementButton = new JButton("Decrement"); + JButton toggleListenerButton = new JButton("Disable Listener"); + JButton incrementButton = new JButton("Increment"); + frame.setLayout(new BorderLayout()); + frame.add(counterLabel, BorderLayout.CENTER); + frame.add(incrementButton, BorderLayout.NORTH); + frame.add(decrementButton, BorderLayout.SOUTH); + frame.add(toggleListenerButton, BorderLayout.EAST); + StateListener stateListener = state -> counterLabel.setText("Counter: " + state.value()); + bloc.addListener(stateListener); + toggleListenerButton.addActionListener(e -> { + if (bloc.getListeners().contains(stateListener)) { + bloc.removeListener(stateListener); + toggleListenerButton.setText("Enable Listener"); + } else { + bloc.addListener(stateListener); + toggleListenerButton.setText("Disable Listener"); + } + }); + incrementButton.addActionListener(e -> bloc.increment()); + decrementButton.addActionListener(e -> bloc.decrement()); + frame.setVisible(true); + } +} +``` + +### 程式輸出 + +* **增加**:`Counter: 1` +* **減少**:`Counter: 0` +* **動態切換監聽者**:停用後計數器停止更新,重新啟用後恢復更新。 + +## 何時使用 Bloc 模式 + +* 想在 Java 應用程式中清楚分離商業邏輯與 UI。 +* 需要依照狀態變更反應式更新 UI。 +* 想避免控制器或呈現器直接耦合資料操作。 +* 多個 UI 元素需要存取相同的商業邏輯。 + +## Bloc 模式的實際應用 + +* 需要即時 UI 更新的 Java 桌面應用程式。 +* 將服務層與呈現層分離的後端 Java 框架。 +* 必須維持一致邏輯、不受 UI 技術影響的跨平台應用程式。 + +## Bloc 模式的優點與取捨 + +優點: + +* 移除直接商業邏輯,簡化 UI 元件。 +* 隔離狀態與行為,提高可測試性。 +* 集中資料流,鼓勵程式碼重複使用。 +* 清楚分離關注點,提高可維護性。 + +取捨: + +* 管理串流或觀察者時可能增加樣板程式碼。 +* 必須妥善設計,避免形成單一龐大的元件。 +* 需要一致的反應式程式設計實務才能發揮效果。 + +## 相關模式 + +* [觀察者](https://java-design-patterns.com/patterns/observer/):Bloc 是觀察者模式的專門實作。 +* [中介者](https://java-design-patterns.com/patterns/mediator/):透過中央元件協調多個物件間的互動。 +* [MVC](https://java-design-patterns.com/patterns/model-view-controller/):同樣強調分離不同層的關注點。 + +## 參考資料與致謝 + +* [Bloc 架構(bloclibrary.dev)](https://bloclibrary.dev/architecture/) +* [Clean Architecture: A Craftsman's Guide to Software Structure and Design](https://amzn.to/3UoKkaR) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Event-Driven Programming in Java(Oracle)](https://www.oracle.com/java/) +* [Java Swing 文件(Oracle)](https://docs.oracle.com/javase/tutorial/uiswing/) \ No newline at end of file diff --git a/localization/zh-TW/bridge/README.md b/localization/zh-TW/bridge/README.md new file mode 100644 index 000000000000..199289460d17 --- /dev/null +++ b/localization/zh-TW/bridge/README.md @@ -0,0 +1,204 @@ +--- +title: Bridge +shortTitle: Bridge +category: Structural +language: zh +tag: + - Gang of Four +--- + +## 又被稱為 + +手柄/身體模式 + +## 目的 + +將抽象與其實現分離,以便二者可以獨立變化。 + +## 解釋 + +真實世界例子 + +> 考慮一下你擁有一種具有不同附魔的武器,並且應該允許將具有不同附魔的不同武器混合使用。 你會怎麼做? 為每個附魔建立每種武器的多個副本,還是隻是建立單獨的附魔並根據需要為武器設定它? 橋接模式使你可以進行第二次操作。 + +通俗的說 + +> 橋接模式是一個更推薦組合而不是繼承的模式。將實現細節從一個層次結構推送到具有單獨層次結構的另一個物件。 + +維基百科說 + +> 橋接模式是軟體工程中使用的一種設計模式,旨在“將抽象與其實現分離,從而使兩者可以獨立變化” + +**程式示例** + +翻譯一下上面的武器示例。下面我們有武器的類層級: + +```java +public interface Weapon { + void wield(); + void swing(); + void unwield(); + Enchantment getEnchantment(); +} + +public class Sword implements Weapon { + + private final Enchantment enchantment; + + public Sword(Enchantment enchantment) { + this.enchantment = enchantment; + } + + @Override + public void wield() { + LOGGER.info("The sword is wielded."); + enchantment.onActivate(); + } + + @Override + public void swing() { + LOGGER.info("The sword is swinged."); + enchantment.apply(); + } + + @Override + public void unwield() { + LOGGER.info("The sword is unwielded."); + enchantment.onDeactivate(); + } + + @Override + public Enchantment getEnchantment() { + return enchantment; + } +} + +public class Hammer implements Weapon { + + private final Enchantment enchantment; + + public Hammer(Enchantment enchantment) { + this.enchantment = enchantment; + } + + @Override + public void wield() { + LOGGER.info("The hammer is wielded."); + enchantment.onActivate(); + } + + @Override + public void swing() { + LOGGER.info("The hammer is swinged."); + enchantment.apply(); + } + + @Override + public void unwield() { + LOGGER.info("The hammer is unwielded."); + enchantment.onDeactivate(); + } + + @Override + public Enchantment getEnchantment() { + return enchantment; + } +} +``` + +這裡是單獨的附魔類結構: + +```java +public interface Enchantment { + void onActivate(); + void apply(); + void onDeactivate(); +} + +public class FlyingEnchantment implements Enchantment { + + @Override + public void onActivate() { + LOGGER.info("The item begins to glow faintly."); + } + + @Override + public void apply() { + LOGGER.info("The item flies and strikes the enemies finally returning to owner's hand."); + } + + @Override + public void onDeactivate() { + LOGGER.info("The item's glow fades."); + } +} + +public class SoulEatingEnchantment implements Enchantment { + + @Override + public void onActivate() { + LOGGER.info("The item spreads bloodlust."); + } + + @Override + public void apply() { + LOGGER.info("The item eats the soul of enemies."); + } + + @Override + public void onDeactivate() { + LOGGER.info("Bloodlust slowly disappears."); + } +} +``` + +這裡是兩種層次結構的實踐: + +```java +var enchantedSword = new Sword(new SoulEatingEnchantment()); +enchantedSword.wield(); +enchantedSword.swing(); +enchantedSword.unwield(); +// The sword is wielded. +// The item spreads bloodlust. +// The sword is swinged. +// The item eats the soul of enemies. +// The sword is unwielded. +// Bloodlust slowly disappears. + +var hammer = new Hammer(new FlyingEnchantment()); +hammer.wield(); +hammer.swing(); +hammer.unwield(); +// The hammer is wielded. +// The item begins to glow faintly. +// The hammer is swinged. +// The item flies and strikes the enemies finally returning to owner's hand. +// The hammer is unwielded. +// The item's glow fades. +``` + + + +## 類圖 + +![alt text](./etc/bridge.urm.png "Bridge class diagram") + +## 適用性 + +使用橋接模式當 + +* 你想永久性的避免抽象和他的實現之間的繫結。有可能是這種情況,當實現需要被選擇或者在執行時切換。 +* 抽象和他們的實現應該能透過寫子類來擴充套件。這種情況下,橋接模式讓你可以組合不同的抽象和實現並獨立的擴充套件他們。 +* 對抽象的實現的改動應當不會對客戶產生影響;也就是說,他們的程式碼不必重新編譯。 +* 你有種類繁多的類。這樣的類層次結構表明需要將一個物件分為兩部分。Rumbaugh 使用術語“巢狀歸納”來指代這種類層次結構。 +* 你想在多個物件間分享一種實現(可能使用引用計數),這個事實應該對客戶隱藏。一個簡單的示例是Coplien的String類,其中多個物件可以共享同一字串表示形式 + +## 教程 + +* [Bridge Pattern Tutorial](https://www.journaldev.com/1491/bridge-design-pattern-java) + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/localization/zh-TW/bridge/etc/bridge.urm.png b/localization/zh-TW/bridge/etc/bridge.urm.png new file mode 100644 index 000000000000..785585bf8163 Binary files /dev/null and b/localization/zh-TW/bridge/etc/bridge.urm.png differ diff --git a/localization/zh-TW/builder/README.md b/localization/zh-TW/builder/README.md new file mode 100644 index 000000000000..d2fb01d6597f --- /dev/null +++ b/localization/zh-TW/builder/README.md @@ -0,0 +1,138 @@ +--- +title: Builder +shortTitle: Builder +category: Creational +language: zh +tag: + - Gang of Four + +--- + +## 目的 + +將複雜物件的構造與其表示分開,以便同一構造過程可以建立不同的表示。 + +## 解釋 + +現實世界例子 + +> 想象一個角色扮演遊戲的角色生成器。最簡單的選擇是讓計算機為你建立角色。但是如果你想選擇一些像專業,性別,髮色等角色細節時,這個角色生成就變成了一個漸進的過程。當所有選擇完成時,該過程也將完成。 + +用通俗的話說 + +> 允許你建立不同口味的物件同時避免構造器汙染。當一個物件可能有幾種口味,或者一個物件的建立涉及到很多步驟時會很有用。 + +維基百科說 + +> 建造者模式是一種物件建立的軟體設計模式,旨在為伸縮構造器反模式尋找一個解決方案。 + +說了這麼多,讓我補充一下什麼是伸縮建構函式反模式。我們肯定都見過像下面這樣的構造器: + +```java +public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) { +} +``` + +就像你看到的構造器引數的數量很快就會失控同時引數的排列方式可能變得難以理解。另外,如果你將來希望新增更多選項,則此引數列表可能會繼續增長。這就被稱伸縮構造器反模式。 + +**程式設計示例** + +明智的選擇是使用建造者模式。首先我們有一個英雄要建立。 + +```java +public final class Hero { + private final Profession profession; + private final String name; + private final HairType hairType; + private final HairColor hairColor; + private final Armor armor; + private final Weapon weapon; + + private Hero(Builder builder) { + this.profession = builder.profession; + this.name = builder.name; + this.hairColor = builder.hairColor; + this.hairType = builder.hairType; + this.weapon = builder.weapon; + this.armor = builder.armor; + } +} +``` + +然後我們有建立者 + +```java + public static class Builder { + private final Profession profession; + private final String name; + private HairType hairType; + private HairColor hairColor; + private Armor armor; + private Weapon weapon; + + public Builder(Profession profession, String name) { + if (profession == null || name == null) { + throw new IllegalArgumentException("profession and name can not be null"); + } + this.profession = profession; + this.name = name; + } + + public Builder withHairType(HairType hairType) { + this.hairType = hairType; + return this; + } + + public Builder withHairColor(HairColor hairColor) { + this.hairColor = hairColor; + return this; + } + + public Builder withArmor(Armor armor) { + this.armor = armor; + return this; + } + + public Builder withWeapon(Weapon weapon) { + this.weapon = weapon; + return this; + } + + public Hero build() { + return new Hero(this); + } + } +``` + +然後可以這樣使用 + +```java +var mage = new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER).build(); +``` + +## 類圖 + +![alt text](./etc/builder.urm.png "Builder class diagram") + +## 適用性 + +使用建造者模式當 + +* 建立複雜物件的演算法應獨立於組成物件的零件及其組裝方式 +* 構造過程必須允許所構造的物件具有不同的表示形式 + +## Java世界例子 + +* [java.lang.StringBuilder](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html) +* [java.nio.ByteBuffer](http://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html#put-byte-) as well as similar buffers such as FloatBuffer, IntBuffer and so on. +* [java.lang.StringBuffer](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuffer.html#append-boolean-) +* All implementations of [java.lang.Appendable](http://docs.oracle.com/javase/8/docs/api/java/lang/Appendable.html) +* [Apache Camel builders](https://github.com/apache/camel/tree/0e195428ee04531be27a0b659005e3aa8d159d23/camel-core/src/main/java/org/apache/camel/builder) +* [Apache Commons Option.Builder](https://commons.apache.org/proper/commons-cli/apidocs/org/apache/commons/cli/Option.Builder.html) + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Effective Java](https://www.amazon.com/gp/product/0134685997/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0134685997&linkCode=as2&tag=javadesignpat-20&linkId=4e349f4b3ff8c50123f8147c828e53eb) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) \ No newline at end of file diff --git a/localization/zh-TW/builder/etc/builder.urm.png b/localization/zh-TW/builder/etc/builder.urm.png new file mode 100644 index 000000000000..d77808d36097 Binary files /dev/null and b/localization/zh-TW/builder/etc/builder.urm.png differ diff --git a/localization/zh-TW/business-delegate/README.md b/localization/zh-TW/business-delegate/README.md new file mode 100644 index 000000000000..9de7f2ee20ae --- /dev/null +++ b/localization/zh-TW/business-delegate/README.md @@ -0,0 +1,154 @@ +--- +title: Business Delegate +shortTitle: Business Delegate +category: Structural +language: zh +tag: + - Decoupling +--- + +## 意圖 + +業務委託模式在表示層和業務層之間新增了一個抽象層。 透過使用該模式,我們獲得了各層之間的鬆散耦合,並封裝了有關如何定位,連線到組成應用程式的業務物件以及與之互動的邏輯。 + +## 解釋 + +真實世界例子 + +> 手機應用程式承諾將現有的任何電影流式傳輸到你的手機。它捕獲使用者的搜尋字串,並將其傳遞給業務委託層。業務委託層選擇最合適的影片流服務,然後從那裡播放影片。 + +通俗的說 + +> 業務委託模式在表示層和業務層之間新增了一個抽象層。 + +維基百科說 + +> 業務委託是一種Java EE設計模式。 該模式旨在減少業務服務與連線的表示層之間的耦合,並隱藏服務的實現細節(包括EJB體系結構的查詢和可訪問性)。 業務代表充當介面卡,以從表示層呼叫業務物件。 + +**程式示例** + +首先,我們有影片流服務的抽象類和一些它的實現。 + +```java +public interface VideoStreamingService { + void doProcessing(); +} + +@Slf4j +public class NetflixService implements VideoStreamingService { + @Override + public void doProcessing() { + LOGGER.info("NetflixService is now processing"); + } +} + +@Slf4j +public class YouTubeService implements VideoStreamingService { + @Override + public void doProcessing() { + LOGGER.info("YouTubeService is now processing"); + } +} +``` + +然後我們有一個查詢服務來決定我們使用哪個影片流服務。 + +```java +@Setter +public class BusinessLookup { + + private NetflixService netflixService; + private YouTubeService youTubeService; + + public VideoStreamingService getBusinessService(String movie) { + if (movie.toLowerCase(Locale.ROOT).contains("die hard")) { + return netflixService; + } else { + return youTubeService; + } + } +} +``` + +業務委託類使用業務查詢服務將電影播放請求路由到合適的影片流服務。 + +```java +@Setter +public class BusinessDelegate { + + private BusinessLookup lookupService; + + public void playbackMovie(String movie) { + VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie); + videoStreamingService.doProcessing(); + } +} +``` + +移動客戶端利用業務委託來呼叫業務層。 + +```java +public class MobileClient { + + private final BusinessDelegate businessDelegate; + + public MobileClient(BusinessDelegate businessDelegate) { + this.businessDelegate = businessDelegate; + } + + public void playbackMovie(String movie) { + businessDelegate.playbackMovie(movie); + } +} +``` + +最後我們展示完整示例。 + +```java + public static void main(String[] args) { + + // prepare the objects + var businessDelegate = new BusinessDelegate(); + var businessLookup = new BusinessLookup(); + businessLookup.setNetflixService(new NetflixService()); + businessLookup.setYouTubeService(new YouTubeService()); + businessDelegate.setLookupService(businessLookup); + + // create the client and use the business delegate + var client = new MobileClient(businessDelegate); + client.playbackMovie("Die Hard 2"); + client.playbackMovie("Maradona: The Greatest Ever"); + } +``` + +這是控制檯的輸出。 + +``` +21:15:33.790 [main] INFO com.iluwatar.business.delegate.NetflixService - NetflixService is now processing +21:15:33.794 [main] INFO com.iluwatar.business.delegate.YouTubeService - YouTubeService is now processing +``` + +## 類圖 + +![alt text](./etc/business-delegate.urm.png "Business Delegate") + +## 相關模式 + +* [服務定位器模式](https://java-design-patterns.com/patterns/service-locator/) + +## 適用性 + +使用業務委託模式當 + +* 你希望表示層和業務層之間的鬆散耦合 +* 你想編排對多個業務服務的呼叫 +* 你希望封裝查詢服務和服務呼叫 + +## 教程 + +* [Business Delegate Pattern at TutorialsPoint](https://www.tutorialspoint.com/design_pattern/business_delegate_pattern.htm) + +## 鳴謝 + +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +* [Core J2EE Patterns: Best Practices and Design Strategies](https://www.amazon.com/gp/product/0130648841/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0130648841&linkId=a0100de2b28c71ede8db1757fb2b5947) diff --git a/localization/zh-TW/business-delegate/etc/business-delegate.urm.png b/localization/zh-TW/business-delegate/etc/business-delegate.urm.png new file mode 100644 index 000000000000..4dca6c263b99 Binary files /dev/null and b/localization/zh-TW/business-delegate/etc/business-delegate.urm.png differ diff --git a/localization/zh-TW/bytecode/README.md b/localization/zh-TW/bytecode/README.md new file mode 100644 index 000000000000..4704228866c7 --- /dev/null +++ b/localization/zh-TW/bytecode/README.md @@ -0,0 +1,233 @@ +--- +title: Bytecode +shortTitle: Bytecode +category: Behavioral +language: zh +tag: + - Game programming +--- + +## 意圖 + +允許編碼行為作為虛擬機器的指令。 + +## 解釋 + +真實世界例子 + +> 一個團隊正在開發一款新的巫師對戰遊戲。巫師的行為需要經過精心的調整和上百次的遊玩測試。每次當遊戲設計師想改變巫師行為時都讓程式設計師去修改程式碼這是不妥的,所以巫師行為以資料驅動的虛擬機器方式實現。 + +通俗地說 + +> 位元組碼模式支援由資料而不是程式碼驅動的行為。 + +[Gameprogrammingpatterns.com](https://gameprogrammingpatterns.com/bytecode.html) 中做了如下闡述: + +> 指令集定義了可以執行的低階操作。一系列指令被編碼為位元組序列。虛擬機器一次一條地執行這些指令,中間的值用棧處理。透過組合指令,可以定義複雜的高階行為。 + +**程式示例** + +其中最重要的遊戲物件是`巫師`類。 + +```java +@AllArgsConstructor +@Setter +@Getter +@Slf4j +public class Wizard { + + private int health; + private int agility; + private int wisdom; + private int numberOfPlayedSounds; + private int numberOfSpawnedParticles; + + public void playSound() { + LOGGER.info("Playing sound"); + numberOfPlayedSounds++; + } + + public void spawnParticles() { + LOGGER.info("Spawning particles"); + numberOfSpawnedParticles++; + } +} +``` + +下面我們展示虛擬機器可用的指令。每個指令對於如何操作棧中的資料都有自己的語義。例如,增加指令,其取得棧頂的兩個元素並把結果壓入棧中。 + +```java +@AllArgsConstructor +@Getter +public enum Instruction { + + LITERAL(1), // e.g. "LITERAL 0", push 0 to stack + SET_HEALTH(2), // e.g. "SET_HEALTH", pop health and wizard number, call set health + SET_WISDOM(3), // e.g. "SET_WISDOM", pop wisdom and wizard number, call set wisdom + SET_AGILITY(4), // e.g. "SET_AGILITY", pop agility and wizard number, call set agility + PLAY_SOUND(5), // e.g. "PLAY_SOUND", pop value as wizard number, call play sound + SPAWN_PARTICLES(6), // e.g. "SPAWN_PARTICLES", pop value as wizard number, call spawn particles + GET_HEALTH(7), // e.g. "GET_HEALTH", pop value as wizard number, push wizard's health + GET_AGILITY(8), // e.g. "GET_AGILITY", pop value as wizard number, push wizard's agility + GET_WISDOM(9), // e.g. "GET_WISDOM", pop value as wizard number, push wizard's wisdom + ADD(10), // e.g. "ADD", pop 2 values, push their sum + DIVIDE(11); // e.g. "DIVIDE", pop 2 values, push their division + // ... +} +``` + +我們示例的核心是`虛擬機器`類。 它將指令作為輸入並執行它們以提供遊戲物件行為。 + +```java +@Getter +@Slf4j +public class VirtualMachine { + + private final Stack stack = new Stack<>(); + + private final Wizard[] wizards = new Wizard[2]; + + public VirtualMachine() { + wizards[0] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32), + 0, 0); + wizards[1] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32), + 0, 0); + } + + public VirtualMachine(Wizard wizard1, Wizard wizard2) { + wizards[0] = wizard1; + wizards[1] = wizard2; + } + + public void execute(int[] bytecode) { + for (var i = 0; i < bytecode.length; i++) { + Instruction instruction = Instruction.getInstruction(bytecode[i]); + switch (instruction) { + case LITERAL: + // Read the next byte from the bytecode. + int value = bytecode[++i]; + // Push the next value to stack + stack.push(value); + break; + case SET_AGILITY: + var amount = stack.pop(); + var wizard = stack.pop(); + setAgility(wizard, amount); + break; + case SET_WISDOM: + amount = stack.pop(); + wizard = stack.pop(); + setWisdom(wizard, amount); + break; + case SET_HEALTH: + amount = stack.pop(); + wizard = stack.pop(); + setHealth(wizard, amount); + break; + case GET_HEALTH: + wizard = stack.pop(); + stack.push(getHealth(wizard)); + break; + case GET_AGILITY: + wizard = stack.pop(); + stack.push(getAgility(wizard)); + break; + case GET_WISDOM: + wizard = stack.pop(); + stack.push(getWisdom(wizard)); + break; + case ADD: + var a = stack.pop(); + var b = stack.pop(); + stack.push(a + b); + break; + case DIVIDE: + a = stack.pop(); + b = stack.pop(); + stack.push(b / a); + break; + case PLAY_SOUND: + wizard = stack.pop(); + getWizards()[wizard].playSound(); + break; + case SPAWN_PARTICLES: + wizard = stack.pop(); + getWizards()[wizard].spawnParticles(); + break; + default: + throw new IllegalArgumentException("Invalid instruction value"); + } + LOGGER.info("Executed " + instruction.name() + ", Stack contains " + getStack()); + } + } + + public void setHealth(int wizard, int amount) { + wizards[wizard].setHealth(amount); + } + // other setters -> + // ... +} +``` + +現在我們可以展示使用虛擬機器的完整示例。 + +```java + public static void main(String[] args) { + + var vm = new VirtualMachine( + new Wizard(45, 7, 11, 0, 0), + new Wizard(36, 18, 8, 0, 0)); + + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0")); + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0")); + vm.execute(InstructionConverterUtil.convertToByteCode("GET_HEALTH")); + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0")); + vm.execute(InstructionConverterUtil.convertToByteCode("GET_AGILITY")); + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0")); + vm.execute(InstructionConverterUtil.convertToByteCode("GET_WISDOM")); + vm.execute(InstructionConverterUtil.convertToByteCode("ADD")); + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 2")); + vm.execute(InstructionConverterUtil.convertToByteCode("DIVIDE")); + vm.execute(InstructionConverterUtil.convertToByteCode("ADD")); + vm.execute(InstructionConverterUtil.convertToByteCode("SET_HEALTH")); + } +``` + +下面是控制檯輸出。 + +``` +16:20:10.193 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0] +16:20:10.196 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 0] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_HEALTH, Stack contains [0, 45] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 0] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_AGILITY, Stack contains [0, 45, 7] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 7, 0] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_WISDOM, Stack contains [0, 45, 7, 11] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 45, 18] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 18, 2] +16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed DIVIDE, Stack contains [0, 45, 9] +16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 54] +16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed SET_HEALTH, Stack contains [] +``` + +## 類圖 + +![alt text](./etc/bytecode.urm.png "Bytecode class diagram") + +## 適用性 + + + +當你需要定義很多行為並且遊戲的實現語言不合適時,請使用位元組碼模式,因為: + +* 它的等級太低,使得程式設計變得乏味或容易出錯。 +* 由於編譯時間慢或其他工具問題,迭代它需要很長時間。 +* 它有太多的信任。 如果你想確保定義的行為不會破壞遊戲,你需要將其與程式碼庫的其餘部分進行沙箱化。 + +## 相關模式 + +* [Interpreter](https://java-design-patterns.com/patterns/interpreter/) + +## 鳴謝 + +* [Game programming patterns](http://gameprogrammingpatterns.com/bytecode.html) diff --git a/localization/zh-TW/bytecode/etc/bytecode.urm.png b/localization/zh-TW/bytecode/etc/bytecode.urm.png new file mode 100644 index 000000000000..51335fa0a4b4 Binary files /dev/null and b/localization/zh-TW/bytecode/etc/bytecode.urm.png differ diff --git a/localization/zh-TW/caching/README.md b/localization/zh-TW/caching/README.md new file mode 100644 index 000000000000..c32ecc8570d1 --- /dev/null +++ b/localization/zh-TW/caching/README.md @@ -0,0 +1,26 @@ +--- +title: Caching +shortTitle: Caching +category: Behavioral +language: zh +tag: + - Performance + - Cloud distributed +--- + +## 目的 +為了避免昂貴的資源重新獲取,方法是在資源使用後不立即釋放資源。資源保留其身份,保留在某些快速訪問的儲存中,並被重新使用,以避免再次獲取它們。 + +## 類圖 +![alt text](./etc/caching.png "Caching") + +## 適用性 +在以下情況下使用快取模式 + +* 重複獲取,初始化和釋放同一資源會導致不必要的效能開銷。 + +## 鳴謝 + +* [Write-through, write-around, write-back: Cache explained](http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained) +* [Read-Through, Write-Through, Write-Behind, and Refresh-Ahead Caching](https://docs.oracle.com/cd/E15357_01/coh.360/e15723/cache_rtwtwbra.htm#COHDG5177) +* [Cache-Aside pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/cache-aside) diff --git a/localization/zh-TW/caching/etc/caching.png b/localization/zh-TW/caching/etc/caching.png new file mode 100644 index 000000000000..b6ed703ab8b7 Binary files /dev/null and b/localization/zh-TW/caching/etc/caching.png differ diff --git a/localization/zh-TW/callback/README.md b/localization/zh-TW/callback/README.md new file mode 100644 index 000000000000..c7b56306e5e6 --- /dev/null +++ b/localization/zh-TW/callback/README.md @@ -0,0 +1,78 @@ +--- +title: Callback +shortTitle: Callback +category: Idiom +language: zh +tag: + - Reactive +--- + +## 目的 +回撥是一部分被當為引數來傳遞給其他程式碼的可執行程式碼,接收方的程式碼可以在一些方便的時候來呼叫它。 + +## 解釋 + +真實世界例子 + +> 我們需要被通知當執行的任務結束時。我們為呼叫者傳遞一個回撥方法然後等它呼叫通知我們。 + +通俗的講 + + +> 回撥是一個用來傳遞給呼叫者的方法,它將在定義的時刻被呼叫。 + +維基百科說 + +> 在計算機程式設計中,回撥又被稱為“稍後呼叫”函式,可以是任何可執行的程式碼用來作為引數傳遞給其他程式碼;其它程式碼被期望在給定時間內呼叫回撥方法。 + +**程式設計示例** + +回撥是一個只有一個方法的簡單介面。 + +```java +public interface Callback { + + void call(); +} +``` + +下面我們定義一個任務它將在任務執行完成後執行回撥。 + +```java +public abstract class Task { + + final void executeWith(Callback callback) { + execute(); + Optional.ofNullable(callback).ifPresent(Callback::call); + } + + public abstract void execute(); +} + +public final class SimpleTask extends Task { + + private static final Logger LOGGER = getLogger(SimpleTask.class); + + @Override + public void execute() { + LOGGER.info("Perform some important activity and after call the callback method."); + } +} +``` + +最後這裡是我們如何執行一個任務然後接收一個回撥當它完成時。 + +```java + var task = new SimpleTask(); + task.executeWith(() -> LOGGER.info("I'm done now.")); +``` +## 類圖 +![alt text](./etc/callback.png "Callback") + +## 適用性 +使用回撥模式當 +* 當一些同步或非同步架構動作必須在一些定義好的活動執行後執行時。 + +## Java例子 + +* [CyclicBarrier](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CyclicBarrier.html#CyclicBarrier%28int,%20java.lang.Runnable%29) 建構函式可以接受回撥,該回撥將在每次障礙被觸發時觸發。 diff --git a/localization/zh-TW/callback/etc/callback.png b/localization/zh-TW/callback/etc/callback.png new file mode 100644 index 000000000000..7b499f79fcaa Binary files /dev/null and b/localization/zh-TW/callback/etc/callback.png differ diff --git a/localization/zh-TW/chain-of-responsibility/README.md b/localization/zh-TW/chain-of-responsibility/README.md new file mode 100644 index 000000000000..4b3090b5d716 --- /dev/null +++ b/localization/zh-TW/chain-of-responsibility/README.md @@ -0,0 +1,183 @@ +--- +title: "Java 責任鏈模式:建立可靠的請求處理機制" +shortTitle: 責任鏈 +description: "透過真實世界範例、程式碼片段與類別圖了解 Java 責任鏈設計模式。" +category: Behavioral +language: zh-TW +tag: + - Decoupling + - Event-driven + - Gang of Four + - Messaging +--- + +## 又稱為 + +* 命令鏈 +* 物件鏈 +* 責任鏈 + +## 責任鏈設計模式的目的 + +責任鏈模式是一種行為型設計模式,透過讓多個物件都有機會處理請求,將請求發送者與接收者解耦。接收者會串成鏈,請求沿著鏈傳遞,直到某個物件處理它為止。 + +## 責任鏈模式的詳細說明與真實世界範例 + +真實世界範例 + +> 技術支援客服中心是責任鏈模式的例子。每個支援層級都是鏈中的處理者。客戶提出問題時,前線人員先接聽;簡單問題由前線處理,複雜問題則轉交第二線技術人員。這個過程會逐級升級,直到由能解決問題的專家處理。每個層級都是一個處理者,請求會沿鏈傳遞,因此請求不需要知道具體接收者。 + +簡單來說 + +> 建立一串物件,請求從一端進入,依序傳遞到其他物件,直到找到適合的處理者。 + +維基百科指出 + +> 責任鏈模式由命令物件來源與一系列處理物件組成。每個處理物件包含能處理的命令類型邏輯,其餘命令則傳給鏈中的下一個處理物件。 + +流程圖 + +![責任鏈流程圖](./etc/chain-of-responsibility-flowchart.png) + +## Java 責任鏈模式的程式範例 + +本範例中,Orc King 發出命令,由代表責任鏈的命令鏈處理。指揮官、軍官與士兵依序組成責任鏈。 + +首先建立 `Request` 類別: + +```java +@Getter +public class Request { + private final RequestType requestType; + private final String requestDescription; + private boolean handled; + + public Request(final RequestType requestType, final String requestDescription) { + this.requestType = Objects.requireNonNull(requestType); + this.requestDescription = Objects.requireNonNull(requestDescription); + } + + public void markHandled() { this.handled = true; } + + @Override + public String toString() { return getRequestDescription(); } +} + +public enum RequestType { + DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX +} +``` + +接著建立 `RequestHandler` 階層: + +```java +public interface RequestHandler { + boolean canHandleRequest(Request req); + int getPriority(); + void handle(Request req); + String name(); +} + +@Slf4j +public class OrcCommander implements RequestHandler { + @Override + public boolean canHandleRequest(Request req) { + return req.getRequestType() == RequestType.DEFEND_CASTLE; + } + + @Override public int getPriority() { return 2; } + + @Override + public void handle(Request req) { + req.markHandled(); + LOGGER.info("{} handling request \"{}\"", name(), req); + } + + @Override public String name() { return "Orc commander"; } +} +// OrcOfficer 與 OrcSoldier 的定義方式與 OrcCommander 類似。 +``` + +`OrcKing` 發出命令並建立責任鏈: + +```java +public class OrcKing { + private List handlers; + + public OrcKing() { buildChain(); } + + private void buildChain() { + handlers = Arrays.asList(new OrcCommander(), new OrcOfficer(), new OrcSoldier()); + } + + public void makeRequest(Request req) { + handlers.stream() + .sorted(Comparator.comparing(RequestHandler::getPriority)) + .filter(handler -> handler.canHandleRequest(req)) + .findFirst() + .ifPresent(handler -> handler.handle(req)); + } +} +``` + +責任鏈開始運作: + +```java +public static void main(String[] args) { + var king = new OrcKing(); + king.makeRequest(new Request(RequestType.DEFEND_CASTLE, "defend castle")); + king.makeRequest(new Request(RequestType.TORTURE_PRISONER, "torture prisoner")); + king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax")); +} +``` + +主控台輸出: + +``` +Orc commander handling request "defend castle" +Orc officer handling request "torture prisoner" +Orc soldier handling request "collect tax" +``` + +## 何時在 Java 中使用責任鏈模式 + +* 可能有多個物件能處理請求,而處理者事先未知,需要自動找出處理者。 +* 想向多個物件之一發出請求,但不想明確指定接收者。 +* 能處理請求的物件集合需要動態指定。 + +## 責任鏈模式的實際應用 + +* GUI 框架中的事件冒泡,事件可能在 UI 元件階層的多個層級處理。 +* 請求會經過一系列處理物件的中介軟體框架。 +* 訊息可經過一連串記錄器、由各記錄器以不同方式處理的記錄框架。 +* [java.util.logging.Logger#log()](http://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html#log%28java.util.logging.Level,%20java.lang.String%29) +* [Apache Commons Chain](https://commons.apache.org/proper/commons-chain/index.html) +* [javax.servlet.Filter#doFilter()](http://docs.oracle.com/javaee/7/api/javax/servlet/Filter.html#doFilter-javax.servlet.ServletRequest-javax.servlet.ServletResponse-javax.servlet.FilterChain-) + +## 責任鏈模式的優點與取捨 + +優點: + +* 降低耦合,發送者不需知道實際處理請求的處理者。 +* 指派物件責任更有彈性,可以透過調整鏈的成員與順序變更處理責任。 +* 沒有具體處理者時可以設定預設處理者。 + +取捨: + +* 鏈很長或很複雜時,流程可能難以除錯與理解。 +* 如果鏈沒有全域處理者,請求可能無法處理。 +* 找到適合處理者前可能經過多個處理者,造成效能問題。 + +## 相關 Java 設計模式 + +* [命令](https://java-design-patterns.com/patterns/command/):可將請求封裝成物件,再沿鏈傳遞。 +* [組合](https://java-design-patterns.com/patterns/composite/):責任鏈常與組合模式一起使用。 +* [裝飾者](https://java-design-patterns.com/patterns/decorator/):裝飾者可像責任鏈中的責任一樣串接。 + +## 參考資料與致謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3PAJUg5) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) +* [Pattern languages of program design 3](https://amzn.to/4a4NxTH) \ No newline at end of file diff --git a/localization/zh-TW/chain-of-responsibility/etc/chain-of-responsibility-flowchart.png b/localization/zh-TW/chain-of-responsibility/etc/chain-of-responsibility-flowchart.png new file mode 100644 index 000000000000..875507374086 Binary files /dev/null and b/localization/zh-TW/chain-of-responsibility/etc/chain-of-responsibility-flowchart.png differ diff --git a/localization/zh-TW/chain/README.md b/localization/zh-TW/chain/README.md new file mode 100644 index 000000000000..df49eefadbc6 --- /dev/null +++ b/localization/zh-TW/chain/README.md @@ -0,0 +1,158 @@ +--- +title: Chain of responsibility +shortTitle: Chain of responsibility +category: Behavioral +language: zh +tag: + - Gang of Four +--- + +## 目的 +透過給多個物件一個處理請求的機會,避免請求的傳送者和它的接收者耦合。串聯接收物件並在鏈條中傳遞請求直到一個物件處理它。 + +## 解釋 + +真實世界例子 + +> 獸王大聲命令他的軍隊。最近響應的是指揮官,然後是軍官,然後是士兵。指揮官,軍官,士兵這裡就形成了一個責任鏈。 + +通俗的說 + +> 它幫助構建一串物件。請求從一個物件中進入並結束然後進入到一個個物件中直到找到合適的處理器。 + +維基百科說 + +> 在物件導向設計中,責任鏈模式是一種由源命令物件和一系列處理物件組成的設計模式。每個處理物件包含了其定義的可處理的命令物件型別的邏輯。剩下的會傳遞給鏈條中的下一個處理物件。 + +**程式示例** + +用上面的獸人來翻譯我們的示例。首先我們有請求類 + +```java +public class Request { + + private final RequestType requestType; + private final String requestDescription; + private boolean handled; + + public Request(final RequestType requestType, final String requestDescription) { + this.requestType = Objects.requireNonNull(requestType); + this.requestDescription = Objects.requireNonNull(requestDescription); + } + + public String getRequestDescription() { return requestDescription; } + + public RequestType getRequestType() { return requestType; } + + public void markHandled() { this.handled = true; } + + public boolean isHandled() { return this.handled; } + + @Override + public String toString() { return getRequestDescription(); } +} + +public enum RequestType { + DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX +} +``` + +然後是請求處理器的層次結構 + +```java +@Slf4j +public abstract class RequestHandler { + private final RequestHandler next; + + public RequestHandler(RequestHandler next) { + this.next = next; + } + + public void handleRequest(Request req) { + if (next != null) { + next.handleRequest(req); + } + } + + protected void printHandling(Request req) { + LOGGER.info("{} handling request \"{}\"", this, req); + } + + @Override + public abstract String toString(); +} + +public class OrcCommander extends RequestHandler { + public OrcCommander(RequestHandler handler) { + super(handler); + } + + @Override + public void handleRequest(Request req) { + if (req.getRequestType().equals(RequestType.DEFEND_CASTLE)) { + printHandling(req); + req.markHandled(); + } else { + super.handleRequest(req); + } + } + + @Override + public String toString() { + return "Orc commander"; + } +} + +// OrcOfficer和OrcSoldier的定義與OrcCommander類似 + +``` + +然後我們有獸王下達命令並形成鏈條 + +```java +public class OrcKing { + RequestHandler chain; + + public OrcKing() { + buildChain(); + } + + private void buildChain() { + chain = new OrcCommander(new OrcOfficer(new OrcSoldier(null))); + } + + public void makeRequest(Request req) { + chain.handleRequest(req); + } +} +``` + +然後這樣使用它 + +```java +var king = new OrcKing(); +king.makeRequest(new Request(RequestType.DEFEND_CASTLE, "defend castle")); // Orc commander handling request "defend castle" +king.makeRequest(new Request(RequestType.TORTURE_PRISONER, "torture prisoner")); // Orc officer handling request "torture prisoner" +king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax")); // Orc soldier handling request "collect tax" +``` + +## 類圖 +![alt text](./etc/chain-of-responsibility.urm.png "Chain of Responsibility class diagram") + +## 適用性 +使用責任鏈模式當 + +* 多於一個物件可能要處理請求,並且處理器並不知道一個優先順序。處理器應自動確定。 +* 你想對多個物件之一發出請求而無需明確指定接收者 +* 處理請求的物件集合應該被動態指定時 + +## Java世界例子 + +* [java.util.logging.Logger#log()](http://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html#log%28java.util.logging.Level,%20java.lang.String%29) +* [Apache Commons Chain](https://commons.apache.org/proper/commons-chain/index.html) +* [javax.servlet.Filter#doFilter()](http://docs.oracle.com/javaee/7/api/javax/servlet/Filter.html#doFilter-javax.servlet.ServletRequest-javax.servlet.ServletResponse-javax.servlet.FilterChain-) + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/localization/zh-TW/chain/etc/chain-of-responsibility.urm.png b/localization/zh-TW/chain/etc/chain-of-responsibility.urm.png new file mode 100644 index 000000000000..c3a4c80ba322 Binary files /dev/null and b/localization/zh-TW/chain/etc/chain-of-responsibility.urm.png differ diff --git a/localization/zh-TW/circuit-breaker/README.md b/localization/zh-TW/circuit-breaker/README.md new file mode 100644 index 000000000000..62aecb090b95 --- /dev/null +++ b/localization/zh-TW/circuit-breaker/README.md @@ -0,0 +1,314 @@ +--- +title: Circuit Breaker +shortTitle: Circuit Breaker +category: Behavioral +language: zh +tag: + - Performance + - Decoupling + - Cloud distributed +--- + +## 意圖 + +以這樣一種方式處理昂貴的遠端服務呼叫,即單個服務/元件的故障不會導致整個應用程式宕機,我們可以儘快重新連線到服務。 + +## 解釋 + +真實世界例子 + +> 想象一個 Web 應用程式,它同時具有用於獲取資料的本地檔案/影象和遠端服務。 這些遠端服務有時可能健康且響應迅速,或者由於各種原因可能在某 個時間點變得緩慢和無響應。因此,如果其中一個遠端服務緩慢或未成功響應,我們的應用程式將嘗試使用多個執行緒/程序從遠端服務獲取響應,很快它們都會掛起(也稱為 [執行緒飢餓][thread starvation](https://en.wikipedia.org/wiki/Starvation_(computer_science)))導致我們的整個 Web 應用程式崩潰。我們應該能夠檢測到這種情況並向使用者顯示適當的訊息,以便他/她可以探索不受遠端服務故障影響的應用程式的其他部分。 同時,其他正常工作的服務應保持正常執行,不受此故障的影響。 +> + +通俗地說 + +> 斷路器允許優雅地處理失敗的遠端服務。當我們應用程式的所有部分彼此高度解耦時,它特別有用,一個元件的故障並不意味著其他部分將停止工作。 + +維基百科說 + +> 斷路器是現代軟體開發中使用的一種設計模式。 它用於檢測故障並封裝防止故障不斷重複發生、維護期間、臨時外部系統故障或意外系統困難的邏輯。 + +## 程式示例 + +So, how does this all come together? With the above example in mind we will imitate the +functionality in a simple example. A monitoring service mimics the web app and makes both local and +remote calls. + +那麼,這一切是如何結合在一起的呢? 記住上面的例子,我們將在一個簡單的例子中模仿這個功能。 監控服務模仿 Web 應用程式並進行本地和遠端呼叫。 + +服務架構如下: + +![alt text](./etc/ServiceDiagram.png "Service Diagram") + +在程式碼方面,終端使用者應用程式是: + +```java +@Slf4j +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) { + + var serverStartTime = System.nanoTime(); + + var delayedService = new DelayedRemoteService(serverStartTime, 5); + var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2, + 2000 * 1000 * 1000); + + var quickService = new QuickRemoteService(); + var quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, 2, + 2000 * 1000 * 1000); + + // 建立一個可以進行本地和遠端呼叫的監控服務物件 + var monitoringService = new MonitoringService(delayedServiceCircuitBreaker, + quickServiceCircuitBreaker); + + // 獲取本地資源 + LOGGER.info(monitoringService.localResourceResponse()); + + // 從延遲服務中獲取響應 2 次,以滿足失敗閾值 + LOGGER.info(monitoringService.delayedServiceResponse()); + LOGGER.info(monitoringService.delayedServiceResponse()); + + // 在超過故障閾值限制後獲取延遲服務斷路器的當前狀態 + // 現在是開啟狀態 + LOGGER.info(delayedServiceCircuitBreaker.getState()); + + // 同時,延遲服務宕機,從健康快速服務獲取響應 + LOGGER.info(monitoringService.quickServiceResponse()); + LOGGER.info(quickServiceCircuitBreaker.getState()); + + // 等待延遲的服務響應 + try { + LOGGER.info("Waiting for delayed service to become responsive"); + Thread.sleep(5000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // 檢查延時斷路器的狀態,應該是HALF_OPEN + LOGGER.info(delayedServiceCircuitBreaker.getState()); + + // 從延遲服務中獲取響應,現在應該是健康的 + LOGGER.info(monitoringService.delayedServiceResponse()); + // 獲取成功響應後,它的狀態應該是關閉。 + LOGGER.info(delayedServiceCircuitBreaker.getState()); + } +} +``` + +監控服務類: + +```java +public class MonitoringService { + + private final CircuitBreaker delayedService; + + private final CircuitBreaker quickService; + + public MonitoringService(CircuitBreaker delayedService, CircuitBreaker quickService) { + this.delayedService = delayedService; + this.quickService = quickService; + } + + // 假設:本地服務不會失敗,無需將其包裝在斷路器邏輯中 + public String localResourceResponse() { + return "Local Service is working"; + } + + /** + * Fetch response from the delayed service (with some simulated startup time). + * + * @return response string + */ + public String delayedServiceResponse() { + try { + return this.delayedService.attemptRequest(); + } catch (RemoteServiceException e) { + return e.getMessage(); + } + } + + /** + * Fetches response from a healthy service without any failure. + * + * @return response string + */ + public String quickServiceResponse() { + try { + return this.quickService.attemptRequest(); + } catch (RemoteServiceException e) { + return e.getMessage(); + } + } +} +``` +可以看出,它直接呼叫獲取本地資源,但它將對遠端(昂貴)服務的呼叫包裝在斷路器物件中,防止故障如下: + +```java +public class DefaultCircuitBreaker implements CircuitBreaker { + + private final long timeout; + private final long retryTimePeriod; + private final RemoteService service; + long lastFailureTime; + private String lastFailureResponse; + int failureCount; + private final int failureThreshold; + private State state; + private final long futureTime = 1000 * 1000 * 1000 * 1000; + + /** + * Constructor to create an instance of Circuit Breaker. + * + * @param timeout Timeout for the API request. Not necessary for this simple example + * @param failureThreshold Number of failures we receive from the depended service before changing + * state to 'OPEN' + * @param retryTimePeriod Time period after which a new request is made to remote service for + * status check. + */ + DefaultCircuitBreaker(RemoteService serviceToCall, long timeout, int failureThreshold, + long retryTimePeriod) { + this.service = serviceToCall; + // 我們從關閉狀態開始希望一切都是正常的 + this.state = State.CLOSED; + this.failureThreshold = failureThreshold; + // API的超時時間. + // 用於在超過限制時中斷對遠端資源的呼叫 + this.timeout = timeout; + this.retryTimePeriod = retryTimePeriod; + //An absurd amount of time in future which basically indicates the last failure never happened + this.lastFailureTime = System.nanoTime() + futureTime; + this.failureCount = 0; + } + + // 重置所有 + @Override + public void recordSuccess() { + this.failureCount = 0; + this.lastFailureTime = System.nanoTime() + futureTime; + this.state = State.CLOSED; + } + + @Override + public void recordFailure(String response) { + failureCount = failureCount + 1; + this.lastFailureTime = System.nanoTime(); + // Cache the failure response for returning on open state + this.lastFailureResponse = response; + } + + // 根據 failureThreshold、failureCount 和 lastFailureTime 評估當前狀態。 + protected void evaluateState() { + if (failureCount >= failureThreshold) { //Then something is wrong with remote service + if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) { + // 我們已經等得夠久了,應該嘗試檢查服務是否已啟動 + state = State.HALF_OPEN; + } else { + // 服務可能仍會出現故障 + state = State.OPEN; + } + } else { + // 一切正常 + state = State.CLOSED; + } + } + + @Override + public String getState() { + evaluateState(); + return state.name(); + } + + /** + * Break the circuit beforehand if it is known service is down Or connect the circuit manually if + * service comes online before expected. + * + * @param state State at which circuit is in + */ + @Override + public void setState(State state) { + this.state = state; + switch (state) { + case OPEN -> { + this.failureCount = failureThreshold; + this.lastFailureTime = System.nanoTime(); + } + case HALF_OPEN -> { + this.failureCount = failureThreshold; + this.lastFailureTime = System.nanoTime() - retryTimePeriod; + } + default -> this.failureCount = 0; + } + } + + /** + * Executes service call. + * + * @return Value from the remote resource, stale response or a custom exception + */ + @Override + public String attemptRequest() throws RemoteServiceException { + evaluateState(); + if (state == State.OPEN) { + // 如果電路處於開啟狀態,則返回快取的響應 + return this.lastFailureResponse; + } else { + // 如果電路未開啟,則發出 API 請求 + try { + //在實際應用程式中,這將線上程中執行,並且將利用斷路器的超時引數來了解服務 + // 是否正在工作。 在這裡,我們根據伺服器響應本身模擬 + var response = service.call(); + // api 響應正常,重置所有。 + recordSuccess(); + return response; + } catch (RemoteServiceException ex) { + recordFailure(ex.getMessage()); + throw ex; + } + } + } +} +``` + +上述模式如何防止失敗? 讓我們透過它實現的這個有限狀態機來理解。 + +![alt text](./etc/StateDiagram.png "State Diagram") + +- 我們使用某些引數初始化斷路器物件:`timeout`、`failureThreshold` 和 `retryTimePeriod`,這有助於確定 API 的彈性。 +- 最初,我們處於“關閉”狀態,沒有發生對 API 的遠端呼叫。 +- 每次呼叫成功時,我們都會將狀態重置為開始時的狀態。 +- 如果失敗次數超過某個閾值,我們將進入“open”狀態,這就像開路一樣,阻止遠端服務呼叫,從而節省資源。 (這裡,我們從 API 返回名為 ```stale response``` 的響應) +- 一旦超過重試超時時間,我們就會進入“半開”狀態並再次呼叫遠端服務以檢查服務是否正常工作,以便我們可以提供新鮮內容。 失敗將其設定回“開啟”狀態,並在重試超時時間後進行另一次嘗試,而成功將其設定為“關閉”狀態,以便一切重新開始正常工作。 + +## 類圖 + +![alt text](./etc/circuit-breaker.urm.png "Circuit Breaker class diagram") + +## 適用性 + +在以下情況下使用斷路器模式 + +- 構建一個容錯應用程式,其中某些服務的故障不應導致整個應用程式宕機。 +- 構建一個持續執行(永遠線上)的應用程式,這樣它的元件就可以在不完全關閉的情況下升級。 + +## 相關模式 + +- [Retry Pattern](https://github.com/iluwatar/java-design-patterns/tree/master/retry) + +## 真實世界例子 + +* [Spring Circuit Breaker module](https://spring.io/guides/gs/circuit-breaker) +* [Netflix Hystrix API](https://github.com/Netflix/Hystrix) + +## 鳴謝 + +* [Understanding Circuit Breaker Pattern](https://itnext.io/understand-circuitbreaker-design-pattern-with-simple-practical-example-92a752615b42) +* [Martin Fowler on Circuit Breaker](https://martinfowler.com/bliki/CircuitBreaker.html) +* [Fault tolerance in a high volume, distributed system](https://medium.com/netflix-techblog/fault-tolerance-in-a-high-volume-distributed-system-91ab4faae74a) +* [Circuit Breaker pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker) diff --git a/localization/zh-TW/circuit-breaker/etc/ServiceDiagram.png b/localization/zh-TW/circuit-breaker/etc/ServiceDiagram.png new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/localization/zh-TW/circuit-breaker/etc/StateDiagram.png b/localization/zh-TW/circuit-breaker/etc/StateDiagram.png new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/localization/zh-TW/circuit-breaker/etc/circuit-breaker.urm.png b/localization/zh-TW/circuit-breaker/etc/circuit-breaker.urm.png new file mode 100644 index 000000000000..fc90318ec8da Binary files /dev/null and b/localization/zh-TW/circuit-breaker/etc/circuit-breaker.urm.png differ diff --git a/localization/zh-TW/clean-architecture/README.md b/localization/zh-TW/clean-architecture/README.md new file mode 100644 index 000000000000..86db21aa94f3 --- /dev/null +++ b/localization/zh-TW/clean-architecture/README.md @@ -0,0 +1,241 @@ +--- +title: "整潔架構:可維護的軟體架構風格" +shortTitle: 整潔架構 +description: "透過 Java 的真實世界範例、程式碼片段與類別圖了解整潔架構風格。" +category: Architectural +language: zh-TW +tag: + - Architecture + - Decoupling + - Domain + - Inversion of control + - Layered architecture + - Modularity + - Testing +--- + +## 整潔架構的目的 + +組織系統,使核心商業邏輯不依賴外部關注點與框架。 + +## 整潔架構的詳細說明與真實世界範例 + +真實世界範例 + +> 想像一家大型披薩連鎖店有網站、行動 App、電話與店內自助機等訂購管道。核心的披薩領域邏輯(計算價格、準備訂單、管理忠誠度點數)完全與使用者介面和儲存機制分離。因此,連鎖店可以新增聊天機器人或更換資料庫,而不必修改基本訂購規則。 + +簡單來說 + +> 整潔架構透過嚴格分層與清楚邊界,將核心商業邏輯與資料庫、框架或 UI 等外部關注點隔離,確保單一層的變更不會波及整個系統。 + +維基百科指出: + +> Robert C. Martin 於 2012 年提出的整潔架構結合六角形架構、洋蔥架構及其他變體的原則。它以同心環呈現不同元件層級,將 UI、資料庫、外部系統與裝置等介面卡放在外環,將使用案例與實體留在內環。它使用相依性反轉原則,嚴格規定相依性只能由外環指向內環,不能反向存在。 + +心智圖 + +![整潔架構心智圖](./etc/clean-architecture-mind-map.png) + +流程圖 + +![整潔架構流程圖](./etc/clean-architecture-flowchart.png) + +## Java 整潔架構的程式範例 + +首先定義核心領域實體:`Product`、`Order` 與 `Cart`。這些類別包含基本的商業邏輯與狀態。 + +```java +public class Product { + private String id; + private String name; + private double price; + + public Product(String id, String name, double price) { + this.id = id; + this.name = name; + this.price = price; + } +} + +public class Cart { + private Product product; + private int quantity; + + public CartItem(Product product, int quantity) { + this.product = product; + this.quantity = quantity; + } + + public double getTotalPrice() { + return product.getPrice() * quantity; + } +} + +public class Order { + private String orderId; + private List items; + private double totalPrice; + + public Order(String orderId, List items) { + this.orderId = orderId; + this.items = items; + this.totalPrice = items.stream().mapToDouble(CartItem::getTotalPrice).sum(); + } +} +``` + +建立儲存庫介面來抽象每個領域物件的資料操作,這樣可以替換儲存或持久化機制,而不必修改較高層的邏輯。 + +```java +public interface CartRepository { + void addItemToCart(String userId, Product product, int quantity); + void removeItemFromCart(String userId, String productId); + List getItemsInCart(String userId); + double calculateTotal(String userId); + void clearCart(String userId); +} + +public interface ProductRepository { + Product getProductById(String productId); +} + +public interface OrderRepository { + void saveOrder(Order order); +} +``` + +記憶體內儲存庫使用簡單集合保存狀態,展示如何替換或擴充資料層,例如換成資料庫,而不影響領域邏輯。 + +```java +public class InMemoryCartRepository implements CartRepository { + private final Map> userCarts = new HashMap<>(); + + @Override + public void addItemToCart(String userId, Product product, int quantity) { + List cart = userCarts.getOrDefault(userId, new ArrayList<>()); + cart.add(new Cart(product, quantity)); + userCarts.put(userId, cart); + } + + @Override + public void removeItemFromCart(String userId, String productId) { + List cart = userCarts.get(userId); + if (cart != null) cart.removeIf(item -> item.getProduct().getId().equals(productId)); + } + + @Override public List getItemsInCart(String userId) { + return userCarts.getOrDefault(userId, new ArrayList<>()); + } + + @Override public double calculateTotal(String userId) { + return userCarts.getOrDefault(userId, new ArrayList<>()).stream() + .mapToDouble(Cart::getTotalPrice).sum(); + } + + @Override public void clearCart(String userId) { userCarts.remove(userId); } +} + +public class InMemoryOrderRepository implements OrderRepository { + private final List orders = new ArrayList<>(); + @Override public void saveOrder(Order order) { orders.add(order); } +} + +public class InMemoryProductRepository implements ProductRepository { + private final Map products = new HashMap<>(); + public InMemoryProductRepository() { + products.put("1", new Product("1", "Laptop", 1000.0)); + products.put("2", new Product("2", "Smartphone", 500.0)); + } + @Override public Product getProductById(String productId) { return products.get(productId); } +} +``` + +控制器透過使用案例或服務層 `ShoppingCartService` 協調結帳流程與購物車操作: + +```java +public class OrderController { + private final ShoppingCartService shoppingCartUseCase; + public OrderController(ShoppingCartService useCase) { this.shoppingCartUseCase = useCase; } + public Order checkout(String userId) { return shoppingCartUseCase.checkout(userId); } +} + +public class CartController { + private final ShoppingCartService shoppingCartUseCase; + public CartController(ShoppingCartService useCase) { this.shoppingCartUseCase = useCase; } + public void addItemToCart(String userId, String productId, int quantity) { + shoppingCartUseCase.addItemToCart(userId, productId, quantity); + } + public void removeItemFromCart(String userId, String productId) { + shoppingCartUseCase.removeItemFromCart(userId, productId); + } + public double calculateTotal(String userId) { + return shoppingCartUseCase.calculateTotal(userId); + } +} +``` + +整潔架構開始運作。在 `main` 中組合所有元件,模擬加入商品、計算總額與建立訂單的流程: + +```java +public static void main(String[] args) { + ProductRepository productRepository = new InMemoryProductRepository(); + CartRepository cartRepository = new InMemoryCartRepository(); + OrderRepository orderRepository = new InMemoryOrderRepository(); + ShoppingCartService service = new ShoppingCartService( + productRepository, cartRepository, orderRepository); + CartController cartController = new CartController(service); + OrderController orderController = new OrderController(service); + String userId = "user123"; + cartController.addItemToCart(userId, "1", 1); + cartController.addItemToCart(userId, "2", 2); + System.out.println("Total: $" + cartController.calculateTotal(userId)); + Order order = orderController.checkout(userId); + System.out.println("Order placed! Order ID: " + order.getOrderId() + + ", Total: $" + order.getTotalPrice()); +} +``` + +輸出: + +```md +Total: $2000.0 +Order placed! Order ID: ORDER-1743349969254, Total: $2000.0 +``` + +## 何時在 Java 中使用整潔架構 + +* 想讓商業規則獨立於 UI、資料庫及其他外部代理。 +* 大型 Java 應用程式需要高度可維護性與可測試性。 +* 想在應用程式層之間建立清楚邊界。 + +## 整潔架構的實際應用 + +* 金融與保險領域的大型企業系統。 +* 強調解耦與模組化設計的微服務架構。 +* 需要嚴格分離關注點、以領域為中心的 Java 系統。 + +## 整潔架構的優點與取捨 + +優點: + +* 將核心邏輯與基礎設施細節隔離,提高可維護性。 +* 透過領域模型周圍的清楚邊界提高可測試性。 +* 可替換或升級外部元件而不影響核心邏輯。 + +取捨: + +* 強制嚴格分層與邊界會增加初期複雜度。 +* 對不需要嚴格分離的小型專案可能產生額外負擔。 +* 團隊必須有紀律地遵守架構規則。 + +## 相關 Java 設計模式 + +* [相依性注入](https://java-design-patterns.com/patterns/dependency-injection/):以注入相依性取代硬編碼,促進層間解耦。 +* [分層架構](https://java-design-patterns.com/patterns/layered-architecture/):兩者都分離不同層的關注點,但整潔架構更強調嚴格的相依性規則。 +* [六角形架構](https://java-design-patterns.com/patterns/hexagonal-architecture/):同樣著重以連接埠與介面卡隔離核心邏輯。 + +## 參考資料與致謝 + +* [Clean Architecture: A Craftsman's Guide to Software Structure and Design](https://amzn.to/3UoKkaR) +* [Clean Code: A Handbook of Agile Software Craftsmanship](https://amzn.to/3wRnjp5) +* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3wlDrze) \ No newline at end of file diff --git a/localization/zh-TW/client-session/README.md b/localization/zh-TW/client-session/README.md new file mode 100644 index 000000000000..79ee1448b738 --- /dev/null +++ b/localization/zh-TW/client-session/README.md @@ -0,0 +1,133 @@ +--- +title: "Java 用戶端工作階段模式:在工作階段間整理用戶端資料" +shortTitle: 用戶端工作階段 +description: "了解 Java 用戶端工作階段模式,管理多個請求中的使用者狀態與資料,提供連貫且個人化的 Web 應用程式體驗。" +category: Behavioral +language: zh-TW +tags: + - Client-server + - Session management + - State tracking + - Web development +--- + +## 又稱為 + +* 使用者工作階段 + +## 用戶端工作階段設計模式的目的 + +用戶端工作階段模式適合涉及用戶端與伺服器互動的 Web 開發。它在 Web 應用程式的多個請求之間維持使用者狀態與資料,確保連續且個人化的使用體驗。 + +## 用戶端工作階段模式的詳細說明與真實世界範例 + +真實世界範例 + +> 圖書館會員系統就是一個例子。會員登入後,系統建立工作階段追蹤借閱活動,其中包含會員 ID、目前借閱的書籍、到期日與罰款。會員瀏覽目錄、借書或還書時,工作階段持續維護這些狀態,直到會員登出或工作階段逾時。 + +簡單來說 + +> 用戶端工作階段模式在多個 Web 請求之間管理使用者專屬資料,維持連續性與個人化。 + +維基百科指出,用戶端與伺服器模型中,用戶端請求中央伺服器提供服務與資源;Web 應用程式利用工作階段管理多個請求中的使用者資料,例如維持網路銀行互動的登入資訊與狀態。 + +序列圖 + +![用戶端工作階段序列圖](./etc/client-session-sequence-diagram.png) + +## Java 用戶端工作階段模式的程式範例 + +以下有 `Server` 與 `Session` 類別。`Server` 代表處理請求並替用戶端建立工作階段的伺服器,`Session` 代表分配給用戶端的工作階段。 + +```java +// Server 類別代表處理請求並替用戶端建立工作階段的伺服器。 +public class Server { + private String host; + private int port; + + public Server(String host, int port) { + this.host = host; + this.port = port; + } + + public Session getSession(String name) { + return new Session(name, "ClientName"); + } + + public void process(Request request) { + // 處理請求... + } +} + +// Session 類別代表分配給用戶端的工作階段。 +public class Session { + private String id; + private String clientName; + + public Session(String id, String clientName) { + this.id = id; + this.clientName = clientName; + } +} +``` + +在 `main` 中建立伺服器與兩個用戶端工作階段,再把工作階段與資料放入請求傳給伺服器。伺服器便能根據工作階段辨識用戶端。 + +```java +public class App { + public static void main(String[] args) { + var server = new Server("localhost", 8080); + var session1 = server.getSession("Session1"); + var session2 = server.getSession("Session2"); + var request1 = new Request("Data1", session1); + var request2 = new Request("Data2", session2); + server.process(request1); + server.process(request2); + } +} +``` + +程式輸出: + +``` +19:28:49.152 [main] INFO com.iluwatar.client.session.Server -- Processing Request with client: Session1 data: Data1 +19:28:49.154 [main] INFO com.iluwatar.client.session.Server -- Processing Request with client: Session2 data: Data2 +``` + +## 何時在 Java 中使用用戶端工作階段模式 + +* 需要使用者驗證與授權的 Web 應用程式。 +* 需要在多個請求或造訪期間追蹤使用者活動與偏好的應用程式。 +* 需要將狀態管理卸載到用戶端以最佳化伺服器資源的系統。 + +## 用戶端工作階段模式的實際應用 + +* 電子商務網站跨工作階段追蹤購物車內容。 +* 依照使用者偏好與歷史紀錄提供個人化內容的線上平台。 +* 需要登入才能使用個人化或受保護內容的 Web 應用程式。 + +## 用戶端工作階段模式的優點與取捨 + +優點: + +* 減少伺服器儲存使用者狀態的需求,提高伺服器效能。 +* 透過個人化內容與一致導覽提升使用者體驗。 +* 可使用 Cookie、Web Storage API 等多種用戶端儲存機制管理工作階段。 + +取捨: + +* 敏感資訊若未妥善加密與驗證,儲存在用戶端工作階段可能產生安全風險。 +* 依賴不同瀏覽器與使用者設定的 Cookie 政策等用戶端能力。 +* 工作階段逾時、更新,以及多裝置或多分頁同步會增加管理複雜度。 + +## 相關模式 + +* 伺服器工作階段:常與用戶端工作階段搭配,平衡用戶端效率與伺服器控制。 +* [單例](https://java-design-patterns.com/patterns/singleton/):確保應用程式中使用者工作階段只有一個實例。 +* [狀態](https://java-design-patterns.com/patterns/state/):管理工作階段的狀態轉換,例如已驗證、訪客或已逾時。 + +## 參考資料與致謝 + +* [Professional Java for Web Applications](https://amzn.to/4aazY59) +* [Securing Web Applications with Spring Security](https://amzn.to/3PCCEA1) +* [Client Session State Design Pattern: Explained Simply](https://www.youtube.com/watch?v=ycOSj9g41pc) \ No newline at end of file diff --git a/localization/zh-TW/client-session/etc/client-session-sequence-diagram.png b/localization/zh-TW/client-session/etc/client-session-sequence-diagram.png new file mode 100644 index 000000000000..7d1282a9bbd9 Binary files /dev/null and b/localization/zh-TW/client-session/etc/client-session-sequence-diagram.png differ diff --git a/localization/zh-TW/cloud-static-content-hosting/README.md b/localization/zh-TW/cloud-static-content-hosting/README.md new file mode 100644 index 000000000000..6eb6335b5f9d --- /dev/null +++ b/localization/zh-TW/cloud-static-content-hosting/README.md @@ -0,0 +1,132 @@ +--- +title: Static Content Hosting +shortTitle: Static Content Hosting +category: Cloud +language: zh +tag: +- Cloud distributed +--- + +## 意圖 + +將靜態內容部署到基於雲的儲存服務,該服務可以將它們直接交付給客戶端。 這可以減少對昂貴計算例項的需求。 + +## 解釋 + +真實世界例子 + +> 全球性的營銷網站(靜態內容)需要快速的部署以開始吸引潛在的客戶。為了將託管費用和維護成本降至最低,使用雲託管儲存服務和內容交付網路。 + +通俗地說 + +> 靜態內容託管模式利用雲原生儲存服務來儲存內容和全球內容交付網路,將其快取在世界各地的多個資料中心。 在靜態網站上,單個網頁包含靜態內容。 它們還可能包含客戶端指令碼,例如 Javascript。相比之下,動態網站依賴於伺服器端處理,包括伺服器端指令碼,如 PHP、JSP 或 ASP.NET。 + +維基百科說 + +> 與由 Web 應用程式生成的動態網頁相反,靜態網頁(有時稱為平面網頁或固定網頁)是完全按照儲存的方式傳送到使用者的網頁瀏覽器的網頁。靜態網頁適用於從不或很少需要更新的內容,儘管現代 +> Web 模板系統正在改變這一點。可以將大量靜態頁面作為檔案進行維護,沒有自動化工具(例如靜態站點生成器)是不切實際的。 + +**示例** + +![alt text](./etc/static-content-hosting.png "Static Content Hosting") + +在這個例子中我們使用AWS S3建立一個靜態網站,並利用 AWS Cloudfront 在全球範圍內分發內容。 + +1. 首先你需要一個AWS賬戶,你可以在這個建立一個免費的:[AWS Free Tier](https://aws.amazon.com/free/free-tier/) + +2. 登陸 [AWS控制檯](https://console.aws.amazon.com/console/home?nc2=h_ct&src=header-signin) + +3. 進入身份和接入管理服務 (IAM) . + +4. 建立一個僅具有此應用程式必要許可權的IAM使用者。 + + * 點選 `使用者` + * 點選 `新增使用者`. 選擇你想要的 `使用者名稱`, `接入型別`應該是 `程式設計式接入`. 點選 `下一步: 許可權`. + * 選擇 `直接附加已存在的策略`. 選擇 `AmazonS3FullAccess` 和 `CloudFrontFullAccess`. Click `下一步: 標籤`. + * 沒有需要的標籤, 所以直接點選 `下一步: 回顧`. + * 檢查呈現的資訊,沒問題的話點選`建立使用者` + * 完成這個示例所需要的`訪問秘鑰Id`和`訪問秘鑰密碼`將會呈現在你面前,請妥善保管。 + * 點選 `關閉`. + +5. [安裝AWS 命令列工具 (AWS CLI)](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv1.html) 來獲得程式設計式訪問AWS雲。 + +6. 使用`aws configure`命令來配置AWS CLI [說明書](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-config) + +7. 為web站點建立AWS S3 bucket。 注意S3 bucket名字必須要在全球範圍內唯一。 + + + * 語法是 `aws s3 mb ` [說明書](https://docs.aws.amazon.com/cli/latest/userguide/cli-services-s3-commands.html#using-s3-commands-managing-buckets-creating) + * 比如 `aws s3 mb s3://my-static-website-jh34jsjmg` + * 使用列出現有儲存桶的命令`aws s3 ls`驗證儲存桶是否已成功建立 + +8. 使用命令`aws s3 website`來配置bucket作為web站點。 [說明書](https://docs.aws.amazon.com/cli/latest/reference/s3/website.html). + + * 比如`aws s3 website s3://my-static-website-jh34jsjmg --index-document index.html --error-document error.html` + +9. 上傳內容到bucket中。 + * 首先建立內容,至少包含`index.html`和`error.html`文件。 + * 上傳內容到你的bucket中。 [說明書](https://docs.aws.amazon.com/cli/latest/userguide/cli-services-s3-commands.html#using-s3-commands-managing-objects-copy) + * 比如`aws s3 cp index.html s3://my-static-website-jh34jsjmg` and `aws s3 cp error.html s3://my-static-website-jh34jsjmg` + +10. 然後我們需要設定bucket的策略以允許讀取訪問。 + + * 使用以下內容建立`policy.json`(注意需要將bucket名稱替換為自己的)。 + + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "PublicReadGetObject", + "Effect": "Allow", + "Principal": "*", + "Action": "s3:GetObject", + "Resource": "arn:aws:s3:::my-static-website-jh34jsjmg/*" + } + ] + } + ``` + + * 根據這些設定桶策略[說明書](https://docs.aws.amazon.com/cli/latest/reference/s3api/put-bucket-policy.html) + * 比如 `aws s3api put-bucket-policy --bucket my-static-website-jh34jsjmg --policy file://policy.json` + +11. 使用瀏覽器測試web站點。 + + * web站點的URL格式是 `http://.s3-website-.amazonaws.com` + * 比如 這個站點建立在 `eu-west-1` 區域 ,名字是 `my-static-website-jh34jsjmg` 所以它可以透過 `http://my-static-website-jh34jsjmg.s3-website-eu-west-1.amazonaws.com`來訪問。 + +12. 為web站點建立CloudFormation 分發。 + + * 語法文件在這裡 [this reference](https://docs.aws.amazon.com/cli/latest/reference/cloudfront/create-distribution.html) + * 比如,最簡單的方式是使用命令l `aws cloudfront create-distribution --origin-domain-name my-static-website-jh34jsjmg.s3.amazonaws.com --default-root-object index.html` + * 也支援JSON格式的配置 比如使用 `--distribution-config file://dist-config.json` 來傳遞分發的配置檔案引數 + * 命令的舒勇將顯示準確的分配配置項,包括包括可用於測試的生成的 CloudFront 域名,例如 `d2k3xwnaqa8nqx.cloudfront.net` + * CloudFormation 分發部署需要一些時間,但一旦完成,你的網站就會從全球各地的資料中心提供服務! + +13. 就是這樣! 你已經實現了一個靜態網站,其內容分發網路以閃電般的速度在世界各地提供服務。 + + * 要更新網站,你需要更新 S3 儲存桶中的物件並使 CloudFront 分配中的物件無效 + * 要從 AWS CLI 執行此操作,請參閱 [this reference](https://docs.aws.amazon.com/cli/latest/reference/cloudfront/create-invalidation.html) + * 你可能想要做的進一步開發是透過 https 提供內容併為你的站點新增域名 + +## 適用性 + +當你想要執行以下操作時,請使用靜態內容託管模式: + +* 最小化包含一些靜態資源的網站和應用程式的託管成本。 +* 使用靜態內容構建全球可用的網站 +* 監控網站流量、頻寬使用、成本等。 + +## 典型用例 + +* 具有全球影響力的網站 +* 靜態網站生成器生成的內容 +* 沒有動態內容要求的網站 + +## 真實世界例子 + +* [Java Design Patterns web site](https://java-design-patterns.com) + +## 鳴謝 + +* [Static Content Hosting pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/static-content-hosting) diff --git a/localization/zh-TW/cloud-static-content-hosting/etc/static-content-hosting.png b/localization/zh-TW/cloud-static-content-hosting/etc/static-content-hosting.png new file mode 100644 index 000000000000..6e0baa95e0f9 Binary files /dev/null and b/localization/zh-TW/cloud-static-content-hosting/etc/static-content-hosting.png differ diff --git a/localization/zh-TW/collecting-parameter/README.md b/localization/zh-TW/collecting-parameter/README.md new file mode 100644 index 000000000000..a2119d39ac0c --- /dev/null +++ b/localization/zh-TW/collecting-parameter/README.md @@ -0,0 +1,130 @@ +--- +title: "Java 收集參數模式:掌握高效的參數處理" +shortTitle: 收集參數 +description: "了解收集參數設計模式如何將多個參數聚合為單一集合物件,簡化 Java 方法呼叫並提升可讀性。" +category: Behavioral +language: zh-TW +tag: + - Accumulation + - Data processing + - Data transfer + - Generic +--- + +## 又稱為 + +* 收集器 +* 累加器 + +## 收集參數設計模式的目的 + +收集參數模式將多個參數聚合成單一集合物件,以簡化方法呼叫。方法可以把同一個集合物件傳遞給不同方法,讓每個方法將結果加入集合,而不必各自建立集合,藉此提升 Java 程式的可讀性與可維護性。 + +## 收集參數模式的詳細說明與真實世界範例 + +真實世界範例 + +> 在餐廳中,服務生需要記錄顧客的訂單。服務生不必分別記下開胃菜、主餐、甜點與飲料,而是使用一張訂單表單把所有品項集中在同一份文件中。這張表單讓服務生與廚房更容易溝通;在軟體中,收集參數模式也會將多個參數集中到單一物件中。 + +簡單來說 + +> 收集參數模式將多個參數封裝到單一物件,簡化方法呼叫。 + +維基百科指出: + +> 收集參數慣用法會反覆將集合(清單、映射等)作為參數傳給方法,由方法將項目加入集合。 + +流程圖 + +![收集參數流程圖](./etc/collecting-parameter-flowchart.png) + +## Java 收集參數模式的程式範例 + +大型企業大樓中有一個全域印表機佇列,包含目前等待處理的所有列印工作。不同樓層有不同型號的印表機與列印政策,程式需要持續將符合條件的列印工作加入收集參數。 + +規則如下: + +* 彩色 A4 紙必須單面列印,其他非彩色紙張都接受。 +* A3 紙張必須非彩色且單面列印。 +* A2 紙張必須單頁、單面且非彩色。 + +```java +public class App { + static PrinterQueue printerQueue = PrinterQueue.getInstance(); + + public static void main(String[] args) { + printerQueue.addPrinterItem(new PrinterItem(PaperSizes.A4, 5, false, false)); + printerQueue.addPrinterItem(new PrinterItem(PaperSizes.A3, 2, false, false)); + printerQueue.addPrinterItem(new PrinterItem(PaperSizes.A2, 5, false, false)); + + var result = new LinkedList(); + addValidA4Papers(result); + addValidA3Papers(result); + addValidA2Papers(result); + } + + public static void addValidA4Papers(Queue collection) { + for (PrinterItem item : printerQueue.getPrinterQueue()) { + if (item.paperSize.equals(PaperSizes.A4)) { + var valid = item.isColour && !item.isDoubleSided; + if (valid || !item.isColour) collection.add(item); + } + } + } + + public static void addValidA3Papers(Queue collection) { + for (PrinterItem item : printerQueue.getPrinterQueue()) { + if (item.paperSize.equals(PaperSizes.A3) + && !item.isColour && !item.isDoubleSided) collection.add(item); + } + } + + public static void addValidA2Papers(Queue collection) { + for (PrinterItem item : printerQueue.getPrinterQueue()) { + if (item.paperSize.equals(PaperSizes.A2) + && item.pageCount == 1 && !item.isDoubleSided && !item.isColour) { + collection.add(item); + } + } + } +} +``` + +`result` 就是收集參數。它從一個方法傳到下一個方法,逐步累積符合政策的列印工作,這正是收集參數模式的核心。 + +## 何時使用收集參數模式 + +* 方法需要大量參數,導致方法簽章難以閱讀。 +* 多個方法需要傳遞同一組參數,以減少重複與錯誤。 +* 想提升程式碼的可讀性與可維護性。 + +## 收集參數模式的實際應用 + +* 收集多個處理步驟的結果。 +* 在資料處理流程中傳遞同一個累加器。 +* 重構參數過多且難以維護的方法。 + +## 收集參數模式的優點與取捨 + +優點: + +* 減少方法簽章中的參數數量,提高可讀性。 +* 讓不同方法重複使用參數集合。 +* 集中參數結構,提高可維護性。 + +取捨: + +* 會增加額外類別,管理不當時可能提高複雜度。 +* 參數物件過大時可能造成過度泛化。 + +## 相關 Java 設計模式 + +* [命令](https://java-design-patterns.com/patterns/command/):命令可使用收集參數聚合多個操作的結果。 +* [組合](https://java-design-patterns.com/patterns/composite/):處理階層結構時,可收集整個組合結構的結果。 +* [訪問者](https://java-design-patterns.com/patterns/visitor/):訪問者負責走訪,收集參數則累積結果。 + +## 參考資料與致謝 + +* [Refactoring To Patterns](http://www.tarrani.net/RefactoringToPatterns.pdf) +* [Smalltalk Best Practice Patterns](https://ptgmedia.pearsoncmg.com/images/9780134769042/samplepages/013476904X.pdf) +* [Collecting Parameter(WikiWikiWeb)](https://wiki.c2.com/?CollectingParameter) \ No newline at end of file diff --git a/localization/zh-TW/collection-pipeline/README.md b/localization/zh-TW/collection-pipeline/README.md new file mode 100644 index 000000000000..3acf96e30814 --- /dev/null +++ b/localization/zh-TW/collection-pipeline/README.md @@ -0,0 +1,28 @@ +--- +title: Collection Pipeline +shortTitle: Collection Pipeline +category: Functional +language: zh +tag: + - Reactive +--- + +## 釋義 +**集合管道(Collection Pipeline)**包含**函式組合(Function Composition)**和**集合管道(Collection Pipeline)**兩組合概念,這是兩種函數語言程式設計模式,你可以在程式碼中結合這兩種模式來進行集合迭代。 +在函數語言程式設計中,可以透過一系列較小的模組化函式或操作來編排複雜的操作。這一系列函式被稱為函式組合。當一個資料集合流經一個函式組合時,它就成為一個集合管道。函式組合和集合管道是函數語言程式設計中經常使用的兩種設計模式。 + +## 類圖 +![alt text](./etc/collection-pipeline.png "Collection Pipeline") + +## 適用場景 +在以下場景適用集合管道模式: + +* 當你想執行一組連續的運算元操作,其中一個運算元收集的輸出需要被輸入到下一個運算元中 +* 當你在程式碼中需要使用大量的中間狀態語句時 +* 當你在程式碼中使用大量的迴圈語句時 + +## 引用 + +* [Function composition and the Collection Pipeline pattern](https://www.ibm.com/developerworks/library/j-java8idioms2/index.html) +* [Martin Fowler](https://martinfowler.com/articles/collection-pipeline/) +* [Java8 Streams](https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html) \ No newline at end of file diff --git a/localization/zh-TW/collection-pipeline/etc/collection-pipeline.png b/localization/zh-TW/collection-pipeline/etc/collection-pipeline.png new file mode 100644 index 000000000000..67d52629cb86 Binary files /dev/null and b/localization/zh-TW/collection-pipeline/etc/collection-pipeline.png differ diff --git a/localization/zh-TW/combinator/README.md b/localization/zh-TW/combinator/README.md new file mode 100644 index 000000000000..6fbab09b0e6a --- /dev/null +++ b/localization/zh-TW/combinator/README.md @@ -0,0 +1,208 @@ +--- +title: Combinator +shortTitle: Combinator +category: Idiom +language: zh +tag: + - Reactive +--- + +## 或稱 + +構圖模式 + +## 目的 + +功能模式代表了一種以組合功能為中心的圖書館組織風格。 +簡單地說,有一些型別 T,一些用於構造型別 T 的“原始”值的函式,以及一些可以以各種方式組合型別 T 的值以構建更復雜的型別 T 值的“組合器”。 + +## 解釋 + +真實世界例子 + +> 在電腦科學中,組合邏輯被用作計算的簡化模型,用於可計算性理論和證明理論。 儘管組合邏輯很簡單,但它捕獲了計算的許多基本特徵。 +> + +通俗的說 +> 組合器允許從先前定義的“事物”建立新的“事物”。 +> + +維基百科說 + +> 組合器是一個高階函式,僅使用函式應用程式和之前定義的組合器來定義其引數的結果。 +> + +**程式示例** + +翻譯上面的組合器示例。 首先,我們有一個由幾個方法`contains`, `not`, `or`, `and`組成的介面。 + +```java +// 用於查詢文字中的行的功能介面。 +public interface Finder { + + // 在文字中查詢行的函式。 + List find(String text); + + // 函式{@link #find(String)}的簡單實現。 + static Finder contains(String word) { + return txt -> Stream.of(txt.split("\n")) + .filter(line -> line.toLowerCase().contains(word.toLowerCase())) + .collect(Collectors.toList()); + } + + // 組合器:not。 + default Finder not(Finder notFinder) { + return txt -> { + List res = this.find(txt); + res.removeAll(notFinder.find(txt)); + return res; + }; + } + + // 組合器:or。 + default Finder or(Finder orFinder) { + return txt -> { + List res = this.find(txt); + res.addAll(orFinder.find(txt)); + return res; + }; + } + + // 組合器:and。 + default Finder and(Finder andFinder) { + return + txt -> this + .find(txt) + .stream() + .flatMap(line -> andFinder.find(line).stream()) + .collect(Collectors.toList()); + } + ... +} +``` + +然後我們還有另一個組合器用於一些複雜的查詢器`advancedFinder`, `filteredFinder`, `specializedFinder`和`expandedFinder`。 + +```java +// 由簡單取景器組成的複雜取景器。 +public class Finders { + + private Finders() { + } + + // Finder 用於查詢複雜的查詢。 + public static Finder advancedFinder(String query, String orQuery, String notQuery) { + return + Finder.contains(query) + .or(Finder.contains(orQuery)) + .not(Finder.contains(notQuery)); + } + + // 過濾查詢器也會查詢包含排除查詢的查詢。 + public static Finder filteredFinder(String query, String... excludeQueries) { + var finder = Finder.contains(query); + + for (String q : excludeQueries) { + finder = finder.not(Finder.contains(q)); + } + return finder; + } + + // 專門查詢。 每個下一個查詢都會在上一個結果中查詢。 + public static Finder specializedFinder(String... queries) { + var finder = identMult(); + + for (String query : queries) { + finder = finder.and(Finder.contains(query)); + } + return finder; + } + + // 擴充套件查詢。 尋找替代品。 + public static Finder expandedFinder(String... queries) { + var finder = identSum(); + + for (String query : queries) { + finder = finder.or(Finder.contains(query)); + } + return finder; + } + ... +} +``` + +現在我們已經建立了組合器的介面和方法。 現在我們有一個處理這些組合器的應用程式。 + +```java +var queriesOr = new String[]{"many", "Annabel"}; +var finder = Finders.expandedFinder(queriesOr); +var res = finder.find(text()); +LOGGER.info("the result of expanded(or) query[{}] is {}", queriesOr, res); + +var queriesAnd = new String[]{"Annabel", "my"}; +finder = Finders.specializedFinder(queriesAnd); +res = finder.find(text()); +LOGGER.info("the result of specialized(and) query[{}] is {}", queriesAnd, res); + +finder = Finders.advancedFinder("it was", "kingdom", "sea"); +res = finder.find(text()); +LOGGER.info("the result of advanced query is {}", res); + +res = Finders.filteredFinder(" was ", "many", "child").find(text()); +LOGGER.info("the result of filtered query is {}", res); + +private static String text() { + return + "It was many and many a year ago,\n" + + "In a kingdom by the sea,\n" + + "That a maiden there lived whom you may know\n" + + "By the name of ANNABEL LEE;\n" + + "And this maiden she lived with no other thought\n" + + "Than to love and be loved by me.\n" + + "I was a child and she was a child,\n" + + "In this kingdom by the sea;\n" + + "But we loved with a love that was more than love-\n" + + "I and my Annabel Lee;\n" + + "With a love that the winged seraphs of heaven\n" + + "Coveted her and me."; + } +``` + +**程式輸出:** + +```java +the result of expanded(or) query[[many, Annabel]] is [It was many and many a year ago,, By the name of ANNABEL LEE;, I and my Annabel Lee;] +the result of specialized(and) query[[Annabel, my]] is [I and my Annabel Lee;] +the result of advanced query is [It was many and many a year ago,] +the result of filtered query is [But we loved with a love that was more than love-] +``` + +現在我們可以設計我們的應用程式,使其具有查詢查詢功能`expandedFinder`, `specializedFinder`, `advancedFinder`, `filteredFinder`,這些功能均派生自`contains`, `or`, `not`, `and`。 + + +## 類圖 +![alt text](./etc/combinator.urm.png "Combinator class diagram") + +## 適用性 +在以下情況下使用組合器模式: + +- 你可以從更簡單的值建立更復雜的值,但具有相同的型別(它們的組合) + +## 好處 + +- 從開發人員的角度來看,API 由領域中的術語組成。 +- 組合階段和應用階段之間有明顯的區別。 +- 首先構造一個例項,然後執行它。 +- 這使得該模式適用於並行環境。 + + +## 現實世界的例子 + +- java.util.function.Function#compose +- java.util.function.Function#andThen + +## 鳴謝 + +- [Example for java](https://gtrefs.github.io/code/combinator-pattern/) +- [Combinator pattern](https://wiki.haskell.org/Combinator_pattern) +- [Combinatory logic](https://wiki.haskell.org/Combinatory_logic) diff --git a/localization/zh-TW/combinator/etc/combinator.urm.png b/localization/zh-TW/combinator/etc/combinator.urm.png new file mode 100644 index 000000000000..80fdd36340bb Binary files /dev/null and b/localization/zh-TW/combinator/etc/combinator.urm.png differ diff --git a/localization/zh-TW/command-query-responsibility-segregation/README.md b/localization/zh-TW/command-query-responsibility-segregation/README.md new file mode 100644 index 000000000000..80f812a7b8b9 --- /dev/null +++ b/localization/zh-TW/command-query-responsibility-segregation/README.md @@ -0,0 +1,125 @@ +--- +title: "Java 命令查詢職責分離:為可擴充性最佳化資料互動" +shortTitle: 命令查詢職責分離(CQRS) +description: "了解 Java 的 CQRS 模式,透過分離命令與查詢提升系統的擴充性、效能與可維護性。" +category: Architectural +language: zh-TW +tag: + - Event-driven + - Performance + - Scalability +--- + +## 又稱為 + +* CQRS + +## 命令查詢職責分離的目的 + +命令查詢職責分離(CQRS)將修改應用程式狀態的操作(命令)與讀取狀態的操作(查詢)分開,以提升複雜軟體系統的擴充性、效能與可維護性。 + +## CQRS 的詳細說明與真實世界範例 + +真實世界範例 + +> 想像一座現代圖書館:借書與還書(命令)在服務櫃台處理,搜尋與閱讀書籍(查詢)則在閱覽區進行。櫃台最佳化交易效率與紀錄保存,閱覽區則最佳化舒適度與取用性。這種分離就像 CQRS 一樣,提升整體效率與使用體驗。 + +簡單來說 + +> CQRS 將修改資料的動作(命令)與取得資料的動作(查詢)分開,讓讀取與寫入可以獨立最佳化。 + +Microsoft 文件指出: + +> CQRS 將讀取與寫入分成不同模型,使用命令更新資料,使用查詢讀取資料。 + +架構圖 + +![CQRS 架構圖](./etc/cqrs-architecture-diagram.png) + +## Java CQRS 模式的程式範例 + +實作 CQRS 的一種方式,是將讀取與寫入操作放在不同服務中。以下程式使用 `CommandServiceImpl` 執行寫入,並使用 `QueryServiceImpl` 執行讀取。 + +```java +public static void main(String[] args) { + // 使用 CommandService 建立作者與書籍 + var commands = new CommandServiceImpl(); + commands.authorCreated(AppConstants.E_EVANS, "Eric Evans", "evans@email.com"); + commands.authorCreated(AppConstants.J_BLOCH, "Joshua Bloch", "jBloch@email.com"); + commands.authorCreated(AppConstants.M_FOWLER, "Martin Fowler", "mFowler@email.com"); + commands.bookAddedToAuthor("Domain-Driven Design", 60.08, AppConstants.E_EVANS); + commands.bookAddedToAuthor("Effective Java", 40.54, AppConstants.J_BLOCH); + commands.bookAddedToAuthor("Java Puzzlers", 39.99, AppConstants.J_BLOCH); + commands.bookAddedToAuthor("Java Concurrency in Practice", 29.40, AppConstants.J_BLOCH); + commands.authorNameUpdated(AppConstants.E_EVANS, "Eric J. Evans"); + + // 使用 QueryService 查詢資料庫 + var queries = new QueryServiceImpl(); + var nullAuthor = queries.getAuthorByUsername("username"); + var evans = queries.getAuthorByUsername(AppConstants.E_EVANS); + var blochBooksCount = queries.getAuthorBooksCount(AppConstants.J_BLOCH); + var authorsCount = queries.getAuthorsCount(); + var dddBook = queries.getBook("Domain-Driven Design"); + var blochBooks = queries.getAuthorBooks(AppConstants.J_BLOCH); + LOGGER.info("Author username : {}", nullAuthor); + LOGGER.info("Author evans : {}", evans); + LOGGER.info("jBloch number of books : {}", blochBooksCount); + LOGGER.info("Number of authors : {}", authorsCount); + LOGGER.info("DDD book : {}", dddBook); + LOGGER.info("jBloch books : {}", blochBooks); + HibernateUtil.getSessionFactory().close(); +} +``` + +`CommandServiceImpl` 負責建立作者、書籍及將書籍加入作者;`QueryServiceImpl` 負責取得作者與書籍細節。這種關注點分離讓資料存取與操作方式更有彈性,是 CQRS 的核心。 + +程式輸出: + +``` +Author username : null +Author evans : Author(name=Eric J. Evans, email=evans@email.com, username=eEvans) +jBloch number of books : 3 +Number of authors : 3 +DDD book : Book(title=Domain-Driven Design, price=60.08) +``` + +## 何時在 Java 中使用 CQRS + +* 需要為擴充性與維護性使用不同讀寫模型的系統,例如電子商務平台與高流量網站。 +* 金融或醫療等複雜領域,更新物件與讀取資料的需求差異很大。 +* 讀取效能很重要,且讀寫可以使用不同資料模型或資料庫的情境。 + +## CQRS 的實際應用 + +* 由不同服務分別管理讀寫責任的分散式系統與微服務架構。 +* 將狀態變更儲存為事件序列的事件溯源系統。 +* 分離讀寫資料庫以最佳化負載處理的高效能 Web 應用程式。 + +## CQRS 的優點與取捨 + +優點: + +* 擴充性:讀寫模型可依各自需求獨立擴充。 +* 最佳化:讀取模型可針對查詢、寫入模型可針對交易完整性最佳化。 +* 可維護性:分離關注點,降低複雜度。 +* 彈性:讀寫兩端可選擇不同技術。 + +取捨: + +* 讀寫模型同步與一致性維護會增加複雜度。 +* 對簡單系統而言可能是過度設計。 +* 需要更深入理解與仔細設計,初期學習成本較高。 + +## 相關 Java 設計模式 + +* [事件溯源](https://java-design-patterns.com/patterns/event-sourcing/):常與 CQRS 一起使用,將狀態變更儲存為事件序列。 +* 領域驅動設計(DDD):CQRS 能提供清楚邊界與關注點分離。 +* [儲存庫](https://java-design-patterns.com/patterns/repository/):抽象資料層,整合命令端與查詢端。 + +## 參考資料與致謝 + +* [Implementing Domain-Driven Design](https://amzn.to/3TJN2HH) +* [Microsoft .NET: Architecting Applications for the Enterprise](https://amzn.to/4aktRes) +* [Patterns, Principles, and Practices of Domain-Driven Design](https://amzn.to/3vNV4Hm) +* [CQRS(Martin Fowler)](https://martinfowler.com/bliki/CQRS.html) +* [CQRS pattern(Microsoft)](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs) \ No newline at end of file diff --git a/localization/zh-TW/command/README.md b/localization/zh-TW/command/README.md new file mode 100644 index 000000000000..8954aa9c832b --- /dev/null +++ b/localization/zh-TW/command/README.md @@ -0,0 +1,252 @@ +--- +title: Command +shortTitle: Command +category: Behavioral +language: zh +tag: + - Gang of Four +--- + +## 或稱 +行動, 事務模式 + +## 目的 +將請求封裝為物件,從而使你可以將具有不同請求的客戶端引數化,佇列或記錄請求,並且支援可撤銷操作。 + +## 解釋 +真實世界例子 + +> 有一個巫師在地精上施放咒語。咒語在地精上一一執行。第一個咒語使地精縮小,第二個使他不可見。然後巫師將咒語一個個的反轉。這裡的每一個咒語都是一個可撤銷的命令物件。 + +用通俗的話說 + +> 用命令物件的方式儲存請求以在將來時可以執行它或撤銷它。 + +維基百科說 + +> 在物件導向程式設計中,命令模式是一種行為型設計模式,它把在稍後執行的一個動作或觸發的一個事件所需要的所有資訊封裝到一個物件中。 + +**程式設計示例** + +這是巫師和地精的示例程式碼。讓我們從巫師類開始。 + +```java +public class Wizard { + + private static final Logger LOGGER = LoggerFactory.getLogger(Wizard.class); + + private final Deque undoStack = new LinkedList<>(); + private final Deque redoStack = new LinkedList<>(); + + public Wizard() {} + + public void castSpell(Command command, Target target) { + LOGGER.info("{} casts {} at {}", this, command, target); + command.execute(target); + undoStack.offerLast(command); + } + + public void undoLastSpell() { + if (!undoStack.isEmpty()) { + var previousSpell = undoStack.pollLast(); + redoStack.offerLast(previousSpell); + LOGGER.info("{} undoes {}", this, previousSpell); + previousSpell.undo(); + } + } + + public void redoLastSpell() { + if (!redoStack.isEmpty()) { + var previousSpell = redoStack.pollLast(); + undoStack.offerLast(previousSpell); + LOGGER.info("{} redoes {}", this, previousSpell); + previousSpell.redo(); + } + } + + @Override + public String toString() { + return "Wizard"; + } +} +``` + +接下來我們介紹咒語層級 + +```java +public interface Command { + + void execute(Target target); + + void undo(); + + void redo(); + + String toString(); +} + +public class InvisibilitySpell implements Command { + + private Target target; + + @Override + public void execute(Target target) { + target.setVisibility(Visibility.INVISIBLE); + this.target = target; + } + + @Override + public void undo() { + if (target != null) { + target.setVisibility(Visibility.VISIBLE); + } + } + + @Override + public void redo() { + if (target != null) { + target.setVisibility(Visibility.INVISIBLE); + } + } + + @Override + public String toString() { + return "Invisibility spell"; + } +} + +public class ShrinkSpell implements Command { + + private Size oldSize; + private Target target; + + @Override + public void execute(Target target) { + oldSize = target.getSize(); + target.setSize(Size.SMALL); + this.target = target; + } + + @Override + public void undo() { + if (oldSize != null && target != null) { + var temp = target.getSize(); + target.setSize(oldSize); + oldSize = temp; + } + } + + @Override + public void redo() { + undo(); + } + + @Override + public String toString() { + return "Shrink spell"; + } +} +``` + +最後我們有咒語的目標地精。 + +```java +public abstract class Target { + + private static final Logger LOGGER = LoggerFactory.getLogger(Target.class); + + private Size size; + + private Visibility visibility; + + public Size getSize() { + return size; + } + + public void setSize(Size size) { + this.size = size; + } + + public Visibility getVisibility() { + return visibility; + } + + public void setVisibility(Visibility visibility) { + this.visibility = visibility; + } + + @Override + public abstract String toString(); + + public void printStatus() { + LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility()); + } +} + +public class Goblin extends Target { + + public Goblin() { + setSize(Size.NORMAL); + setVisibility(Visibility.VISIBLE); + } + + @Override + public String toString() { + return "Goblin"; + } + +} +``` + +最後是整個示例的實踐。 + +```java +var wizard = new Wizard(); +var goblin = new Goblin(); +goblin.printStatus(); +// Goblin, [size=normal] [visibility=visible] +wizard.castSpell(new ShrinkSpell(), goblin); +// Wizard casts Shrink spell at Goblin +goblin.printStatus(); +// Goblin, [size=small] [visibility=visible] +wizard.castSpell(new InvisibilitySpell(), goblin); +// Wizard casts Invisibility spell at Goblin +goblin.printStatus(); +// Goblin, [size=small] [visibility=invisible] +wizard.undoLastSpell(); +// Wizard undoes Invisibility spell +goblin.printStatus(); +// Goblin, [size=small] [visibility=visible] +``` + +## 類圖 +![alt text](./etc/command.png "Command") + +## 適用性 +使用命令模式當你想 + +* 透過操作將物件引數化。你可以使用回撥函式(即,已在某處註冊以便稍後呼叫的函式)以過程語言表示這種引數化。命令是回撥的一種物件導向替代方案。 +* 在不同的時間指定,排隊和執行請求。一個命令物件的生存期可以獨立於原始請求。如果請求的接收方可以以地址空間無關的方式來表示,那麼你可以將請求的命令物件傳輸到其他程序並在那裡執行請求。 +* 支援撤銷。命令的執行操作可以在命令本身中儲存狀態以反轉其效果。命令介面必須有新增的反執行操作,該操作可以逆轉上一次執行呼叫的效果。執行的命令儲存在歷史列表中。無限撤消和重做透過分別向後和向前遍歷此列表來實現,分別呼叫unexecute和execute。 +* 支援日誌記錄更改,以便在系統崩潰時可以重新應用它們。透過使用載入和儲存操作擴充套件命令介面,你可以保留更改的永久日誌。從崩潰中恢復涉及從磁碟重新載入記錄的命令,並透過執行操作重新執行它們。 +* 透過原始的操作來構建一個以高階操作圍繞的系統。這種結構在支援事務的資訊系統中很常見。事務封裝了一組資料更改。命令模式提供了一種對事務進行建模的方法。命令具有公共介面,讓你以相同的方式呼叫所有事務。該模式還可以透過新的事務來輕鬆擴充套件系統。 + +## 典型用例 + +* 保留請求歷史 +* 實現回撥功能 +* 實現撤銷功能 + +## Java世界例子 + +* [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html) +* [org.junit.runners.model.Statement](https://github.com/junit-team/junit4/blob/master/src/main/java/org/junit/runners/model/Statement.java) +* [Netflix Hystrix](https://github.com/Netflix/Hystrix/wiki) +* [javax.swing.Action](http://docs.oracle.com/javase/8/docs/api/javax/swing/Action.html) + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) diff --git a/localization/zh-TW/command/etc/command.png b/localization/zh-TW/command/etc/command.png new file mode 100644 index 000000000000..0f026464ecc4 Binary files /dev/null and b/localization/zh-TW/command/etc/command.png differ diff --git a/localization/zh-TW/commander/README.md b/localization/zh-TW/commander/README.md new file mode 100644 index 000000000000..459358afd47d --- /dev/null +++ b/localization/zh-TW/commander/README.md @@ -0,0 +1,25 @@ +--- +title: Commander +shortTitle: Commander +category: Concurrency +language: zh +tag: + - Cloud distributed +--- + +## 目的 + +> 用於處理執行分散式事務時可能遇到的所有問題。 + +## 類圖 +![alt text](./etc/commander.urm.png "Commander class diagram") + +## 適用場合 +當我們需要提交兩個資料庫去完成事務,提交不是原子性且可能因此造成問題時,適合用這個設計模式。 + +## 解釋 +處理分散式事務很棘手,但如果我們不仔細處理,可能會帶來不想要的後果。假設我們有一個電子商務網站,它有一個支付微服務和一個運輸微服務。如果當前運輸可用,但支付服務不可用,或者反之,當我們已經收到使用者的訂單後,我們應該如何處理?我們需要有一個機制來處理這些情況。我們必須將訂單指向其中一個服務(在這個例子中是運輸),然後將訂單新增到另一個服務的資料庫中(在這個例子中是支付),因為兩個資料庫不能原子地更新。如果我們當前無法做到這一點,應該有一個佇列,可以將這個請求排隊,並且必須有一個機制,允許佇列中出現失敗。所有這些都需要透過不斷的重試,在保證冪等性(即使請求多次,變化只應用一次)的情況下,由一個指揮類來完成,以達到最終一致性的狀態。 + +## 鳴謝 + +* [Distributed Transactions: The Icebergs of Microservices](https://www.grahamlea.com/2016/08/distributed-transactions-microservices-icebergs/) diff --git a/localization/zh-TW/commander/etc/commander.urm.png b/localization/zh-TW/commander/etc/commander.urm.png new file mode 100644 index 000000000000..6b5ebba75bd6 Binary files /dev/null and b/localization/zh-TW/commander/etc/commander.urm.png differ diff --git a/localization/zh-TW/component/README.md b/localization/zh-TW/component/README.md new file mode 100644 index 000000000000..c502d3870ece --- /dev/null +++ b/localization/zh-TW/component/README.md @@ -0,0 +1,152 @@ +--- +title: "Java 元件模式:以可重複使用的元件簡化複雜系統" +shortTitle: 元件 +description: "了解 Java 元件設計模式,包括 ECS 架構、模組化與解耦,以及遊戲開發中的實際應用。" +categories: Structural +language: zh-TW +tag: + - Game programming + - Decoupling + - Modularity +--- + +## 又稱為 + +* 實體-元件-系統(ECS) +* 元件-實體-系統(CES) +* 元件式架構(CBA) + +## 元件設計模式的目的 + +元件模式將程式碼組織成可重複使用、可互換的元件,促進彈性、模組化與易維護性。它特別適合遊戲開發,讓實體可以動態組合不同的行為。 + +## 元件模式的詳細說明與真實世界範例 + +真實世界範例 + +> 假設一款遊戲同時有圖形與音效元件。若把兩者放在同一個 Java 類別中,長程式碼與不同團隊同時修改同一類別可能造成維護困難與衝突。元件模式將圖形與音效拆成個別元件,讓它們能獨立開發,提升可維護性與可擴充性。 + +簡單來說 + +> 元件模式提供單一屬性,讓多個物件都能存取它,而不必讓物件彼此建立關係。 + +架構圖 + +![元件架構圖](./etc/component-architecture-diagram.png) + +## Java 元件模式的程式範例 + +`App` 建立兩個由可修改元件集合組成的物件,示範元件模式的使用方式。 + +```java +public final class App { + public static void main(String[] args) { + final var player = GameObject.createPlayer(); + final var npc = GameObject.createNpc(); + LOGGER.info("Player Update:"); + player.update(KeyEvent.KEY_LOCATION_LEFT); + LOGGER.info("NPC Update:"); + npc.demoUpdate(); + } +} +``` + +`GameObject` 負責建立玩家與 NPC,也包含更新或修改物件元件資料的方法。 + +```java +public class GameObject { + private final InputComponent inputComponent; + private final PhysicComponent physicComponent; + private final GraphicComponent graphicComponent; + public String name; + public int velocity = 0; + public int coordinate = 0; + + public static GameObject createPlayer() { + return new GameObject(new PlayerInputComponent(), new ObjectPhysicComponent(), + new ObjectGraphicComponent(), "player"); + } + + public static GameObject createNpc() { + return new GameObject(new DemoInputComponent(), new ObjectPhysicComponent(), + new ObjectGraphicComponent(), "npc"); + } + + public void demoUpdate() { + inputComponent.update(this); + physicComponent.update(this); + graphicComponent.update(this); + } + + public void update(int e) { + inputComponent.update(this, e); + physicComponent.update(this); + graphicComponent.update(this); + } + + public void updateVelocity(int acceleration) { this.velocity += acceleration; } + public void updateCoordinate() { this.coordinate += this.velocity; } +} +``` + +元件套件中的元件提供物件繼承各種領域的介面。以下 `PlayerInputComponent` 會依照使用者按鍵更新物件的速度。 + +```java +public class PlayerInputComponent implements InputComponent { + private static final int WALK_ACCELERATION = 1; + + @Override + public void update(GameObject gameObject, int e) { + switch (e) { + case KeyEvent.KEY_LOCATION_LEFT -> { + gameObject.updateVelocity(-WALK_ACCELERATION); + LOGGER.info(gameObject.getName() + " has moved left."); + } + case KeyEvent.KEY_LOCATION_RIGHT -> { + gameObject.updateVelocity(WALK_ACCELERATION); + LOGGER.info(gameObject.getName() + " has moved right."); + } + default -> { + LOGGER.info(gameObject.getName() + "'s velocity is unchanged due to the invalid input"); + gameObject.updateVelocity(0); + } + } + } +} +``` + +## 何時在 Java 中使用元件模式 + +* 遊戲與模擬系統中的實體(角色、物品等)需要動態組合能力或狀態。 +* 系統需要高度模組化,且實體可能在執行期間改變行為,不適合依賴繼承階層。 + +## 元件模式的實際應用 + +元件模式適合遊戲開發與模擬,也適合角色和物品具有動態能力或狀態的系統。它讓實體能在執行期間改變行為,不必依賴繼承階層。 + +## 元件模式的優點與取捨 + +優點: + +* 彈性與重複使用:元件能被不同實體共用,方便新增或修改功能。 +* 解耦:減少遊戲實體狀態與行為的相依性,讓變更與維護更容易。 +* 動態組合:實體可在執行期間新增或移除元件,提供高度彈性。 + +取捨: + +* 可能增加系統架構的複雜度,尤其是元件間的相依與溝通。 +* 間接呼叫與動態行為可能產生效能負擔,在高效能遊戲迴圈中尤其需要注意。 + +## 相關 Java 設計模式 + +* [裝飾者](https://java-design-patterns.com/patterns/decorator/):同樣能動態增加責任,但不專注於遊戲實體。 +* [享元](https://java-design-patterns.com/patterns/flyweight/):可與元件模式搭配,在多個實體間共用元件以節省記憶體。 +* [觀察者](https://java-design-patterns.com/patterns/observer/):元件系統常用觀察者傳遞元件間的狀態變更。 + +## 參考資料與致謝 + +* [Game Programming Patterns](https://amzn.to/4cDRWhV) +* [Procedural Content Generation for Unity Game Development](https://amzn.to/3vBKCTp) +* [Unity in Action: Multiplatform Game Development in C#](https://amzn.to/3THO6vw) +* [Component(Game Programming Patterns)](https://gameprogrammingpatterns.com/component.html) +* [Component pattern - game programming series](https://www.youtube.com/watch?v=n92GBp2WMkg&ab_channel=Tutemic) \ No newline at end of file diff --git a/localization/zh-TW/composite-entity/README.md b/localization/zh-TW/composite-entity/README.md new file mode 100644 index 000000000000..d3eb37290191 --- /dev/null +++ b/localization/zh-TW/composite-entity/README.md @@ -0,0 +1,120 @@ +--- +title: Composite Entity +shortTitle: Composite Entity +category: Structural +language: zh +tag: + - Enterprise Integration Pattern +--- + +## 含義 + +複合實體模式用於對一組相關聯的持久化物件進行建模、描述和管理,用於取代對這組物件描述為單獨粒度的實體。 + +## 解釋 + +現例項子 + +> 對於一個控制檯物件,需要管理許多介面功能。透過使用複合實體模式,將訊息物件、訊號物件等依賴性物件組合在一起,直接使用單個物件對其進行控制。 + +簡單地說 + +> 複合實體模式允許使用一個統一物件來管理一組相互關聯的物件 + +**程式設計示例** + +我們需要一個通用的解決方案來解決上述的控制檯問題。我們引入了以下的通用複合物件。 + +```java +public abstract class DependentObject { + + T data; + + public void setData(T message) { + this.data = message; + } + + public T getData() { + return data; + } +} + +public abstract class CoarseGrainedObject { + + DependentObject[] dependentObjects; + + public void setData(T... data) { + IntStream.range(0, data.length).forEach(i -> dependentObjects[i].setData(data[i])); + } + + public T[] getData() { + return (T[]) Arrays.stream(dependentObjects).map(DependentObject::getData).toArray(); + } +} + +``` + +專用的 `console` 複合實體繼承自這個基類,如下所示。 + +```java +public class MessageDependentObject extends DependentObject { + +} + +public class SignalDependentObject extends DependentObject { + +} + +public class ConsoleCoarseGrainedObject extends CoarseGrainedObject { + + @Override + public String[] getData() { + super.getData(); + return new String[]{ + dependentObjects[0].getData(), dependentObjects[1].getData() + }; + } + + public void init() { + dependentObjects = new DependentObject[]{ + new MessageDependentObject(), new SignalDependentObject()}; + } +} + +public class CompositeEntity { + + private final ConsoleCoarseGrainedObject console = new ConsoleCoarseGrainedObject(); + + public void setData(String message, String signal) { + console.setData(message, signal); + } + + public String[] getData() { + return console.getData(); + } +} +``` + +現在我們使用 `console` 複合實體來進行訊息物件、訊號物件的分配。 + +```java +var console = new CompositeEntity(); +console.init(); +console.setData("No Danger", "Green Light"); +Arrays.stream(console.getData()).forEach(LOGGER::info); +console.setData("Danger", "Red Light"); +Arrays.stream(console.getData()).forEach(LOGGER::info); +``` + +## 類圖 + +![alt text](./etc/composite_entity.urm.png "Composite Entity Pattern") + +## 適用場景 + +複合實體模式適用於以下場景: + +* 你想要透過一個物件來管理多個依賴物件,已調整物件之間的細化程度。同時將依賴物件的生命週期託管到這個粗粒度的複合實體物件。 +## 引用 + +* [Composite Entity Pattern in wikipedia](https://en.wikipedia.org/wiki/Composite_entity_pattern) \ No newline at end of file diff --git a/localization/zh-TW/composite-entity/etc/composite_entity.urm.png b/localization/zh-TW/composite-entity/etc/composite_entity.urm.png new file mode 100644 index 000000000000..d6c29a718837 Binary files /dev/null and b/localization/zh-TW/composite-entity/etc/composite_entity.urm.png differ diff --git a/localization/zh-TW/composite-view/README.md b/localization/zh-TW/composite-view/README.md new file mode 100644 index 000000000000..2560d9c045e8 --- /dev/null +++ b/localization/zh-TW/composite-view/README.md @@ -0,0 +1,133 @@ +--- +title: "Java 複合視圖模式:提升應用程式間的 UI 一致性" +shortTitle: 複合視圖 +description: "了解 Java 複合視圖設計模式如何管理複雜的階層式視圖。" +category: Structural +language: zh-TW +tag: + - Abstraction + - Enterprise patterns + - Object composition + - Presentation +--- + +## 複合視圖設計模式的目的 + +複合視圖模式的主要目標,是將物件組合成代表部分與整體階層的樹狀結構,讓用戶端能以一致方式處理個別物件與物件組合,簡化複雜階層式視圖的管理。 + +## 複合視圖模式的詳細說明與真實世界範例 + +真實世界範例 + +> Web 應用程式的儀表板就是複合視圖的例子。金融儀表板可能顯示股價圖、近期交易、帳戶餘額與新聞摘要,每個元件都能獨立更新與管理。透過複合視圖,這些元件被組合成統一的儀表板,便於重新排列或加入新元件,同時維持整體版面的一致管理。 + +簡單來說 + +> 複合視圖是由較小子視圖組成的主要視圖。複合視圖的版面依照範本配置,再由視圖管理器決定要加入哪些子視圖。 + +維基百科指出,複合視圖由多個原子子視圖組成,各元件可以動態加入整體,版面也能與內容分開管理,藉由模組化範本片段的加入與替換來重複使用視圖元件。 + +流程圖 + +![複合視圖流程圖](./etc/composite-view-flowchart.png) + +## Java 複合視圖模式的程式範例 + +新聞網站會依照使用者偏好顯示日期與新聞,並根據興趣替換不同的新聞摘要,若沒有偏好則顯示本地新聞。本範例使用 Tomcat 10.0.13,且需要 Tomcat 10 以上版本。 + +`AppServlet` 只負責將 GET 請求轉送到正確的 JSP;PUT、POST 與 DELETE 請求會顯示錯誤訊息。 + +```java +public class AppServlet extends HttpServlet { + private String destination = "newsDisplay.jsp"; + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + RequestDispatcher dispatcher = req.getRequestDispatcher(destination); + ClientPropertiesBean properties = new ClientPropertiesBean(req); + req.setAttribute("properties", properties); + dispatcher.forward(req, resp); + } +} +``` + +視圖管理由儲存使用者偏好的 JavaBean `ClientPropertiesBean` 負責: + +```java +public class ClientPropertiesBean implements Serializable { + private boolean worldNewsInterest; + private boolean sportsInterest; + private boolean businessInterest; + private boolean scienceNewsInterest; + private String name; + + public ClientPropertiesBean(HttpServletRequest req) { + worldNewsInterest = Boolean.parseBoolean(req.getParameter("world")); + sportsInterest = Boolean.parseBoolean(req.getParameter("sport")); + businessInterest = Boolean.parseBoolean(req.getParameter("bus")); + scienceNewsInterest = Boolean.parseBoolean(req.getParameter("sci")); + name = req.getParameter("name"); + } +} +``` + +`newsDisplay.jsp` 是新聞頁面的範本,會依照 JavaBean 中的偏好條件加入不同的原子子視圖: + +```html +

Welcome <%= propertiesBean.getName()%>

+ + + + + <% if(propertiesBean.isWorldNewsInterest()) { %> + + <% } else { %> + + <% } %> + + +
<%@include file="worldNews.jsp"%><%@include file="localNews.jsp"%>
+``` + +這個範本有三列:第一列一個元件、第二列兩個元件、第三列一個元件。`businessNews.jsp`、`localNews.jsp` 等子視圖會依請求參數條件加入。 + +### 如何使用 + +安裝 Tomcat 10 以上版本,將模組建置為 WAR 檔並部署到伺服器。若使用 IntelliJ,請在 Run 的設定中加入 Tomcat,並在 Deployment 中加入 `composite-view:war exploded`,確認輸出內容包含 `web` 目錄與模組編譯結果後執行。 + +## 何時使用複合視圖模式 + +* 需要表示物件的部分與整體階層。 +* 預期複合結構未來可能加入新元件。 +* 希望用戶端忽略物件組合與個別物件的差異,以一致方式處理整個結構。 + +## 複合視圖模式的實際應用 + +* GUI 中可包含其他元件的元件,例如包含面板、按鈕與文字欄位的視窗。 +* 文件結構,例如由列組成的表格,而列又由儲存格組成,所有元素都可視為統一階層中的成員。 + +## 複合視圖模式的優點與取捨 + +優點: + +* 容易加入新元件,因為複合節點與葉節點以一致方式處理。 +* 簡化用戶端程式碼,降低分別處理個別元素與複合結構的複雜度。 + +取捨: + +* 若所有物件都設計成複合物件,可能造成過度泛化與額外複雜度。 +* 限制複合結構只能包含特定類型的元件可能較困難。 + +## 相關 Java 設計模式 + +* [組合](https://java-design-patterns.com/patterns/composite/):複合視圖的基礎結構模式。 +* [裝飾者](https://java-design-patterns.com/patterns/decorator/):不修改原視圖就能增加個別視圖的行為。 +* [享元](https://java-design-patterns.com/patterns/flyweight/):管理大量相似視圖物件的記憶體使用量。 +* 視圖輔助器:將視圖邏輯與商業邏輯分離。 + +## 參考資料與致謝 + +* [Head First Design Patterns](https://amzn.to/3xfntGJ) +* [Patterns of Enterprise Application Architecture](https://amzn.to/49jpQG3) +* [Core J2EE Patterns - Composite View(Oracle)](https://www.oracle.com/java/technologies/composite-view.html) \ No newline at end of file diff --git a/localization/zh-TW/composite-view/etc/composite-view-flowchart.png b/localization/zh-TW/composite-view/etc/composite-view-flowchart.png new file mode 100644 index 000000000000..bee1d8d9b006 Binary files /dev/null and b/localization/zh-TW/composite-view/etc/composite-view-flowchart.png differ diff --git a/localization/zh-TW/composite/README.md b/localization/zh-TW/composite/README.md new file mode 100644 index 000000000000..55beeab69027 --- /dev/null +++ b/localization/zh-TW/composite/README.md @@ -0,0 +1,173 @@ +--- +title: Composite +shortTitle: Composite +category: Structural +language: zh +tag: + - Gang of Four +--- + +## 目的 + +將物件組合成樹結構以表示部分整體層次結構。 組合可以使客戶統一對待單個物件和組合物件。 + +## 解釋 + +真實世界例子 + +> 每個句子由單片語成,單詞又由字元組成。這些物件中的每一個都是可列印的,它們可以在它們之前或之後列印一些內容,例如句子始終以句號結尾,單詞始終在其前面有空格。 + +通俗的說 + +> 組合模式使客戶能夠以統一的方式對待各個物件。 + +維基百科說 + +> 在軟體工程中,組合模式是一種分割槽設計模式。組合模式中,一組物件將像一個物件的單獨例項一樣被對待。組合的目的是將物件“組成”樹狀結構,以表示部分整體層次結構。實現組合模式可使客戶統一對待單個物件和組合物件。 + +**程式示例** + +使用上面的句子例子。 這裡我們有基類`LetterComposite`和不同的可列印型別`Letter`,`Word`和`Sentence`。 + +```java +public abstract class LetterComposite { + + private final List children = new ArrayList<>(); + + public void add(LetterComposite letter) { + children.add(letter); + } + + public int count() { + return children.size(); + } + + protected void printThisBefore() { + } + + protected void printThisAfter() { + } + + public void print() { + printThisBefore(); + children.forEach(LetterComposite::print); + printThisAfter(); + } +} + +public class Letter extends LetterComposite { + + private final char character; + + public Letter(char c) { + this.character = c; + } + + @Override + protected void printThisBefore() { + System.out.print(character); + } +} + +public class Word extends LetterComposite { + + public Word(List letters) { + letters.forEach(this::add); + } + + public Word(char... letters) { + for (char letter : letters) { + this.add(new Letter(letter)); + } + } + + @Override + protected void printThisBefore() { + System.out.print(" "); + } +} + +public class Sentence extends LetterComposite { + + public Sentence(List words) { + words.forEach(this::add); + } + + @Override + protected void printThisAfter() { + System.out.print("."); + } +} +``` + +然後我們有一個訊息攜帶者來攜帶訊息。 + +```java +public class Messenger { + + LetterComposite messageFromOrcs() { + + var words = List.of( + new Word('W', 'h', 'e', 'r', 'e'), + new Word('t', 'h', 'e', 'r', 'e'), + new Word('i', 's'), + new Word('a'), + new Word('w', 'h', 'i', 'p'), + new Word('t', 'h', 'e', 'r', 'e'), + new Word('i', 's'), + new Word('a'), + new Word('w', 'a', 'y') + ); + + return new Sentence(words); + + } + + LetterComposite messageFromElves() { + + var words = List.of( + new Word('M', 'u', 'c', 'h'), + new Word('w', 'i', 'n', 'd'), + new Word('p', 'o', 'u', 'r', 's'), + new Word('f', 'r', 'o', 'm'), + new Word('y', 'o', 'u', 'r'), + new Word('m', 'o', 'u', 't', 'h') + ); + + return new Sentence(words); + + } + +} +``` + +然後它可以這樣使用: + +```java +var orcMessage = new Messenger().messageFromOrcs(); +orcMessage.print(); // Where there is a whip there is a way. +var elfMessage = new Messenger().messageFromElves(); +elfMessage.print(); // Much wind pours from your mouth. +``` + +## 類圖 + +![alt text](./etc/composite.urm.png "Composite class diagram") + +## 適用性 + +使用組合模式當 + +* 你想要表示物件的整體層次結構 +* 你希望客戶能夠忽略組合物件和單個物件之間的差異。 客戶將統一對待組合結構中的所有物件。 + +## 真實世界例子 + +* [java.awt.Container](http://docs.oracle.com/javase/8/docs/api/java/awt/Container.html) and [java.awt.Component](http://docs.oracle.com/javase/8/docs/api/java/awt/Component.html) +* [Apache Wicket](https://github.com/apache/wicket) component tree, see [Component](https://github.com/apache/wicket/blob/91e154702ab1ff3481ef6cbb04c6044814b7e130/wicket-core/src/main/java/org/apache/wicket/Component.java) and [MarkupContainer](https://github.com/apache/wicket/blob/b60ec64d0b50a611a9549809c9ab216f0ffa3ae3/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java) + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/zh-TW/composite/etc/composite.urm.png b/localization/zh-TW/composite/etc/composite.urm.png new file mode 100644 index 000000000000..93c160f6450a Binary files /dev/null and b/localization/zh-TW/composite/etc/composite.urm.png differ diff --git a/localization/zh-TW/context-object/README.md b/localization/zh-TW/context-object/README.md new file mode 100644 index 000000000000..8bde59d0f02f --- /dev/null +++ b/localization/zh-TW/context-object/README.md @@ -0,0 +1,150 @@ +--- +title: "Java 情境物件模式:簡化情境資料的存取" +shortTitle: 情境物件 +description: "了解情境物件模式如何封裝使用者或請求相關的狀態與行為,讓應用程式元件與環境複雜度解耦。" +category: Behavioral +language: zh-TW +tags: + - Context + - Decoupling + - Encapsulation + - Session management +--- + +## 又稱為 + +* Context +* 情境封裝 +* 情境持有者 +* 封裝情境 + +## 情境物件設計模式的目的 + +封裝與使用者或目前處理的請求相關的情境狀態與行為,使 Java 應用程式元件不必直接面對環境複雜度,並有效管理應用程式情境。 + +## 情境物件模式的詳細說明與真實世界範例 + +真實世界範例 + +> 忙碌的機場中,多個服務都需要在旅途中存取與共享旅客資訊。機場不讓每個服務分別要求與傳遞資料,而是使用「旅客情境物件」保存身分、航班細節與偏好。報到、安檢、登機與客服服務都能依需要讀取或更新它,既保持一致,也避免服務緊密耦合。 + +簡單來說 + +> 建立物件儲存與管理情境資料,並在 Java 應用程式需要時傳遞此情境物件,讓程式更解耦、更乾淨。 + +[Core J2EE Patterns](http://corej2eepatterns.com/ContextObject.htm)指出: + +> 使用情境物件,以與協定無關的方式封裝狀態,並在整個應用程式中共享。 + +序列圖 + +![情境物件序列圖](./etc/context-object-sequence-diagram.png) + +## Java 情境物件的程式範例 + +多層 Java 應用程式中的 A、B、C 層會從共享情境取得不同資訊。逐一傳遞每筆資料效率不佳,因此使用情境物件集中保存與傳遞。 + +```java +@Getter +@Setter +public class ServiceContext { + String accountService; + String sessionService; + String searchService; +} + +public class ServiceContextFactory { + public static ServiceContext createContext() { + return new ServiceContext(); + } +} +``` + +第一層建立情境,後續層取得目前層的情境並繼續填入資料: + +```java +@Getter +public class LayerA { + private static ServiceContext context; + public LayerA() { context = ServiceContextFactory.createContext(); } + public void addAccountInfo(String accountService) { + context.setACCOUNT_SERVICE(accountService); + } +} + +@Getter +public class LayerB { + private final ServiceContext context; + public LayerB(LayerA layerA) { context = layerA.getContext(); } + public void addSessionInfo(String sessionService) { + context.setSESSION_SERVICE(sessionService); + } +} + +@Getter +public class LayerC { + private final ServiceContext context; + public LayerC(LayerB layerB) { context = layerB.getContext(); } + public void addSearchInfo(String searchService) { + context.setSEARCH_SERVICE(searchService); + } +} +``` + +情境物件在各層之間傳遞,且保留先前層加入的資訊: + +```java +public static void main(String[] args) { + var layerA = new LayerA(); + layerA.addAccountInfo("SERVICE"); + logContext(layerA.getContext()); + var layerB = new LayerB(layerA); + layerB.addSessionInfo("SERVICE"); + logContext(layerB.getContext()); + var layerC = new LayerC(layerB); + layerC.addSearchInfo("SERVICE"); + logContext(layerC.getContext()); +} +``` + +程式輸出中三次記錄會顯示同一個 `ServiceContext` 實例。 + +## 何時使用情境物件模式 + +* 需要抽象與封裝情境資訊,避免環境專屬程式碼污染商業邏輯。 +* Web 應用程式需要封裝請求專屬資訊,讓整個應用程式都能存取。 +* 分散式系統需要傳遞工作情境、使用者偏好或安全憑證。 + +## 情境物件模式的實際應用 + +* Web 框架封裝 HTTP 請求與回應、工作階段及其他請求資料。 +* 企業 Java 應用程式在不同層與服務間傳遞交易資訊、安全憑證與使用者設定。 +* [Spring ApplicationContext](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationContext.html) +* [Oracle SecurityContext](https://docs.oracle.com/javaee/7/api/javax/ws/rs/core/SecurityContext.html) +* [Oracle ServletContext](https://docs.oracle.com/javaee/6/api/javax/servlet/ServletContext.html) + +## 情境物件模式的優點與取捨 + +優點: + +* 解耦:元件與服務不依賴特定執行環境,提高模組化與可維護性。 +* 集中:情境資訊集中在一處,容易管理、存取與除錯。 +* 彈性:可依環境或需求變化彈性管理情境。 + +取捨: + +* 若實作效率不佳,加入情境物件可能增加效能負擔。 +* 設計不當時可能變成難以管理與理解的龐大單體物件。 + +## 相關 Java 設計模式 + +* [單例](https://java-design-patterns.com/patterns/singleton/):情境物件常以單例實作,提供全域存取點。 +* [策略](https://java-design-patterns.com/patterns/strategy/):可依封裝的情境使用策略調整行為。 +* [裝飾者](https://java-design-patterns.com/patterns/decorator/):可動態為情境物件加入責任。 + +## 參考資料與致謝 + +* [Core J2EE Design Patterns](https://amzn.to/3IhcY9w) +* [Context Object(Core J2EE Patterns)](http://corej2eepatterns.com/ContextObject.htm) +* [The Encapsulate Context Pattern](https://accu.org/journals/overload/12/63/kelly_246/) +* [Context Object - A Design Pattern for Efficient Information Sharing](https://www.dre.vanderbilt.edu/~schmidt/PDF/Context-Object-Pattern.pdf) \ No newline at end of file diff --git a/localization/zh-TW/context-object/etc/context-object-sequence-diagram.png b/localization/zh-TW/context-object/etc/context-object-sequence-diagram.png new file mode 100644 index 000000000000..f8cc87ff38e2 Binary files /dev/null and b/localization/zh-TW/context-object/etc/context-object-sequence-diagram.png differ diff --git a/localization/zh-TW/converter/README.md b/localization/zh-TW/converter/README.md new file mode 100644 index 000000000000..a765618bb672 --- /dev/null +++ b/localization/zh-TW/converter/README.md @@ -0,0 +1,100 @@ +--- +title: Converter +shortTitle: Converter +category: Creational +language: zh +tag: + - Decoupling +--- + +## 目的 + +轉換器模式的目的是提供相應型別之間雙向轉換的通用方法,允許進行乾淨的實現,而型別之間無需相互瞭解。此外,Converter模式引入了雙向集合對映,從而將樣板程式碼減少到最少。 + +## 解釋 + +真實世界例子 + +> 在真實的應用中經常有這種情況,資料庫層包含需要被轉換成業務邏輯層DTO來使用的實體。對於潛在的大量類進行類似的對映,我們需要一種通用的方法來實現這一點。 + +通俗的說 + +> 轉換器模式讓一個類的例項對映成另一個類的例項變得簡單 + +**程式示例** + +我們需要一個通用的方案來解決對映問題。讓我們來介紹一個通用的轉換器。 + +```java +public class Converter { + + private final Function fromDto; + private final Function fromEntity; + + public Converter(final Function fromDto, final Function fromEntity) { + this.fromDto = fromDto; + this.fromEntity = fromEntity; + } + + public final U convertFromDto(final T dto) { + return fromDto.apply(dto); + } + + public final T convertFromEntity(final U entity) { + return fromEntity.apply(entity); + } + + public final List createFromDtos(final Collection dtos) { + return dtos.stream().map(this::convertFromDto).collect(Collectors.toList()); + } + + public final List createFromEntities(final Collection entities) { + return entities.stream().map(this::convertFromEntity).collect(Collectors.toList()); + } +} +``` + +專屬的轉換器像下面一樣從基類繼承。 + +```java +public class UserConverter extends Converter { + + public UserConverter() { + super(UserConverter::convertToEntity, UserConverter::convertToDto); + } + + private static UserDto convertToDto(User user) { + return new UserDto(user.getFirstName(), user.getLastName(), user.isActive(), user.getUserId()); + } + + private static User convertToEntity(UserDto dto) { + return new User(dto.getFirstName(), dto.getLastName(), dto.isActive(), dto.getEmail()); + } + +} +``` + +現在,在User和UserDto之間的對映變得輕而易舉。 + +```java +var userConverter = new UserConverter(); +var dtoUser = new UserDto("John", "Doe", true, "whatever[at]wherever.com"); +var user = userConverter.convertFromDto(dtoUser); +``` + +## 類圖 + +![alt text](./etc/converter.png "Converter Pattern") + +## 適用性 + +在下面這些情況下使用轉換器模式: + +* 如果你的型別在邏輯上相互對應,並需要在它們之間轉換實體 +* 當你想根據上下文提供不同的型別轉換方式時 +* 每當你引入DTO(資料傳輸物件)時你可能都需要將其轉換為 + DO + +## 鳴謝 + +* [Converter](http://www.xsolve.pl/blog/converter-pattern-in-java-8/) diff --git a/localization/zh-TW/converter/etc/converter.png b/localization/zh-TW/converter/etc/converter.png new file mode 100644 index 000000000000..01435ef5ae29 Binary files /dev/null and b/localization/zh-TW/converter/etc/converter.png differ diff --git a/localization/zh-TW/crtp/README.md b/localization/zh-TW/crtp/README.md new file mode 100644 index 000000000000..0cc27fb2c78c --- /dev/null +++ b/localization/zh-TW/crtp/README.md @@ -0,0 +1,135 @@ +--- +title: Curiously Recurring Template Pattern +shortTitle: Curiously Recurring Template Pattern +language: zh +category: Structural +tag: +- Extensibility +- Instantiation +--- + +## 名稱/分類 + +Curiously Recurring Template Pattern,CRTP,奇異遞迴模板模式 + +## 別名 + +遞迴型別繫結,遞迴泛型 + +## 目的 + +允許派生元件從與派生型別相容的基本元件繼承某些功能。 + +## 解釋 + +真實世界的例子 + +> 對於正在策劃賽事的綜合格鬥推廣活動來說,確保在相同重量級的運動員之間組織比賽至關重要。這樣可以防止體型明顯不同的拳手之間的不匹配,例如重量級拳手與雛量級拳手的對決。 + +用通俗的話來講 + +> 使型別中的某些方法接受特定於其子型別的引數。 + +維基百科介紹 + +> 奇異遞迴模板模式(curiously recurring template pattern,CRTP)是C++模板程式設計時的一種慣用法:其中類X派生自使用X本身作為模板引數的類别範本例項化。 + +**程式示例** + +讓我們來定義通用介面Fighter + +```java +public interface Fighter { + + void fight(T t); + +} +``` + +MmaFighter類用於例項化按重量級別區分的拳手 + +``` Java +public class MmaFighter> implements Fighter { + + private final String name; + private final String surname; + private final String nickName; + private final String speciality; + + public MmaFighter(String name, String surname, String nickName, String speciality) { + this.name = name; + this.surname = surname; + this.nickName = nickName; + this.speciality = speciality; + } + + @Override + public void fight(T opponent) { + LOGGER.info("{} is going to fight against {}", this, opponent); + } + + @Override + public String toString() { + return name + " \"" + nickName + "\" " + surname; + } +``` + +以下是 MmaFighter 的一些子型別 + +```Java +class MmaBantamweightFighter extends MmaFighter { + + public MmaBantamweightFighter(String name, String surname, String nickName, String speciality) { + super(name, surname, nickName, speciality); + } + +} + +public class MmaHeavyweightFighter extends MmaFighter { + + public MmaHeavyweightFighter(String name, String surname, String nickName, String speciality) { + super(name, surname, nickName, speciality); + } + +} +``` + +允許拳手與相同重量級的對手交手,如果對手是不同重量級,則會出現錯誤 + +``` Java +MmaBantamweightFighter fighter1 = new MmaBantamweightFighter("Joe", "Johnson", "The Geek", "Muay Thai"); +MmaBantamweightFighter fighter2 = new MmaBantamweightFighter("Ed", "Edwards", "The Problem Solver", "Judo"); +fighter1.fight(fighter2); // This is fine + +MmaHeavyweightFighter fighter3 = new MmaHeavyweightFighter("Dave", "Davidson", "The Bug Smasher", "Kickboxing"); +MmaHeavyweightFighter fighter4 = new MmaHeavyweightFighter("Jack", "Jackson", "The Pragmatic", "Brazilian Jiu-Jitsu"); +fighter3.fight(fighter4); // This is fine too + +fighter1.fight(fighter3); // This will raise a compilation error +``` + +## 類圖 + +![alt text](etc/crtp.png "CRTP class diagram") + +## 適用性 + +在以下情況下使用CRTP + +* 在物件層次結構中連結方法時存在型別衝突 +* 你想使用一個引數化的類方法,該方法可以接受類的子類作為引數,從而可以應用於繼承自類的物件 +* 你希望某些方法僅適用於相同型別的例項,例如實現相互比較。 + +## 教程 + +* [The NuaH Blog](https://nuah.livejournal.com/328187.html) +* Yogesh Umesh Vaity answer to [What does "Recursive type bound" in Generics mean?](https://stackoverflow.com/questions/7385949/what-does-recursive-type-bound-in-generics-mean) + +## 已知用途 + +* [java.lang.Enum](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Enum.html) + +## 鳴謝 + +* [How do I decrypt "Enum>"?](http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ106) +* Chapter 5 Generics, Item 30 in [Effective Java](https://www.amazon.com/gp/product/0134685997/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0134685997&linkCode=as2&tag=javadesignpat-20&linkId=4e349f4b3ff8c50123f8147c828e53eb) diff --git a/localization/zh-TW/crtp/etc/crtp.png b/localization/zh-TW/crtp/etc/crtp.png new file mode 100644 index 000000000000..a348c8af6175 Binary files /dev/null and b/localization/zh-TW/crtp/etc/crtp.png differ diff --git a/localization/zh-TW/curiously-recurring-template-pattern/README.md b/localization/zh-TW/curiously-recurring-template-pattern/README.md new file mode 100644 index 000000000000..ca8762a14f13 --- /dev/null +++ b/localization/zh-TW/curiously-recurring-template-pattern/README.md @@ -0,0 +1,132 @@ +--- +title: "Java 奇異遞迴模板模式:以獨特方式運用多型" +shortTitle: 奇異遞迴模板模式(CRTP) +description: "了解 Java 的奇異遞迴模板模式,透過編譯期多型實現高效的方法覆寫與型別安全行為。" +language: zh-TW +category: Structural +tag: + - Code simplification + - Extensibility + - Generic + - Idiom + - Instantiation + - Polymorphism + - Recursion +--- + +## 又稱為 + +* CRTP +* Mixin 繼承 +* 遞迴型別界限 +* 遞迴泛型 +* 靜態多型 + +## 奇異遞迴模板模式的目的 + +奇異遞迴模板模式(CRTP)是 Java 中用來實現靜態多型的設計模式。類別模板繼承自己類別的模板實例,讓方法覆寫與編譯期多型行為更有效率。 + +## 詳細說明與真實世界範例 + +真實世界範例 + +> 圖書館管理書籍、DVD 與雜誌等媒體。每種媒體都有自己的屬性與行為,但都共用借閱與歸還功能。透過 Java 的 CRTP,可以建立包含共用方法的 `MediaItem` 基底模板,讓 `Book`、`DVD` 與 `Magazine` 以自身作為模板參數繼承它,避免虛擬方法的負擔。 + +簡單來說 + +> CRTP 讓型別中的方法能接受特定子型別的參數,在編譯期提供更有效率且型別安全的多型行為。 + +維基百科指出: + +> 奇異遞迴模板模式原本是 C++ 的慣用法,類別 X 會以自己作為模板參數,繼承類別模板的實例。 + +流程圖 + +![奇異遞迴模板模式流程圖](./etc/crtp-flowchart.png) + +## Java CRTP 的程式範例 + +綜合格鬥賽事必須確保選手與相同量級的對手比賽,避免重量級選手對上雛量級選手。 + +```java +public interface Fighter { + void fight(T opponent); +} + +@Slf4j +@Data +public class MmaFighter> implements Fighter { + private final String name; + private final String surname; + private final String nickName; + private final String speciality; + + @Override + public void fight(T opponent) { + LOGGER.info("{} is going to fight against {}", this, opponent); + } +} + +class MmaBantamweightFighter extends MmaFighter { + public MmaBantamweightFighter(String name, String surname, + String nickName, String speciality) { + super(name, surname, nickName, speciality); + } +} + +public class MmaHeavyweightFighter extends MmaFighter { + public MmaHeavyweightFighter(String name, String surname, + String nickName, String speciality) { + super(name, surname, nickName, speciality); + } +} +``` + +選手只能與相同量級的對手比賽;若量級不同,編譯器會拒絕該呼叫。 + +```java +MmaBantamweightFighter fighter1 = new MmaBantamweightFighter( + "Joe", "Johnson", "The Geek", "Muay Thai"); +MmaBantamweightFighter fighter2 = new MmaBantamweightFighter( + "Ed", "Edwards", "The Problem Solver", "Judo"); +fighter1.fight(fighter2); +``` + +## 何時在 Java 中使用 CRTP + +* 需要透過繼承擴充類別,但為了效率偏好編譯期多型。 +* 想避免虛擬函式呼叫負擔,同時保有多型行為。 +* 使用模板元程式設計,在編譯期選擇函式或策略的實作。 +* 物件階層鏈式方法呼叫時遇到型別衝突。 +* 方法只應接受與目前類別相同型別的實例,例如互相比較。 + +## CRTP 的實際應用 + +* 在模板函式庫中實作編譯期多型介面。 +* 在數學運算、嵌入式系統與即時處理等效能關鍵函式庫中提升程式碼重用。 +* Java 函式庫中 `Cloneable` 介面的實作。 + +## CRTP 的優點與取捨 + +優點: + +* 消除虛擬函式呼叫負擔,提高效能。 +* 安全重用基底類別程式碼,不必承受多重繼承的風險。 +* 編譯期多型情境中具有更高彈性與可擴充性。 + +取捨: + +* 模板與繼承互動複雜,理解與除錯較困難。 +* 每次模板實例化都可能產生新類別,造成程式碼膨脹。 +* 相較於執行期多型較不靈活,行為必須在編譯期決定。 + +## 相關 Java 設計模式 + +* [工廠方法](https://java-design-patterns.com/patterns/factory-method/):可搭配 CRTP,在未知具體型別時建立衍生類別。 +* [策略](https://java-design-patterns.com/patterns/strategy/):CRTP 可實作編譯期策略選擇。 +* [模板方法](https://java-design-patterns.com/patterns/template-method/):結構相似,但模板方法在執行期變化行為,CRTP 則在編譯期變化。 + +## 參考資料與致謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/3cGk2Jz) \ No newline at end of file diff --git a/localization/zh-TW/curiously-recurring-template-pattern/etc/crtp-flowchart.png b/localization/zh-TW/curiously-recurring-template-pattern/etc/crtp-flowchart.png new file mode 100644 index 000000000000..a205e1638ca0 Binary files /dev/null and b/localization/zh-TW/curiously-recurring-template-pattern/etc/crtp-flowchart.png differ diff --git a/localization/zh-TW/currying/README.md b/localization/zh-TW/currying/README.md new file mode 100644 index 000000000000..963b15af6eba --- /dev/null +++ b/localization/zh-TW/currying/README.md @@ -0,0 +1,140 @@ +--- +title: "Java 柯里化模式:提升函式彈性與重用性" +shortTitle: 柯里化 +description: "了解 Java 中的柯里化,將多參數函式拆成一連串單參數函式,並探索其應用、優點與範例。" +category: Functional +language: zh-TW +tag: + - Code simplification + - Functional decomposition + - Generic + - Immutable +--- + +## 又稱為 + +* 部分函式應用 + +## 柯里化設計模式的目的 + +柯里化將接受多個參數的函式拆成一連串各自只接受一個參數的函式。這是函數式程式設計的重要技巧,可透過部分套用參數建立高階函式,讓 Java 程式更模組化、可重用且易維護。 + +## 柯里化模式的詳細說明與真實世界範例 + +真實世界範例 + +> 柯里化可以比喻為工廠的裝配線。每個工作站只執行一項任務,例如安裝引擎、烤漆或裝輪子;每站接收部分完成的汽車,處理後傳給下一站。柯里化也將多參數函式拆成一連串單參數函式,逐步完成複雜工作。 + +簡單來說 + +> 將接受多個參數的函式拆成多個各自接受單一參數的函式。 + +維基百科指出: + +> 在數學與電腦科學中,柯里化是將接受多個參數的函式轉換成一連串函式族的技巧,每個函式只接受一個參數。 + +序列圖 + +![柯里化序列圖](./etc/currying-sequence-diagram.png) + +## Java 柯里化模式的程式範例 + +圖書館員想建立不同類型與作者的書籍。柯里化可以建立柯里化的書籍建構函式,再透過部分套用產生專用函式。 + +```java +public class Book { + private final Genre genre; + private final String author; + private final String title; + private final LocalDate publicationDate; + + Book(Genre genre, String author, String title, LocalDate publicationDate) { + this.genre = genre; + this.author = author; + this.title = title; + this.publicationDate = publicationDate; + } +} + +public enum Genre { FANTASY, HORROR, SCI_FI } + +Book createBook(Genre genre, String author, String title, LocalDate date) { + return new Book(genre, author, title, date); +} +``` + +若只想建立奇幻類書籍,每次都傳入 `FANTASY` 很重複,為每種分類建立方法又不切實際,因此可以使用柯里化函式: + +```java +static Function>>> bookCreator = genre -> author -> title + -> publicationDate -> new Book(genre, author, title, publicationDate); + +Function>> fantasyBookFunc + = bookCreator.apply(Genre.FANTASY); +``` + +上面的型別簽章不易閱讀,可以搭配建造者模式與函數介面改善: + +```java +public static AddGenre builder() { + return genre -> author -> title -> publicationDate + -> new Book(genre, author, title, publicationDate); +} + +public interface AddGenre { AddAuthor withGenre(Genre genre); } +public interface AddAuthor { AddTitle withAuthor(String author); } +public interface AddTitle { AddPublicationDate withTitle(String title); } +public interface AddPublicationDate { Book withPublicationDate(LocalDate date); } +``` + +函式會依序加入分類、作者、標題與出版日期: + +```java +Book book = Book.builder().withGenre(Genre.FANTASY) + .withAuthor("Author") + .withTitle("Title") + .withPublicationDate(LocalDate.of(2000, 7, 2)); +``` + +部分套用也能建立專用的書籍建構函式,例如先固定作者或分類,再建立多本書。 + +## 何時在 Java 中使用柯里化模式 + +* Java 中需要預先固定部分參數再呼叫函式時。 +* 函數式程式設計中,需要簡化接受多個參數的函式。 +* 想將函式拆成簡單的一元函式,提升重用性、組合性與模組化。 + +## 柯里化模式的實際應用 + +* Haskell、Scala 與 JavaScript 等函數式程式語言。 +* Java 8 引入 Lambda 與串流後的函數式程式設計。 +* UI 事件處理,需要在事件發生時觸發帶有特定參數的函式。 +* 需要以多個參數進行設定的 API。 + +## 柯里化模式的優點與取捨 + +優點: + +* 可由通用函式建立專用函式,提高函式重用性。 +* 將複雜函式拆成單參數函式,提高可讀性與可維護性。 +* 促進函式組合,產生更宣告式且精簡的程式碼。 + +取捨: + +* 建立額外閉包可能造成效能負擔。 +* 增加函式呼叫層次,讓除錯更困難。 +* 不熟悉函數式概念的開發者可能覺得不直觀。 +* Java 中參數很多的柯里化函式會有冗長的型別簽章。 + +## 相關 Java 設計模式 + +* 函式組合:常與柯里化一起使用,讓程式碼更精簡易讀。 +* [裝飾者](https://java-design-patterns.com/patterns/decorator/):柯里化與裝飾者都包含包裝功能的概念。 +* [工廠](https://java-design-patterns.com/patterns/factory/):可建立預先固定部分參數的工廠函式。 + +## 參考資料與致謝 + +* [Currying in Java(Baeldung)](https://www.baeldung.com/java-currying) +* [What Is Currying in Programming](https://towardsdatascience.com/what-is-currying-in-programming-56fd57103431) +* [Why the fudge should I use currying?](https://medium.com/dailyjs/why-the-fudge-should-i-use-currying-84e4000c8743) \ No newline at end of file diff --git a/localization/zh-TW/currying/etc/currying-sequence-diagram.png b/localization/zh-TW/currying/etc/currying-sequence-diagram.png new file mode 100644 index 000000000000..7c3300263566 Binary files /dev/null and b/localization/zh-TW/currying/etc/currying-sequence-diagram.png differ diff --git a/localization/zh-TW/dao-factory/README.md b/localization/zh-TW/dao-factory/README.md new file mode 100644 index 000000000000..960991196c0f --- /dev/null +++ b/localization/zh-TW/dao-factory/README.md @@ -0,0 +1,192 @@ +--- +title: "DAO 工廠模式:可彈性切換資料來源的資料存取層" +shortTitle: DAO 工廠 +description: "了解 Java 中結合資料存取物件與抽象工廠模式的 DAO 工廠,並學習其目的、適用性、優點與實際用途。" +category: Structural +language: zh-TW +tag: + - Abstraction + - Data access + - Layer architecture + - Persistence +--- + +## 又稱為 + +* DAO 工廠 +* 使用抽象工廠實作的資料存取物件策略工廠 + +## DAO 工廠設計模式的目的 + +DAO 工廠結合資料存取物件與抽象工廠模式,將商業邏輯與資料存取邏輯分離,並提高切換不同資料來源時的彈性。 + +## DAO 工廠模式的詳細說明與真實世界範例 + +真實世界範例 + +> DAO 工廠可以比喻為多語言客服中心。銀行服務說英語、法語與西班牙語的客戶;來電時,系統先辨識偏好語言,再將電話轉給對應團隊。每個團隊遵守相同公司政策,但以特定語言處理互動。 +> +> 同樣地,DAO 工廠會依資料來源(例如 MySQL 或 MongoDB)選擇正確的 DAO 實作集合。每個工廠都回傳符合相同介面的 DAO,讓應用程式能以一致方式使用支援的資料庫,而不必修改商業邏輯。 + +簡單來說 + +> DAO 工廠抽象化資料存取物件的建立,讓你從中央工廠取得特定 DAO,而不必了解底層實作。切換資料庫或儲存機制時,程式更容易維護與調整。 + +維基百科指出,DAO 提供資料庫或其他持久化機制的抽象介面,將應用程式呼叫映射到持久化層而不暴露資料庫細節。DAO 工廠則負責產生所需的 DAO 實作。 + +類別圖 + +![DAO 工廠類別圖](./etc/dao-factory.png "DAO 工廠類別圖") + +## Java DAO 工廠的程式範例 + +本範例的持久化物件是 `Customer`。應用程式可以使用 H2 記憶體內關聯式資料庫、MongoDB 或 JSON 檔案三種資料來源。 + +```java +public enum DataSourceType { H2, Mongo, FlatFile } + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class Customer implements Serializable { + private T id; + private String name; +} + +public interface CustomerDAO { + void save(Customer customer); + void update(Customer customer); + void delete(T id); + List> findAll(); + Optional> findById(T id); +} +``` + +每種資料來源都有對應的 `CustomerDAO` 實作: + +```java +public class H2CustomerDAO implements CustomerDAO { + private final DataSource dataSource; + public void save(Customer customer) { /* H2 儲存操作 */ } + public void update(Customer customer) { /* H2 更新操作 */ } + public void delete(Long id) { /* H2 刪除操作 */ } + public List> findAll() { /* H2 查詢全部 */ return List.of(); } + public Optional> findById(Long id) { return Optional.empty(); } +} + +public class MongoCustomerDAO implements CustomerDAO { + private final MongoCollection customerCollection; + // 使用 MongoDB 實作 CRUD 操作 +} + +public class FlatFileCustomerDAO implements CustomerDAO { + private final Path filePath; + private final Gson gson; + // 使用 JSON 檔案實作 CRUD 操作 +} +``` + +接著建立抽象 `DAOFactory`,由 `getDataSource` 選擇具體工廠,並由 `createCustomerDAO` 建立對應的 DAO: + +```java +public abstract class DAOFactory { + public static DAOFactory getDataSource(DataSourceType type) { + return switch (type) { + case H2 -> new H2DataSourceFactory(); + case Mongo -> new MongoDataSourceFactory(); + case FlatFile -> new FlatFileDataSourceFactory(); + }; + } + + public abstract CustomerDAO createCustomerDAO(); +} +``` + +H2 工廠建立 H2 資料來源與 DAO: + +```java +public class H2DataSourceFactory extends DAOFactory { + @Override + public CustomerDAO createCustomerDAO() { + return new H2CustomerDAO(createDataSource()); + } + + private DataSource createDataSource() { + var dataSource = new JdbcDataSource(); + dataSource.setURL("jdbc:h2:~/test"); + dataSource.setUser("sa"); + dataSource.setPassword(""); + return dataSource; + } +} +``` + +MongoDB 與 JSON 檔案工廠則分別建立 `MongoCustomerDAO` 與 `FlatFileCustomerDAO`,封裝各自的連線、集合、檔案路徑與序列化設定。 + +用戶端可以在執行期選擇資料來源,並以同一組 CRUD 方法操作資料: + +```java +DAOFactory daoFactory = DAOFactory.getDataSource(DataSourceType.H2); +CustomerDAO customerDAO = daoFactory.createCustomerDAO(); +performCreateCustomer(customerDAO, List.of(customer1, customer2)); +performUpdateCustomer(customerDAO, customerUpdate); +performDeleteCustomer(customerDAO, 3L); + +daoFactory = DAOFactory.getDataSource(DataSourceType.Mongo); +customerDAO = daoFactory.createCustomerDAO(); +performCreateCustomer(customerDAO, List.of(customer3, customer4)); + +daoFactory = DAOFactory.getDataSource(DataSourceType.FlatFile); +customerDAO = daoFactory.createCustomerDAO(); +performCreateCustomer(customerDAO, List.of(customer5, customer6)); +``` + +## 何時在 Java 中使用 DAO 工廠模式 + +* 應用程式需要支援多種儲存方式,且希望只少量修改商業邏輯。 +* 想將持久化邏輯抽象並隔離於核心應用程式邏輯。 +* 想讓資料存取層可插拔,容易加入新的儲存技術。 +* 想透過 DAO 的模擬實作簡化單元測試與相依性注入。 +* 執行期設定會決定要使用的資料來源。 + +## DAO 工廠模式的實際應用 + +* 企業 Java 應用程式在測試、開發與正式資料庫間切換。 +* Spring Data JPA 與儲存庫抽象。 +* 使用 SQL、NoSQL 或檔案儲存的微服務。 +* 支援 CSV、JSON 與資料庫匯入匯出的資料整合工具。 +* 需要支援多種資料庫類型的內部框架。 + +## DAO 工廠模式的優點與取捨 + +優點: + +* 資料來源邏輯抽象化,用戶端只依賴 DAO 介面。 +* 只需變更工廠設定即可切換 H2、MongoDB 或檔案等資料來源。 +* 各資料來源的儲存邏輯封裝在自己的 DAO 與工廠中,易於維護。 +* 共用 CRUD 邏輯可在不同實作與專案間重複使用。 +* DAO 與工廠容易模擬或替代,支援單元測試與相依性注入。 + +取捨: + +* 抽象 DAO 與多個工廠類別會增加架構複雜度。 +* 即使是簡單需求,也需要定義多個介面與實作,增加樣板程式碼。 +* 用戶端透過工廠間接取得 DAO,理解實際資料來源行為可能需要深入追蹤。 + +## 相關 Java 設計模式 + +* [工廠方法](https://java-design-patterns.com/patterns/factory-method/):DAO 工廠是以彈性方式建立 DAO 的工廠模式應用。 +* [抽象工廠](https://java-design-patterns.com/patterns/abstract-factory/):支援多種資料來源時,DAO 工廠可以作為抽象工廠。 +* [資料存取物件](https://java-design-patterns.com/patterns/data-access-object/):DAO 工廠管理的核心模式,分離資料存取與商業邏輯。 +* [單例](https://java-design-patterns.com/patterns/singleton/):可確保只有一個實例負責建立 DAO。 +* [服務定位器](https://java-design-patterns.com/patterns/service-locator/):可搭配 DAO 工廠有效取得 DAO 服務。 +* [相依性注入](https://java-design-patterns.com/patterns/dependency-injection/):Spring 等框架通常將 DAO 注入服務層,而不是從工廠取得。 + +## 參考資料與致謝 + +* [DAO Factory - J2EE Design Patterns Book](https://www.oreilly.com/library/view/j2ee-design-patterns/0596004273/re15.html) +* [DAO Factory patterns with Hibernate](http://www.giuseppeurso.eu/en/dao-factory-patterns-with-hibernate/) +* [Design Patterns - Java Means DURGA SOFT](https://www.scribd.com/document/407219980/2-DAO-Factory-Design-Pattern) +* [Generic DAO pattern - Hibernate](https://in.relation.to/2005/09/09/generic-dao-pattern-with-jdk-50/) \ No newline at end of file diff --git a/localization/zh-TW/dao-factory/etc/dao-factory.png b/localization/zh-TW/dao-factory/etc/dao-factory.png new file mode 100644 index 000000000000..d93547d79957 Binary files /dev/null and b/localization/zh-TW/dao-factory/etc/dao-factory.png differ diff --git a/localization/zh-TW/dao/README.md b/localization/zh-TW/dao/README.md new file mode 100644 index 000000000000..93eef8b2e4cc --- /dev/null +++ b/localization/zh-TW/dao/README.md @@ -0,0 +1,161 @@ +--- +title: Data Access Object +shortTitle: Data Access Object +category: Architectural +language: zh +tag: + - Data access +--- + +## 目的 + +物件為某種型別的資料庫或其他永續性機制提供了抽象介面。 + +## 解釋 + +真實世界例子 + +> 有一組客戶資料需要持久化到資料庫中。 我們需要整個額外的增刪改查操作以便操作客戶資料。 + +通俗的說 + +> DAO是我們透過基本永續性機制提供的介面。 + +維基百科說 + +> 在計算機軟體中,資料訪問物件(DAO)是一種模式,可為某種型別的資料庫或其他永續性機制提供抽象介面。 + +**程式示例** + +透過我們的客戶示例,下面是基本的`客戶`實體。 + +```java +public class Customer { + + private int id; + private String firstName; + private String lastName; + + public Customer(int id, String firstName, String lastName) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + } + // getters and setters -> + ... +} +``` + +這是`CustomerDao`介面及其兩個不同的實現。 + +Here's the `CustomerDao` interface and two different implementations for it. `InMemoryCustomerDao` +將簡單的客戶資料對映儲存在記憶體中 而`DBCustomerDao`是真正的RDBMS實現。 + +```java +public interface CustomerDao { + + Stream getAll() throws Exception; + + Optional getById(int id) throws Exception; + + boolean add(Customer customer) throws Exception; + + boolean update(Customer customer) throws Exception; + + boolean delete(Customer customer) throws Exception; +} + +public class InMemoryCustomerDao implements CustomerDao { + + private final Map idToCustomer = new HashMap<>(); + + // implement the interface using the map + ... +} + +public class DbCustomerDao implements CustomerDao { + + private static final Logger LOGGER = LoggerFactory.getLogger(DbCustomerDao.class); + + private final DataSource dataSource; + + public DbCustomerDao(DataSource dataSource) { + this.dataSource = dataSource; + } + + // implement the interface using the data source + ... +``` + +最後,這是我們使用DAO管理客戶資料的方式。 + +```java + final var dataSource = createDataSource(); + createSchema(dataSource); + final var customerDao = new DbCustomerDao(dataSource); + + addCustomers(customerDao); + log.info(ALL_CUSTOMERS); + try (var customerStream = customerDao.getAll()) { + customerStream.forEach((customer) -> log.info(customer.toString())); + } + log.info("customerDao.getCustomerById(2): " + customerDao.getById(2)); + final var customer = new Customer(4, "Dan", "Danson"); + customerDao.add(customer); + log.info(ALL_CUSTOMERS + customerDao.getAll()); + customer.setFirstName("Daniel"); + customer.setLastName("Danielson"); + customerDao.update(customer); + log.info(ALL_CUSTOMERS); + try (var customerStream = customerDao.getAll()) { + customerStream.forEach((cust) -> log.info(cust.toString())); + } + customerDao.delete(customer); + log.info(ALL_CUSTOMERS + customerDao.getAll()); + + deleteSchema(dataSource); +``` + +程式輸出: + +```java +customerDao.getAllCustomers(): +Customer{id=1, firstName='Adam', lastName='Adamson'} +Customer{id=2, firstName='Bob', lastName='Bobson'} +Customer{id=3, firstName='Carl', lastName='Carlson'} +customerDao.getCustomerById(2): Optional[Customer{id=2, firstName='Bob', lastName='Bobson'}] +customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@7cef4e59 +customerDao.getAllCustomers(): +Customer{id=1, firstName='Adam', lastName='Adamson'} +Customer{id=2, firstName='Bob', lastName='Bobson'} +Customer{id=3, firstName='Carl', lastName='Carlson'} +Customer{id=4, firstName='Daniel', lastName='Danielson'} +customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@2db0f6b2 +customerDao.getAllCustomers(): +Customer{id=1, firstName='Adam', lastName='Adamson'} +Customer{id=2, firstName='Bob', lastName='Bobson'} +Customer{id=3, firstName='Carl', lastName='Carlson'} +customerDao.getCustomerById(2): Optional[Customer{id=2, firstName='Bob', lastName='Bobson'}] +customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@12c8a2c0 +customerDao.getAllCustomers(): +Customer{id=1, firstName='Adam', lastName='Adamson'} +Customer{id=2, firstName='Bob', lastName='Bobson'} +Customer{id=3, firstName='Carl', lastName='Carlson'} +Customer{id=4, firstName='Daniel', lastName='Danielson'} +customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@6ec8211c +``` + +## 類圖 + +![alt text](./etc/dao.png "Data Access Object") + +## 適用性 + +在以下情況下,請使用資料訪問物件:: + +* 當你要鞏固如何訪問資料層時。 +* 當你要避免編寫多個資料檢索/持久層時。 + +## 鳴謝 + +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) diff --git a/localization/zh-TW/dao/etc/dao.png b/localization/zh-TW/dao/etc/dao.png new file mode 100644 index 000000000000..452e72ba10ac Binary files /dev/null and b/localization/zh-TW/dao/etc/dao.png differ diff --git a/localization/zh-TW/data-bus/README.md b/localization/zh-TW/data-bus/README.md new file mode 100644 index 000000000000..8eac739d7a4b --- /dev/null +++ b/localization/zh-TW/data-bus/README.md @@ -0,0 +1,29 @@ +--- +title: Data Bus +shortTitle: Data Bus +category: Architectural +language: zh +tag: + - Decoupling +--- + +## 含義 + +資料匯流排模式(譯者:實際上,就是 Event-Bus 訊息匯流排模式)允許在一個應用程式的元件之間收發訊息/事件,而不需要這些元件相互感知,它們只需要知道所傳送/接收的訊息/事件的型別即可。 + +## 類圖 +![data bus pattern uml diagram](./etc/data-bus.urm.png "Data Bus pattern") + +## 適用場景 +可以在以下場景使用資料匯流排模式: + +* 你希望由你的元件自己決定要接收哪些資訊/事件 +* 你希望實現多對多的通訊 +* 你希望你的元件不需要感知彼此 + +## 相關模式 +資料匯流排類似於以下設計模式: + +* 中介者模式(Mediator pattern),由資料匯流排成員自己決定是否要接受任何給定的訊息。 +* 觀察者模式(Observer pattern),但進一步支援了多對多的通訊。 +* 釋出/訂閱模式(Publish/Subscribe pattern),但是資料匯流排將釋出者和訂閱者解耦。 \ No newline at end of file diff --git a/localization/zh-TW/data-bus/etc/data-bus.urm.png b/localization/zh-TW/data-bus/etc/data-bus.urm.png new file mode 100644 index 000000000000..8bd2148174e4 Binary files /dev/null and b/localization/zh-TW/data-bus/etc/data-bus.urm.png differ diff --git a/localization/zh-TW/data-mapper/README.md b/localization/zh-TW/data-mapper/README.md new file mode 100644 index 000000000000..dab58542bb14 --- /dev/null +++ b/localization/zh-TW/data-mapper/README.md @@ -0,0 +1,24 @@ +--- +title: Data Mapper +shortTitle: Data Mapper +category: Architectural +language: zh +tag: + - Decoupling +--- + +## 含義 +一個用於在持久化物件和資料庫之間傳輸資料的對映器,同時保持它們之間和對映器本身的獨立性。 + +## 類圖 +![alt text](./etc/data-mapper.png "Data Mapper") + +## 適用場景 +資料對映器適用於以下場景: + +* 當你想把資料物件從資料庫訪問層解耦時時 +* 當你想編寫多個資料查詢/持久化實現時 + +## 引用 + +* [Data Mapper](http://richard.jp.leguen.ca/tutoring/soen343-f2010/tutorials/implementing-data-mapper/) \ No newline at end of file diff --git a/localization/zh-TW/data-mapper/etc/data-mapper.png b/localization/zh-TW/data-mapper/etc/data-mapper.png new file mode 100644 index 000000000000..bcda8054aac8 Binary files /dev/null and b/localization/zh-TW/data-mapper/etc/data-mapper.png differ diff --git a/localization/zh-TW/data-transfer-object/README.md b/localization/zh-TW/data-transfer-object/README.md new file mode 100644 index 000000000000..9c6d6f486837 --- /dev/null +++ b/localization/zh-TW/data-transfer-object/README.md @@ -0,0 +1,110 @@ +--- +title: Data Transfer Object +shortTitle: Data Transfer Object +category: Architectural +language: zh +tag: + - Performance +--- + +## 目的 + +次將具有多個屬性的資料從客戶端傳遞到伺服器,以避免多次呼叫遠端伺服器。 + +## 解釋 + +真實世界例子 + +> 我們需要從遠端資料庫中獲取有關客戶的資訊。 我們不使用一次查詢一個屬性,而是使用DTO一次傳送所有相關屬性。 + +通俗的說 + +> 使用DTO,可以透過單個後端查詢獲取相關資訊。 + +維基百科說 + +> 在程式設計領域,資料傳輸物件(DTO)是在程序之間承載資料的物件。 使用它的動機是,通常依靠遠端介面(例如Web服務)來完成程序之間的通訊,在這種情況下,每個呼叫都是昂貴的操作。 +> +> 因為每個(方法)呼叫的大部分成本與客戶端和伺服器之間的往返時間有關,所以減少呼叫數量的一種方法是使用一個物件(DTO)來聚合將要在多次呼叫間傳輸的資料,但僅由一個呼叫提供。 + +**程式示例** + +讓我們來介紹我們簡單的`CustomerDTO` 類 + +```java +public class CustomerDto { + private final String id; + private final String firstName; + private final String lastName; + + public CustomerDto(String id, String firstName, String lastName) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + } + + public String getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } +} +``` + +`CustomerResource` 類充當客戶資訊的伺服器。 + +```java +public class CustomerResource { + private final List customers; + + public CustomerResource(List customers) { + this.customers = customers; + } + + public List getAllCustomers() { + return customers; + } + + public void save(CustomerDto customer) { + customers.add(customer); + } + + public void delete(String customerId) { + customers.removeIf(customer -> customer.getId().equals(customerId)); + } +} +``` + +現在拉取客戶資訊變得簡單自從我們有了DTOs。 + +```java + var allCustomers = customerResource.getAllCustomers(); + allCustomers.forEach(customer -> LOGGER.info(customer.getFirstName())); + // Kelly + // Alfonso +``` + +## 類圖 + +![alt text](./etc/data-transfer-object.urm.png "data-transfer-object") + +## 適用性 + +使用資料傳輸物件模式當 + +* 客戶端請求多種資訊。資訊都是相關的 +* 當你想提高獲取資源的效能 +* 你想降低遠端方法呼叫的次數 + +## 鳴謝 + +* [Design Pattern - Transfer Object Pattern](https://www.tutorialspoint.com/design_pattern/transfer_object_pattern.htm) +* [Data Transfer Object](https://msdn.microsoft.com/en-us/library/ff649585.aspx) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) +* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321127420&linkCode=as2&tag=javadesignpat-20&linkId=014237a67c9d46f384b35e10151956bd) diff --git a/localization/zh-TW/data-transfer-object/etc/data-transfer-object.urm.png b/localization/zh-TW/data-transfer-object/etc/data-transfer-object.urm.png new file mode 100644 index 000000000000..46facff8dee4 Binary files /dev/null and b/localization/zh-TW/data-transfer-object/etc/data-transfer-object.urm.png differ diff --git a/localization/zh-TW/decorator/README.md b/localization/zh-TW/decorator/README.md new file mode 100644 index 000000000000..2cfb5f2f7a13 --- /dev/null +++ b/localization/zh-TW/decorator/README.md @@ -0,0 +1,134 @@ +--- +title: Decorator +shortTitle: Decorator +category: Structural +language: zh +tag: + - Gang Of Four + - Extensibility +--- + +## 或稱 +包裝器 + +## 目的 +動態的為物件附加額外的職責。裝飾器為子類提供了靈活的替代方案,以擴充套件功能。 + +## 解釋 + +真實世界例子 + +> 附近的山丘上住著一個憤怒的巨魔。通常它是徒手的,但有時它有武器。為了武裝巨魔不必建立新的巨魔,而是用合適的武器動態的裝飾它。 + +通俗的說 + +> 裝飾者模式讓你可以在執行時透過把物件包裝進一個裝飾類物件中來動態的改變一個物件的行為。 + +維基百科說 + +> 在物件導向的程式設計中,裝飾器模式是一種設計模式,它允許將行為靜態或動態地新增到單個物件中,而不會影響同一類中其他物件的行為。裝飾器模式通常對於遵守單一責任原則很有用,因為它允許將功能劃分到具有唯一關注領域的類之間。 + +**程式示例** + +以巨魔的為例。首先我有有一個簡單的巨魔,實現了巨魔介面。 + +```java +public interface Troll { + void attack(); + int getAttackPower(); + void fleeBattle(); +} + +@Slf4j +public class SimpleTroll implements Troll { + + @Override + public void attack() { + LOGGER.info("The troll tries to grab you!"); + } + + @Override + public int getAttackPower() { + return 10; + } + + @Override + public void fleeBattle() { + LOGGER.info("The troll shrieks in horror and runs away!"); + } +} +``` + +下面我們想為巨魔新增球棒。我們可以用裝飾者來動態的實現。 + +```java +@Slf4j +public class ClubbedTroll implements Troll { + + private final Troll decorated; + + public ClubbedTroll(Troll decorated) { + this.decorated = decorated; + } + + @Override + public void attack() { + decorated.attack(); + LOGGER.info("The troll swings at you with a club!"); + } + + @Override + public int getAttackPower() { + return decorated.getAttackPower() + 10; + } + + @Override + public void fleeBattle() { + decorated.fleeBattle(); + } +} +``` + +這裡是巨魔的實戰 + +```java +// simple troll +var troll = new SimpleTroll(); +troll.attack(); // The troll tries to grab you! +troll.fleeBattle(); // The troll shrieks in horror and runs away! + +// change the behavior of the simple troll by adding a decorator +var clubbedTroll = new ClubbedTroll(troll); +clubbedTroll.attack(); // The troll tries to grab you! The troll swings at you with a club! +clubbedTroll.fleeBattle(); // The troll shrieks in horror and runs away! +``` + +## 類圖 +![alt text](./etc/decorator.urm.png "Decorator pattern class diagram") + +## 適用性 +使用裝飾者 + +* 動態透明地向單個物件新增職責,即不影響其他物件 +* 對於可以撤銷的責任 +* 當透過子類化進行擴充套件是不切實際的。有時可能會有大量的獨立擴充套件,並且會產生大量的子類來支援每種組合。 否則類定義可能被隱藏或無法用於子類化。 + +## 教程 +* [Decorator Pattern Tutorial](https://www.journaldev.com/1540/decorator-design-pattern-in-java-example) + +## Java世界的例子 + * [java.io.InputStream](http://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html), [java.io.OutputStream](http://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html), + [java.io.Reader](http://docs.oracle.com/javase/8/docs/api/java/io/Reader.html) and [java.io.Writer](http://docs.oracle.com/javase/8/docs/api/java/io/Writer.html) + * [java.util.Collections#synchronizedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedCollection-java.util.Collection-) + * [java.util.Collections#unmodifiableXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#unmodifiableCollection-java.util.Collection-) + * [java.util.Collections#checkedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#checkedCollection-java.util.Collection-java.lang.Class-) + + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions](https://www.amazon.com/gp/product/1937785467/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1937785467&linkCode=as2&tag=javadesignpat-20&linkId=7e4e2fb7a141631491534255252fd08b) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) diff --git a/localization/zh-TW/decorator/etc/decorator.urm.png b/localization/zh-TW/decorator/etc/decorator.urm.png new file mode 100644 index 000000000000..141c0563f0c6 Binary files /dev/null and b/localization/zh-TW/decorator/etc/decorator.urm.png differ diff --git a/localization/zh-TW/delegation/README.md b/localization/zh-TW/delegation/README.md new file mode 100644 index 000000000000..9f0670cf753b --- /dev/null +++ b/localization/zh-TW/delegation/README.md @@ -0,0 +1,28 @@ +--- +title: Delegation +shortTitle: Delegation +category: Structural +language: zh +tag: + - Decoupling +--- + +## 或稱 +代理模式 + +## 目的 +它是一種讓物件將某種行為向外部表達,但實際上將實現該行為的責任委託給關聯物件的技術。 + +## 類圖 +![alt text](./etc/delegation.png "Delegate") + +## 適用性 +使用委託模式以實現以下目的 + +* 降低類的耦合性 +* 元件的行為相同,但是意識到這種情況將來可能會改變。 + +## 鳴謝 + +* [Delegate Pattern: Wikipedia ](https://en.wikipedia.org/wiki/Delegation_pattern) +* [Proxy Pattern: Wikipedia ](https://en.wikipedia.org/wiki/Proxy_pattern) diff --git a/localization/zh-TW/delegation/etc/delegation.png b/localization/zh-TW/delegation/etc/delegation.png new file mode 100644 index 000000000000..375ef4d6b00f Binary files /dev/null and b/localization/zh-TW/delegation/etc/delegation.png differ diff --git a/localization/zh-TW/dependency-injection/README.md b/localization/zh-TW/dependency-injection/README.md new file mode 100644 index 000000000000..2f592129e311 --- /dev/null +++ b/localization/zh-TW/dependency-injection/README.md @@ -0,0 +1,100 @@ +--- +title: Dependency Injection +shortTitle: Dependency Injection +category: Creational +language: zh +tag: + - Decoupling +--- + +## 目的 + +依賴注入是一種軟體設計模式,其中一個或多個依賴項(或服務)被注入或透過引用傳遞到一個依賴物件(或客戶端)中,併成為客戶端狀態的一部分。該模式將客戶的依賴關係的建立與其自身的行為分開,這使程式設計可以鬆散耦合,並遵循控制反轉和單一職責原則。 + +## 解釋 + +真實世界例子 + +> 老巫師喜歡不時地裝滿菸斗抽菸。 但是,他不想只依賴一個菸草品牌,而是希望能夠互換使用它們。 + +通俗的說 + +> 依賴注入將客戶端依賴的建立與其自身行為分開。 + +維基百科說 + +> 在軟體工程中,依賴注入是一種物件接收其依賴的其他物件的技術。 這些其他物件稱為依賴項。 + +**程式示例** + +先介紹一下菸草介面和具體的品牌。 + +```java +public abstract class Tobacco { + + private static final Logger LOGGER = LoggerFactory.getLogger(Tobacco.class); + + public void smoke(Wizard wizard) { + LOGGER.info("{} smoking {}", wizard.getClass().getSimpleName(), + this.getClass().getSimpleName()); + } +} + +public class SecondBreakfastTobacco extends Tobacco { +} + +public class RivendellTobacco extends Tobacco { +} + +public class OldTobyTobacco extends Tobacco { +} +``` + +下面是老巫師的類的層次結構。 + +```java +public interface Wizard { + + void smoke(); +} + +public class AdvancedWizard implements Wizard { + + private final Tobacco tobacco; + + public AdvancedWizard(Tobacco tobacco) { + this.tobacco = tobacco; + } + + @Override + public void smoke() { + tobacco.smoke(this); + } +} +``` + +最後我們可以看到給老巫師任意品牌的菸草是多麼的簡單。 + +```java + var advancedWizard = new AdvancedWizard(new SecondBreakfastTobacco()); + advancedWizard.smoke(); +``` + +## 類圖 + +![alt text](./etc/dependency-injection.png "Dependency Injection") + +## 適用性 + +使用依賴注入當: + +- 當你需要從物件中移除掉具體的實現內容時 + +* 使用模擬物件或存根隔離地啟用類的單元測試 + +## 鳴謝 + +* [Dependency Injection Principles, Practices, and Patterns](https://www.amazon.com/gp/product/161729473X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=161729473X&linkId=57079257a5c7d33755493802f3b884bd) +* [Clean Code: A Handbook of Agile Software Craftsmanship](https://www.amazon.com/gp/product/0132350882/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0132350882&linkCode=as2&tag=javadesignpat-20&linkId=2c390d89cc9e61c01b9e7005c7842871) +* [Java 9 Dependency Injection: Write loosely coupled code with Spring 5 and Guice](https://www.amazon.com/gp/product/1788296257/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=1788296257&linkId=4e9137a3bf722a8b5b156cce1eec0fc1) +* [Google Guice Tutorial: Open source Java based dependency injection framework](https://www.amazon.com/gp/product/B083P7DZ8M/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=B083P7DZ8M&linkId=04f0f902c877921e45215b624a124bfe) diff --git a/localization/zh-TW/dependency-injection/etc/dependency-injection.png b/localization/zh-TW/dependency-injection/etc/dependency-injection.png new file mode 100644 index 000000000000..2a92c9eb228b Binary files /dev/null and b/localization/zh-TW/dependency-injection/etc/dependency-injection.png differ diff --git a/localization/zh-TW/dirty-flag/README.md b/localization/zh-TW/dirty-flag/README.md new file mode 100644 index 000000000000..b9d6a965bcb5 --- /dev/null +++ b/localization/zh-TW/dirty-flag/README.md @@ -0,0 +1,28 @@ +--- +title: Dirty Flag +shortTitle: Dirty Flag +category: Behavioral +language: zh +tag: + - Game programming + - Performance +--- + +## 或稱 +* 是否髒 模式 + +## 目的 +避免昂貴資源的重新獲取。資源保留其身份,保留在某些快速訪問的儲存中,並被重新使用以避免再次獲取它們。 + +## 類圖 +![alt text](./etc/dirty-flag.png "Dirty Flag") + +## 適用性 +在以下情況下使用髒標誌模式 + +* 重複獲取,初始化,釋放相同資源所導致不必要的效能開銷 + +## 鳴謝 + +* [Design Patterns: Dirty Flag](https://www.takeupcode.com/podcast/89-design-patterns-dirty-flag/) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) diff --git a/localization/zh-TW/dirty-flag/etc/dirty-flag.png b/localization/zh-TW/dirty-flag/etc/dirty-flag.png new file mode 100644 index 000000000000..98d4f679d17c Binary files /dev/null and b/localization/zh-TW/dirty-flag/etc/dirty-flag.png differ diff --git a/localization/zh-TW/double-checked-locking/README.md b/localization/zh-TW/double-checked-locking/README.md new file mode 100644 index 000000000000..1a20ed6accbe --- /dev/null +++ b/localization/zh-TW/double-checked-locking/README.md @@ -0,0 +1,20 @@ +--- +title: Double Checked Locking +shortTitle: Double Checked Locking +category: Idiom +language: zh +tag: + - Performance +--- + +## 含義 +透過先測試鎖定標準("鎖提示")而不實際獲取鎖的方式來減少獲取鎖的開銷。只有當鎖定標準檢查表明需要鎖定時,才進行實際的鎖定邏輯。 + +## 類圖 +![alt text](./etc/double_checked_locking_1.png "Double Checked Locking") + +## 適用場景 +在以下場景適合使用雙重鎖檢查模式: + +* 在建立物件時有存在併發的訪問。如單例模式中,你想建立同一個類的單個例項,如果存在兩個或更多的執行緒對例項進行判空,僅僅檢查該該例項是否為空可能是不夠的。 +* 在一個方法上存在併發訪問,該方法的行為是根據一些約束條件而改變,而這些約束條件在該方法中也會發生變化。 \ No newline at end of file diff --git a/localization/zh-TW/double-checked-locking/etc/double_checked_locking_1.png b/localization/zh-TW/double-checked-locking/etc/double_checked_locking_1.png new file mode 100644 index 000000000000..cced091b9d67 Binary files /dev/null and b/localization/zh-TW/double-checked-locking/etc/double_checked_locking_1.png differ diff --git a/localization/zh-TW/facade/README.md b/localization/zh-TW/facade/README.md new file mode 100644 index 000000000000..3cfaf0cc9d02 --- /dev/null +++ b/localization/zh-TW/facade/README.md @@ -0,0 +1,191 @@ +--- +title: Facade +shortTitle: Facade +category: Structural +language: zh +tag: + - Gang Of Four + - Decoupling +--- + +## 目的 +為一個子系統中的一系列介面提供一個統一的介面。外觀定義了一個更高階別的介面以便子系統更容易使用。 + +## 解釋 + +真實世界的例子 + +> 一個金礦是怎麼工作的?“嗯,礦工下去然後挖金子!”你說。這是你所相信的因為你在使用一個金礦對外提供的一個簡單介面,在內部它要卻要做很多事情。這個簡單的介面對複雜的子系統來說就是一個外觀。 + +用通俗的話說 + +> 外觀模式為一個複雜的子系統提供一個簡單的介面。 + +維基百科說 + +> 外觀是為很大體量的程式碼(比如類庫)提供簡單介面的一種物件。 + +**程式示例** + +使用上面金礦的例子。這裡我們有矮人的礦工等級制度。 + +```java + +@Slf4j +public abstract class DwarvenMineWorker { + + public void goToSleep() { + LOGGER.info("{} goes to sleep.", name()); + } + + public void wakeUp() { + LOGGER.info("{} wakes up.", name()); + } + + public void goHome() { + LOGGER.info("{} goes home.", name()); + } + + public void goToMine() { + LOGGER.info("{} goes to the mine.", name()); + } + + private void action(Action action) { + switch (action) { + case GO_TO_SLEEP -> goToSleep(); + case WAKE_UP -> wakeUp(); + case GO_HOME -> goHome(); + case GO_TO_MINE -> goToMine(); + case WORK -> work(); + default -> LOGGER.info("Undefined action"); + } + } + + public void action(Action... actions) { + Arrays.stream(actions).forEach(this::action); + } + + public abstract void work(); + + public abstract String name(); + + enum Action { + GO_TO_SLEEP, WAKE_UP, GO_HOME, GO_TO_MINE, WORK + } +} + +@Slf4j +public class DwarvenTunnelDigger extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} creates another promising tunnel.", name()); + } + + @Override + public String name() { + return "Dwarven tunnel digger"; + } +} + +@Slf4j +public class DwarvenGoldDigger extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} digs for gold.", name()); + } + + @Override + public String name() { + return "Dwarf gold digger"; + } +} + +@Slf4j +public class DwarvenCartOperator extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} moves gold chunks out of the mine.", name()); + } + + @Override + public String name() { + return "Dwarf cart operator"; + } +} + +``` + +為了操縱所有這些礦工我們有了這個外觀 + +```java +public class DwarvenGoldmineFacade { + + private final List workers; + + public DwarvenGoldmineFacade() { + workers = List.of( + new DwarvenGoldDigger(), + new DwarvenCartOperator(), + new DwarvenTunnelDigger()); + } + + public void startNewDay() { + makeActions(workers, DwarvenMineWorker.Action.WAKE_UP, DwarvenMineWorker.Action.GO_TO_MINE); + } + + public void digOutGold() { + makeActions(workers, DwarvenMineWorker.Action.WORK); + } + + public void endDay() { + makeActions(workers, DwarvenMineWorker.Action.GO_HOME, DwarvenMineWorker.Action.GO_TO_SLEEP); + } + + private static void makeActions(Collection workers, + DwarvenMineWorker.Action... actions) { + workers.forEach(worker -> worker.action(actions)); + } +} +``` + +現在來使用外觀 + +```java +DwarvenGoldmineFacade facade = new DwarvenGoldmineFacade(); +facade.startNewDay(); +// Dwarf gold digger wakes up. +// Dwarf gold digger goes to the mine. +// Dwarf cart operator wakes up. +// Dwarf cart operator goes to the mine. +// Dwarven tunnel digger wakes up. +// Dwarven tunnel digger goes to the mine. +facade.digOutGold(); +// Dwarf gold digger digs for gold. +// Dwarf cart operator moves gold chunks out of the mine. +// Dwarven tunnel digger creates another promising tunnel. +facade.endDay(); +// Dwarf gold digger goes home. +// Dwarf gold digger goes to sleep. +// Dwarf cart operator goes home. +// Dwarf cart operator goes to sleep. +// Dwarven tunnel digger goes home. +// Dwarven tunnel digger goes to sleep. +``` + +## 類圖 +![alt text](./etc/facade.urm.png "Facade pattern class diagram") + +## 適用性 +使用外觀模式當 + +* 你想為一個複雜的子系統提供一個簡單的介面。隨著子系統的發展,它們通常會變得更加複雜。多數模式在應用時會導致更多和更少的類。這使子系統更可重用,更易於自定義,但是對於不需要自定義它的客戶來說,使用它也變得更加困難。 外觀可以提供子系統的簡單預設檢視,足以滿足大多數客戶端的需求。只有需要更多可定製性的客戶才需要檢視外觀外的東西(原子系統提供的介面)。 +* 客戶端與抽象的實現類之間存在許多依賴關係。 引入外觀以使子系統與客戶端和其他子系統分離,從而提高子系統的獨立性和可移植性。 +* 你想對子系統進行分層。 使用外觀來定義每個子系統級別的入口點。 如果子系統是相關的,則可以透過使子系統僅透過其外觀相互通訊來簡化它們之間的依賴性。 + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/localization/zh-TW/facade/etc/facade.urm.png b/localization/zh-TW/facade/etc/facade.urm.png new file mode 100644 index 000000000000..8e3ec7aca45e Binary files /dev/null and b/localization/zh-TW/facade/etc/facade.urm.png differ diff --git a/localization/zh-TW/factory-kit/README.md b/localization/zh-TW/factory-kit/README.md new file mode 100644 index 000000000000..ee0abc9b98f6 --- /dev/null +++ b/localization/zh-TW/factory-kit/README.md @@ -0,0 +1,26 @@ +--- +title: Factory Kit +shortTitle: Factory Kit +category: Creational +language: zh +tag: + - Extensibility +--- + +## 含義 +使用分離的構建器和工廠介面來定義一個不可變內容的工廠。 + +## 類圖 +![alt text](./etc/factory-kit.png "Factory Kit") + +## 適用場景 +工廠套件模式適用於與以下場景: + +* 一個類無法預知它需要建立的物件的類別 +- 你只是想要一個新的自定義構建器(builder)的例項,而非全域性的構建器 +- 你明確地想要定義物件的型別,而且工廠可以建立這些物件 +- 你想要分離構建器(builder)和建立器(creator)介面 + +## 引用 + +* [Design Pattern Reloaded by Remi Forax: ](https://www.youtube.com/watch?v=-k2X7guaArU) \ No newline at end of file diff --git a/localization/zh-TW/factory-kit/etc/factory-kit.png b/localization/zh-TW/factory-kit/etc/factory-kit.png new file mode 100644 index 000000000000..7093193cb8d0 Binary files /dev/null and b/localization/zh-TW/factory-kit/etc/factory-kit.png differ diff --git a/localization/zh-TW/factory-method/README.md b/localization/zh-TW/factory-method/README.md new file mode 100644 index 000000000000..655da7fe2096 --- /dev/null +++ b/localization/zh-TW/factory-method/README.md @@ -0,0 +1,87 @@ +--- +title: Factory Method +shortTitle: Factory Method +category: Creational +language: zh +tag: + - Extensibility + - Gang Of Four +--- + +## Also known as +# 或稱 + +虛擬構造器 + +## 目的 +為建立一個物件定義一個介面,但是讓子類決定例項化哪個類。工廠方法允許類將例項化延遲到子類。 + +## 解釋 +真實世界例子 + +> 鐵匠生產武器。精靈需要精靈武器,而獸人需要獸人武器。根據客戶來召喚正確型別的鐵匠。 + +通俗的說 + +> 它為類提供了一種把例項化的邏輯委託給子類的方式。 + +維基百科上說 + +> 在基於類的程式設計中,工廠方法模式是一種建立型設計模式用來解決建立物件的問題,而不需要指定將要建立物件的確切類。這是透過呼叫工廠方法建立物件來完成的,而不是透過呼叫構造器。該工廠方法在介面中指定並由子類實現,或者在基類實現並可以選擇由子類重寫。 + + **程式示例** + +以上面的鐵匠為例,首先我們有鐵匠的介面和一些它的實現。 + +```java +public interface Blacksmith { + Weapon manufactureWeapon(WeaponType weaponType); +} + +public class ElfBlacksmith implements Blacksmith { + public Weapon manufactureWeapon(WeaponType weaponType) { + return ELFARSENAL.get(weaponType); + } +} + +public class OrcBlacksmith implements Blacksmith { + public Weapon manufactureWeapon(WeaponType weaponType) { + return ORCARSENAL.get(weaponType); + } +} +``` + +現在隨著客戶的到來,會召喚出正確型別的鐵匠並製造出要求的武器。 + +```java +var blacksmith = new ElfBlacksmith(); +blacksmith.manufactureWeapon(WeaponType.SPEAR); +blacksmith.manufactureWeapon(WeaponType.AXE); +// Elvish weapons are created +``` + +## 類圖 +![alt text](./etc/factory-method.urm.png "Factory Method pattern class diagram") + +## 適用性 +使用工廠方法模式當 + +* 一個類無法預料它所要必須建立的物件的類 +* 一個類想要它的子類來指定它要建立的物件 +* 類將責任委派給幾個幫助子類中的一個,而你想定位瞭解是具體之中的哪一個 + +## Java中的例子 + +* [java.util.Calendar](http://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--) +* [java.util.ResourceBundle](http://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-) +* [java.text.NumberFormat](http://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--) +* [java.nio.charset.Charset](http://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-) +* [java.net.URLStreamHandlerFactory](http://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html#createURLStreamHandler-java.lang.String-) +* [java.util.EnumSet](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of-E-) +* [javax.xml.bind.JAXBContext](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/zh-TW/factory-method/etc/factory-method.urm.png b/localization/zh-TW/factory-method/etc/factory-method.urm.png new file mode 100644 index 000000000000..7c97aff91ee6 Binary files /dev/null and b/localization/zh-TW/factory-method/etc/factory-method.urm.png differ diff --git a/localization/zh-TW/factory/README.md b/localization/zh-TW/factory/README.md new file mode 100644 index 000000000000..ea1d9c875f94 --- /dev/null +++ b/localization/zh-TW/factory/README.md @@ -0,0 +1,140 @@ +--- +title: Factory +shortTitle: Factory +category: Creational +language: zh +tag: + - Gang of Four +--- + +## 也被稱為 + +* 簡單工廠 +* 靜態工廠方法 + +## 含義 + +在工廠類中提供一個封裝的靜態工廠方法,用於隱藏物件初始化細節,使客戶端程式碼可以專注於使用,而不用關心類的初始化過程。 + +## 解釋 + +現例項子 + +> +> 假設我們有一個需要連線到 SQL Server 的 Web 應用,但現在我們需要切換到連線 Oracle。為了不修改現有程式碼的情況下做到這一點,我們需要實現簡單工廠模式。在這種模式下,可以透過呼叫一個靜態方法來建立與給定資料庫的連線。 + +維基百科 + +> 工廠類是一個用於建立其他物件的物件 -- 從形式上看,工廠方法是一個用於返回不同原型或型別的函式或方法。 + +**程式設計示例** + +我們有一個 `Car` 介面,以及實現類 `Ford`, `Ferrari`。 + +```java +public interface Car { + String getDescription(); +} + +public class Ford implements Car { + + static final String DESCRIPTION = "This is Ford."; + + @Override + public String getDescription() { + return DESCRIPTION; + } +} + +public class Ferrari implements Car { + + static final String DESCRIPTION = "This is Ferrari."; + + @Override + public String getDescription() { + return DESCRIPTION; + } +} +``` + +Enumeration above represents types of cars that we support (`Ford` and `Ferrari`). + +以下的列舉用於表示支援的 `Car` 型別(`Ford` 和 `Ferrari`) + +```java +public enum CarType { + + FORD(Ford::new), + FERRARI(Ferrari::new); + + private final Supplier constructor; + + CarType(Supplier constructor) { + this.constructor = constructor; + } + + public Supplier getConstructor() { + return this.constructor; + } +} +``` +接著我們實現了一個靜態方法 `getCar` 用於封裝工廠類 `CarsFactory` 建立 `Car` 具體物件例項的細節。 + +```java +public class CarsFactory { + + public static Car getCar(CarType type) { + return type.getConstructor().get(); + } +} +``` + +現在我們可以在客戶端程式碼中透過工廠類建立不同型別的 `Car` 物件例項。 + +```java +var car1 = CarsFactory.getCar(CarType.FORD); +var car2 = CarsFactory.getCar(CarType.FERRARI); +LOGGER.info(car1.getDescription()); +LOGGER.info(car2.getDescription()); +``` + +程式輸出: + +```java +This is Ford. +This is Ferrari. +``` + +## 類圖 + +![alt text](./etc/factory.urm.png "Factory pattern class diagram") + +## 適用場景 + +在你只關心物件的建立,但不關心如何建立、管理它的時候,請使用簡單工廠模式。 + +**優點** + +* 可以把物件建立程式碼集中在一個地方,避免在程式碼庫存散佈 "new" 關鍵字。 +* 可以讓程式碼更加低耦合。它的一些主要優點包括更好的可測試性、更好的可讀性、元件可替換性、可拓展性、更好的隔離性。 + +**缺點** + +* 會使程式碼變得比原來的更加複雜一些。 + +## 現實案例 + +* [java.util.Calendar#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--) +* [java.util.ResourceBundle#getBundle()](https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-) +* [java.text.NumberFormat#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--) +* [java.nio.charset.Charset#forName()](https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-) +* [java.net.URLStreamHandlerFactory#createURLStreamHandler(String)](https://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html) (Returns different singleton objects, depending on a protocol) +* [java.util.EnumSet#of()](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of(E)) +* [javax.xml.bind.JAXBContext#createMarshaller()](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) and other similar methods. + +## 相關模式 + +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/) +* [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/) +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/) + diff --git a/localization/zh-TW/factory/etc/factory.urm.png b/localization/zh-TW/factory/etc/factory.urm.png new file mode 100644 index 000000000000..4b3420792e06 Binary files /dev/null and b/localization/zh-TW/factory/etc/factory.urm.png differ diff --git a/localization/zh-TW/interpreter/README.md b/localization/zh-TW/interpreter/README.md new file mode 100644 index 000000000000..fa003dbdd5d8 --- /dev/null +++ b/localization/zh-TW/interpreter/README.md @@ -0,0 +1,34 @@ +--- +title: Interpreter +shortTitle: Interpreter +category: Behavioral +language: zh +tag: + - Gang of Four +--- + +## 目的 +給定一種語言,請定義其語法的表示形式,以及使用該表示形式來解釋該語言中的句子的直譯器。 + +## 類圖 +![alt text](./etc/interpreter_1.png "Interpreter") + +## 適用性 +有一種要解釋的語言時,請使用直譯器模式,並且可以將語言中的語句表示為抽象語法樹。直譯器模式在以下情況下效果最佳 + +* 語法很簡單。 對於複雜的語法,語法的類層次結構變得龐大且難以管理。 在這種情況下,解析器生成器之類的工具是更好的選擇。 他們可以在不構建抽象語法樹的情況下解釋表示式,這可以節省空間並可能節省時間 +* 效率不是關鍵問題。 通常,最有效的直譯器不是透過直接解釋解析樹來實現的,而是先將其轉換為另一種形式。 例如,正規表示式通常會轉換為狀態機。 但是即使這樣,翻譯器也可以透過直譯器模式實現,因此該模式仍然適用。 + +## 真實世界例子 + +* [java.util.Pattern](http://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) +* [java.text.Normalizer](http://docs.oracle.com/javase/8/docs/api/java/text/Normalizer.html) +* All subclasses of [java.text.Format](http://docs.oracle.com/javase/8/docs/api/java/text/Format.html) +* [javax.el.ELResolver](http://docs.oracle.com/javaee/7/api/javax/el/ELResolver.html) + + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/zh-TW/interpreter/etc/interpreter_1.png b/localization/zh-TW/interpreter/etc/interpreter_1.png new file mode 100644 index 000000000000..f10342a1df90 Binary files /dev/null and b/localization/zh-TW/interpreter/etc/interpreter_1.png differ diff --git a/localization/zh-TW/iterator/README.md b/localization/zh-TW/iterator/README.md new file mode 100644 index 000000000000..0796cbaf9af7 --- /dev/null +++ b/localization/zh-TW/iterator/README.md @@ -0,0 +1,133 @@ +--- +title: Iterator +shortTitle: Iterator +category: Behavioral +language: zh +tag: + - Gang of Four +--- + +## 又被稱為 +遊標 + +## 目的 +提供一種在不暴露其基礎表示的情況下順序訪問聚合物件的元素的方法。 + +## 解釋 + +真實世界例子 + +> 百寶箱包含一組魔法物品。有多種物品,例如戒指,藥水和武器。可以使用藏寶箱提供的迭代器按型別瀏覽商品。 + +通俗地說 + +> 容器可以提供與表示形式無關的迭代器介面,以提供對元素的訪問。 + +維基百科說 + +> 在物件導向的程式設計中,迭代器模式是一種設計模式,其中迭代器用於遍歷容器並訪問容器的元素。 + +**程式示例** + +在我們的示例中包含物品的藏寶箱是主要類。 + +```java +public class TreasureChest { + + private final List items; + + public TreasureChest() { + items = List.of( + new Item(ItemType.POTION, "Potion of courage"), + new Item(ItemType.RING, "Ring of shadows"), + new Item(ItemType.POTION, "Potion of wisdom"), + new Item(ItemType.POTION, "Potion of blood"), + new Item(ItemType.WEAPON, "Sword of silver +1"), + new Item(ItemType.POTION, "Potion of rust"), + new Item(ItemType.POTION, "Potion of healing"), + new Item(ItemType.RING, "Ring of armor"), + new Item(ItemType.WEAPON, "Steel halberd"), + new Item(ItemType.WEAPON, "Dagger of poison")); + } + + public Iterator iterator(ItemType itemType) { + return new TreasureChestItemIterator(this, itemType); + } + + public List getItems() { + return new ArrayList<>(items); + } +} + +public class Item { + + private ItemType type; + private final String name; + + public Item(ItemType type, String name) { + this.setType(type); + this.name = name; + } + + @Override + public String toString() { + return name; + } + + public ItemType getType() { + return type; + } + + public final void setType(ItemType type) { + this.type = type; + } +} + +public enum ItemType { + + ANY, WEAPON, RING, POTION + +} +``` + +迭代器介面極度簡單。 + +```java +public interface Iterator { + + boolean hasNext(); + + T next(); +} +``` + +在以下示例中,我們遍歷在寶箱中找到的戒指型別物品。 + +```java +var itemIterator = TREASURE_CHEST.iterator(ItemType.RING); +while (itemIterator.hasNext()) { + LOGGER.info(itemIterator.next().toString()); +} +// Ring of shadows +// Ring of armor +``` + +## 類圖 +![alt text](./etc/iterator_1.png "Iterator") + +## 適用性 +以下情況使用迭代器模式 + +* 在不暴露其內部表示的情況下訪問聚合物件的內容 +* 為了支援聚合物件的多種遍歷方式 +* 提供一個遍歷不同聚合結構的統一介面 + +## Java世界例子 + +* [java.util.Iterator](http://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html) +* [java.util.Enumeration](http://docs.oracle.com/javase/8/docs/api/java/util/Enumeration.html) + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/localization/zh-TW/iterator/etc/iterator_1.png b/localization/zh-TW/iterator/etc/iterator_1.png new file mode 100644 index 000000000000..d8313bc5881f Binary files /dev/null and b/localization/zh-TW/iterator/etc/iterator_1.png differ diff --git a/localization/zh-TW/monitor/README.md b/localization/zh-TW/monitor/README.md new file mode 100644 index 000000000000..d4ef5e5f19e8 --- /dev/null +++ b/localization/zh-TW/monitor/README.md @@ -0,0 +1,96 @@ +--- +title: Monitor +shortTitle: Monitor +category: Concurrency +language: zh +tag: + - Performance +--- + +## 或稱 + +監控物件模式 + +## 目的 + +主要目的是為多個執行緒或程序提供一種結構化和受控的方式來安全地訪問和操作共享資源,例如變數、資料結構或程式碼的關鍵部分,而不會導致衝突或競爭條件。 + +## 解釋 + +通俗的說 + +> 監視器模式用於強制對資料進行單執行緒訪問。 一次只允許一個執行緒在監視器物件內執行程式碼。 + +維基百科說 + +> 在併發程式設計(也稱為並行程式設計)中,監視器是一種同步構造,它允許執行緒具有互斥性和等待(阻止)特定條件變為假的能力。 監視器還具有向其他執行緒發出訊號通知其條件已滿足的機制。 + +**程式示例** + +考慮有一家銀行透過轉賬方式將錢從一個帳戶轉移到另一個帳戶。 它是`同步`意味著只有一個執行緒可以訪問此方法,因為如果許多執行緒訪問它並在同一時間將資金從一個帳戶轉移到另一個帳戶,則餘額會發生變化! + +``` +class Bank { + + private int[] accounts; + Logger logger; + + public Bank(int accountNum, int baseAmount, Logger logger) { + this.logger = logger; + accounts = new int[accountNum]; + Arrays.fill(accounts, baseAmount); + } + + public synchronized void transfer(int accountA, int accountB, int amount) { + if (accounts[accountA] >= amount) { + accounts[accountB] += amount; + accounts[accountA] -= amount; + logger.info("Transferred from account :" + accountA + " to account :" + accountB + " , amount :" + amount + " . balance :" + getBalance()); + } + } +``` + +getBalance 始終返回總金額,並且每次轉賬後總金額應相同 + +``` + private synchronized int getBalance() { + int balance = 0; + for (int account : accounts) { + balance += account; + } + return balance; + } + } +``` + +## 類圖 +![alt text](./etc/monitor.urm.png "Monitor class diagram") + +## 適用性 + +監視器設計模式應該用於具有需要由多個執行緒或程序同時訪問和操作的共享資源的情況。 此模式在需要同步以防止競爭條件、資料損壞和不一致狀態的情況下特別有用。 以下是你應該考慮使用監視器模式的一些情況: + +1. **共享資料**:當你的應用程式涉及需要由多個執行緒訪問和更新的共享資料結構、變數或資源時。 監視器確保一次只有一個執行緒可以訪問共享資源,從而防止衝突並確保資料一致性。 + +2. **關鍵部分**:當你有程式碼的關鍵部分一次只需要由一個執行緒執行時。 關鍵部分是操作共享資源的程式碼部分,併發訪問可能會導致問題。 監視器有助於確保在任何給定時間只有一個執行緒可以執行關鍵部分。 + +3. **執行緒安全**:當你需要確保執行緒安全而不是僅僅依賴鎖和訊號量等低階同步機制時。 監視器提供了封裝同步和資源管理的更高階別的抽象。 + +4. **等待和發訊號**:當你遇到執行緒需要等待滿足某些條件才能繼續操作時。 監視器通常包含執行緒等待特定條件以及其他執行緒在滿足條件時通知它們的機制。 + +5. **死鎖預防**:當你希望透過提供結構化方式來獲取和釋放共享資源上的鎖來防止死鎖時。 監視器透過確保資源訪問得到良好管理來幫助避免常見的死鎖情況。 + +6. **併發資料結構**:當你實現併發資料結構(例如佇列、堆疊或雜湊表)時,多個執行緒需要操作該結構,同時保持其完整性。 + +7. **資源共享**:當多個執行緒需要共享有限的資源時,例如連線資料庫或訪問網路套接字。 監視器可以幫助以受控方式管理這些資源的分配和釋放。 + +8. **改進可維護性**:當你想要將同步邏輯和共享資源管理封裝在單個物件中時,改進程式碼組織並使併發相關程式碼更容易推理。 + +但是,需要注意的是,監視器模式可能並不最適合所有併發場景。 在某些情況下,其他同步機制(例如鎖、訊號量或併發資料結構)可能更合適。 此外,現代程式語言和框架通常提供更高階別的併發結構,抽象出低階別同步的複雜性。 + +在應用監視器模式之前,建議徹底分析應用程式的併發需求,並選擇最適合你需求的同步方法,同時考慮效能、複雜性和可用語言功能等因素。 + +## 相關模式 + +* Active object +* Double-checked locking diff --git a/localization/zh-TW/monitor/etc/monitor.urm.png b/localization/zh-TW/monitor/etc/monitor.urm.png new file mode 100644 index 000000000000..c00e70e3fd12 Binary files /dev/null and b/localization/zh-TW/monitor/etc/monitor.urm.png differ diff --git a/localization/zh-TW/observer/README.md b/localization/zh-TW/observer/README.md new file mode 100644 index 000000000000..5f2b84365a3f --- /dev/null +++ b/localization/zh-TW/observer/README.md @@ -0,0 +1,155 @@ +--- +title: Observer +shortTitle: Observer +category: Behavioral +language: zh +tag: + - Gang Of Four + - Reactive +--- + +## Also known as +## 又被稱為 + +家屬,釋出訂閱模式 + +## 目的 + +定義一種一對多的物件依賴關係這樣當一個物件改變狀態時,所有依賴它的物件都將自動通知或更新。 + +## 解釋 + +真實世界例子 + +> 在遙遠的土地上生活著霍位元人和獸人的種族。他們都是戶外生活的人所以他們密切關注天氣的變化。可以說他們不斷地關注著天氣。 + +通俗的說 + +> 註冊成為一個觀察者以接收物件狀態的改變。 + +維基百科說 + +> 觀察者模式是這樣的一種軟體設計模式:它有一個被稱為主題的物件,維護著一個所有依賴於它的依賴者清單,也就是觀察者清單,當主題的狀態發生改變時,主題通常會呼叫觀察者的方法來自動通知觀察者們。 + +**程式設計示例** + +讓我們先來介紹天氣觀察者的介面以及我們的種族,獸人和霍位元人。 + +```java +public interface WeatherObserver { + + void update(WeatherType currentWeather); +} + +@Slf4j +public class Orcs implements WeatherObserver { + + @Override + public void update(WeatherType currentWeather) { + LOGGER.info("The orcs are facing " + currentWeather.getDescription() + " weather now"); + } +} + +@Slf4j +public class Hobbits implements WeatherObserver { + + @Override + public void update(WeatherType currentWeather) { + switch (currentWeather) { + LOGGER.info("The hobbits are facing " + currentWeather.getDescription() + " weather now"); + } +} +``` + +然後這裡是不斷變化的天氣。 + +```java +@Slf4j +public class Weather { + + private WeatherType currentWeather; + private final List observers; + + public Weather() { + observers = new ArrayList<>(); + currentWeather = WeatherType.SUNNY; + } + + public void addObserver(WeatherObserver obs) { + observers.add(obs); + } + + public void removeObserver(WeatherObserver obs) { + observers.remove(obs); + } + + /** + * Makes time pass for weather. + */ + public void timePasses() { + var enumValues = WeatherType.values(); + currentWeather = enumValues[(currentWeather.ordinal() + 1) % enumValues.length]; + LOGGER.info("The weather changed to {}.", currentWeather); + notifyObservers(); + } + + private void notifyObservers() { + for (var obs : observers) { + obs.update(currentWeather); + } + } +} +``` + +這是完整的示例。 + +```java + var weather = new Weather(); + weather.addObserver(new Orcs()); + weather.addObserver(new Hobbits()); + + weather.timePasses(); + // The weather changed to rainy. + // The orcs are facing rainy weather now + // The hobbits are facing rainy weather now + weather.timePasses(); + // The weather changed to windy. + // The orcs are facing windy weather now + // The hobbits are facing windy weather now + weather.timePasses(); + // The weather changed to cold. + // The orcs are facing cold weather now + // The hobbits are facing cold weather now + weather.timePasses(); + // The weather changed to sunny. + // The orcs are facing sunny weather now + // The hobbits are facing sunny weather now +``` + +## Class diagram +![alt text](./etc/observer.png "Observer") + +## 應用 +在下面任何一種情況下都可以使用觀察者模式 + +* 當抽象具有兩個方面時,一個方面依賴於另一個方面。將這些方面封裝在單獨的物件中,可以使你分別進行更改和重用 +* 當一個物件的改變的同時需要改變其他物件,同時你又不知道有多少物件需要改變時 +* 當一個物件可以通知其他物件而無需假設這些物件是誰時。換句話說,你不想讓這些物件緊耦合。 + +## 典型用例 + +* 一個物件的改變導致其他物件的改變 + +## Java中的例子 + +* [java.util.Observer](http://docs.oracle.com/javase/8/docs/api/java/util/Observer.html) +* [java.util.EventListener](http://docs.oracle.com/javase/8/docs/api/java/util/EventListener.html) +* [javax.servlet.http.HttpSessionBindingListener](http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpSessionBindingListener.html) +* [RxJava](https://github.com/ReactiveX/RxJava) + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Java Generics and Collections](https://www.amazon.com/gp/product/0596527756/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596527756&linkCode=as2&tag=javadesignpat-20&linkId=246e5e2c26fe1c3ada6a70b15afcb195) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/zh-TW/observer/etc/observer.png b/localization/zh-TW/observer/etc/observer.png new file mode 100644 index 000000000000..f2ab0edfeb21 Binary files /dev/null and b/localization/zh-TW/observer/etc/observer.png differ diff --git a/localization/zh-TW/private-class-data/README.md b/localization/zh-TW/private-class-data/README.md new file mode 100644 index 000000000000..2f65dcbce48e --- /dev/null +++ b/localization/zh-TW/private-class-data/README.md @@ -0,0 +1,127 @@ +--- +title: Private Class Data +shortTitle: Private Class Data +category: Idiom +language: zh +tag: + - Data access +--- + +## 目的 + +私有類資料設計模式試圖透過限制屬性的可見性來減少屬性的暴露。 透過將它們封裝在單個Data物件中,可以減少類屬性的數量。 + +## 解釋 + +真實世界例子 + +> 想象一下你在為家人做晚餐燉湯。你想阻止家庭成員在你烹飪時偷偷品嚐菜品,否則後面可能東西不夠吃了。 + +通俗的說 + +> 私有類資料模式透過將資料與使用它的方法分離到維護資料狀態的類中,從而防止了對不可變資料的操縱。 + +維基百科說 + +> 私有類資料是計算機程式設計中的一種設計模式,用於封裝類屬性及其操作。 + +**程式示例** + +使用上面燉湯的例子。 首先我們有 `燉湯`類 ,它的屬性沒有被私有類資料保護,從而使燉菜的成分對類方法易變。 + +```java +public class Stew { + private static final Logger LOGGER = LoggerFactory.getLogger(Stew.class); + private int numPotatoes; + private int numCarrots; + private int numMeat; + private int numPeppers; + public Stew(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { + this.numPotatoes = numPotatoes; + this.numCarrots = numCarrots; + this.numMeat = numMeat; + this.numPeppers = numPeppers; + } + public void mix() { + LOGGER.info("Mixing the stew we find: {} potatoes, {} carrots, {} meat and {} peppers", + numPotatoes, numCarrots, numMeat, numPeppers); + } + public void taste() { + LOGGER.info("Tasting the stew"); + if (numPotatoes > 0) { + numPotatoes--; + } + if (numCarrots > 0) { + numCarrots--; + } + if (numMeat > 0) { + numMeat--; + } + if (numPeppers > 0) { + numPeppers--; + } + } +} +``` + +現在,我們有了` ImmutableStew`類,其中的資料受`StewData`類保護。 現在,其中的方法無法處理`ImmutableStew`類的資料。 + +```java +public class StewData { + private final int numPotatoes; + private final int numCarrots; + private final int numMeat; + private final int numPeppers; + public StewData(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { + this.numPotatoes = numPotatoes; + this.numCarrots = numCarrots; + this.numMeat = numMeat; + this.numPeppers = numPeppers; + } + public int getNumPotatoes() { + return numPotatoes; + } + public int getNumCarrots() { + return numCarrots; + } + public int getNumMeat() { + return numMeat; + } + public int getNumPeppers() { + return numPeppers; + } +} +public class ImmutableStew { + private static final Logger LOGGER = LoggerFactory.getLogger(ImmutableStew.class); + private final StewData data; + public ImmutableStew(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { + data = new StewData(numPotatoes, numCarrots, numMeat, numPeppers); + } + public void mix() { + LOGGER + .info("Mixing the immutable stew we find: {} potatoes, {} carrots, {} meat and {} peppers", + data.getNumPotatoes(), data.getNumCarrots(), data.getNumMeat(), data.getNumPeppers()); + } +} +``` + +讓我們嘗試建立每個類的例項並呼叫其方法: + +```java +var stew = new Stew(1, 2, 3, 4); +stew.mix(); // Mixing the stew we find: 1 potatoes, 2 carrots, 3 meat and 4 peppers +stew.taste(); // Tasting the stew +stew.mix(); // Mixing the stew we find: 0 potatoes, 1 carrots, 2 meat and 3 peppers +var immutableStew = new ImmutableStew(2, 4, 3, 6); +immutableStew.mix(); // Mixing the immutable stew we find: 2 potatoes, 4 carrots, 3 meat and 6 peppers +``` + +## 類圖 + +![alt text](./etc/private-class-data.png "Private Class Data") + +## 適用性 + +在以下情況下使用私有類資料模式 + +* 你要阻止對類資料成員的寫訪問。 diff --git a/localization/zh-TW/private-class-data/etc/private-class-data.png b/localization/zh-TW/private-class-data/etc/private-class-data.png new file mode 100644 index 000000000000..65c343a5faf9 Binary files /dev/null and b/localization/zh-TW/private-class-data/etc/private-class-data.png differ diff --git a/localization/zh-TW/producer-consumer/README.md b/localization/zh-TW/producer-consumer/README.md new file mode 100644 index 000000000000..0636a1e85421 --- /dev/null +++ b/localization/zh-TW/producer-consumer/README.md @@ -0,0 +1,20 @@ +--- +title: Producer Consumer +shortTitle: Producer Consumer +category: Concurrency +language: zh +tag: + - Reactive +--- + +## 目的 +生產者消費者設計模式是一種經典的併發模式,透過將工作與執行工作任務分開來減少生產者與消費者之間的耦合。 + +## 類圖 +![alt text](./etc/producer-consumer.png "Producer Consumer") + +## 適用性 +在以下情況下使用生產者消費者 + +* 透過將工作分成生產和消費兩個工作程序來解耦系統 +* 解決生產工作和消費工作需要不同時間的問題 diff --git a/localization/zh-TW/producer-consumer/etc/producer-consumer.png b/localization/zh-TW/producer-consumer/etc/producer-consumer.png new file mode 100644 index 000000000000..e8bc573b388f Binary files /dev/null and b/localization/zh-TW/producer-consumer/etc/producer-consumer.png differ diff --git a/localization/zh-TW/proxy/README.md b/localization/zh-TW/proxy/README.md new file mode 100644 index 000000000000..8f389b6c9f26 --- /dev/null +++ b/localization/zh-TW/proxy/README.md @@ -0,0 +1,160 @@ +--- +title: Proxy +shortTitle: Proxy +category: Structural +language: zh +tag: + - Gang Of Four + - Decoupling +--- + +## 又被稱為 + +替代(代孕)模式 + +## 目的 + +為另一個物件提供代理或佔位符以控制對其的訪問。 + +## 解釋 + +真實世界例子 + +> 想象有一個塔,當地的巫師去那裡學習他們的法術。象牙塔只能夠透過代理來進入以此來保證只有首先3個巫師才能進入。這裡的代理就代表的塔的功能並新增訪問控制。 + +通俗的說 + +> 使用代理模式,一個類代表另一個類的功能。 + +維基百科說 + +> 在最一般的形式上,代理是一個類,它充當與其他物件的介面。代理是客戶端呼叫的包裝器或代理物件,以訪問後臺的實際服務物件。代理本身可以簡單地轉發到真實物件,也可以提供其他邏輯。在代理中,可以提供額外的功能,例如在對實物件的操作佔用大量資源時進行快取,或者在對實物件的操作被呼叫之前檢查前提條件。 + +**程式示例** + +使用上面的巫師塔為例。首先我們有**巫師塔**介面和**象牙塔**類 。 + +```java +public interface WizardTower { + + void enter(Wizard wizard); +} + +public class IvoryTower implements WizardTower { + + private static final Logger LOGGER = LoggerFactory.getLogger(IvoryTower.class); + + public void enter(Wizard wizard) { + LOGGER.info("{} enters the tower.", wizard); + } + +} +``` + +然後有個簡單的巫師類。 + +```java +public class Wizard { + + private final String name; + + public Wizard(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } +} +``` + +然後我們有巫師塔代理類為巫師塔新增訪問控制。 + +```java +public class WizardTowerProxy implements WizardTower { + + private static final Logger LOGGER = LoggerFactory.getLogger(WizardTowerProxy.class); + + private static final int NUM_WIZARDS_ALLOWED = 3; + + private int numWizards; + + private final WizardTower tower; + + public WizardTowerProxy(WizardTower tower) { + this.tower = tower; + } + + @Override + public void enter(Wizard wizard) { + if (numWizards < NUM_WIZARDS_ALLOWED) { + tower.enter(wizard); + numWizards++; + } else { + LOGGER.info("{} is not allowed to enter!", wizard); + } + } +} +``` + +然後這是進入塔的場景。 + +```java +var proxy = new WizardTowerProxy(new IvoryTower()); +proxy.enter(new Wizard("Red wizard")); +proxy.enter(new Wizard("White wizard")); +proxy.enter(new Wizard("Black wizard")); +proxy.enter(new Wizard("Green wizard")); +proxy.enter(new Wizard("Brown wizard")); +``` + +程式輸出: + +``` +Red wizard enters the tower. +White wizard enters the tower. +Black wizard enters the tower. +Green wizard is not allowed to enter! +Brown wizard is not allowed to enter! +``` + +## 類圖 + +![alt text](./etc/proxy.urm.png "Proxy pattern class diagram") + +## 適用性 + +代理適用於需要比簡單指標更廣泛或更復雜的物件引用的情況。這是代理模式適用的幾種常見情況。 + +* 遠端代理為不同地址空間中的物件提供了本地代表。 +* 虛擬代理根據需要建立昂貴的物件。 +* 保護代理控制對原始物件的訪問。當物件有不同的接入許可權時保護代理很有用。 + +## 典型用例 + +* 物件的訪問控制 +* 懶載入 +* 實現日誌記錄 +* 簡化網路連線 +* 物件的訪問計數 + +## 教程 + +* [Controlling Access With Proxy Pattern](http://java-design-patterns.com/blog/controlling-access-with-proxy-pattern/) + +## 已知使用 + +* [java.lang.reflect.Proxy](http://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html) +* [Apache Commons Proxy](https://commons.apache.org/proper/commons-proxy/) +* Mocking frameworks [Mockito](https://site.mockito.org/), +[Powermock](https://powermock.github.io/), [EasyMock](https://easymock.org/) + +## 相關設計模式 + +* [Ambassador](https://java-design-patterns.com/patterns/ambassador/) + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/localization/zh-TW/proxy/etc/proxy.urm.png b/localization/zh-TW/proxy/etc/proxy.urm.png new file mode 100644 index 000000000000..a0c94fc7c717 Binary files /dev/null and b/localization/zh-TW/proxy/etc/proxy.urm.png differ diff --git a/localization/zh-TW/servant/README.md b/localization/zh-TW/servant/README.md new file mode 100644 index 000000000000..195a1389835d --- /dev/null +++ b/localization/zh-TW/servant/README.md @@ -0,0 +1,231 @@ +--- +title: Servant +shortTitle: Servant +category: Behavioral +language: zh +tag: +- Decoupling +--- + +## 含義 +僕人類被用於向一組類提供一些行為,區別於在每個類定義行為-或者當我們無法排除 +公共父類中的這種行為,這些行為在僕人類被定義一次 + +## 解釋 + +現例項子 + +> 國王、王后和其他宮廷皇室成員需要僕人為他們提供飲食、準備飲料等服務 + +簡單地說 + +> 確保一個僕人物件為一組被服務的物件提供一些特定的服務 + +維基百科 + +> 在軟體工程中,僕人模式定義了一個物件,用於向一組類提供某些功能,而無需在每個類中定義該功能。 僕人是一個類,其例項(甚至只是類)提供了處理所需服務的方法,而僕人為其(或與誰)做某事的物件被視為引數。 + +**程式設計示例** + +那些能夠為其他宮廷皇室成員提供服務的僕人類 + +```java +/** + * Servant. + */ +public class Servant { + + public String name; + + /** + * Constructor. + */ + public Servant(String name) { + this.name = name; + } + + public void feed(Royalty r) { + r.getFed(); + } + + public void giveWine(Royalty r) { + r.getDrink(); + } + + public void giveCompliments(Royalty r) { + r.receiveCompliments(); + } + + /** + * Check if we will be hanged. + */ + public boolean checkIfYouWillBeHanged(List tableGuests) { + return tableGuests.stream().allMatch(Royalty::getMood); + } +} +``` + +皇家是一個介面,它被國王和女王類實現,以獲取僕人的服務 + +```java +interface Royalty { + + void getFed(); + + void getDrink(); + + void changeMood(); + + void receiveCompliments(); + + boolean getMood(); +} +``` +國王類正在實現皇家介面 +```java +public class King implements Royalty { + + private boolean isDrunk; + private boolean isHungry = true; + private boolean isHappy; + private boolean complimentReceived; + + @Override + public void getFed() { + isHungry = false; + } + + @Override + public void getDrink() { + isDrunk = true; + } + + public void receiveCompliments() { + complimentReceived = true; + } + + @Override + public void changeMood() { + if (!isHungry && isDrunk) { + isHappy = true; + } + if (complimentReceived) { + isHappy = false; + } + } + + @Override + public boolean getMood() { + return isHappy; + } +} +``` +女王類正在實現皇家介面 +```java +public class Queen implements Royalty { + + private boolean isDrunk = true; + private boolean isHungry; + private boolean isHappy; + private boolean isFlirty = true; + private boolean complimentReceived; + + @Override + public void getFed() { + isHungry = false; + } + + @Override + public void getDrink() { + isDrunk = true; + } + + public void receiveCompliments() { + complimentReceived = true; + } + + @Override + public void changeMood() { + if (complimentReceived && isFlirty && isDrunk && !isHungry) { + isHappy = true; + } + } + + @Override + public boolean getMood() { + return isHappy; + } + + public void setFlirtiness(boolean f) { + this.isFlirty = f; + } + +} +``` + +然後,為了使用: + +```java +public class App { + + private static final Servant jenkins = new Servant("Jenkins"); + private static final Servant travis = new Servant("Travis"); + + /** + * Program entry point. + */ + public static void main(String[] args) { + scenario(jenkins, 1); + scenario(travis, 0); + } + + /** + * Can add a List with enum Actions for variable scenarios. + */ + public static void scenario(Servant servant, int compliment) { + var k = new King(); + var q = new Queen(); + + var guests = List.of(k, q); + + // feed + servant.feed(k); + servant.feed(q); + // serve drinks + servant.giveWine(k); + servant.giveWine(q); + // compliment + servant.giveCompliments(guests.get(compliment)); + + // outcome of the night + guests.forEach(Royalty::changeMood); + + // check your luck + if (servant.checkIfYouWillBeHanged(guests)) { + LOGGER.info("{} will live another day", servant.name); + } else { + LOGGER.info("Poor {}. His days are numbered", servant.name); + } + } +} +``` + +程式輸出 + +``` +Jenkins will live another day +Poor Travis. His days are numbered +``` + + +## 類圖 +![alt text](./etc/servant-pattern.png "Servant") + +## 適用場景 +在什麼時候使用僕人模式 + +* 當我們希望某些物件執行一個公共操作並且不想將該操作定義為每個類中的方法時 + +## 鳴謝 + +* [Let's Modify the Objects-First Approach into Design-Patterns-First](http://edu.pecinovsky.cz/papers/2006_ITiCSE_Design_Patterns_First.pdf) diff --git a/localization/zh-TW/servant/etc/servant-pattern.png b/localization/zh-TW/servant/etc/servant-pattern.png new file mode 100644 index 000000000000..a8237775e332 Binary files /dev/null and b/localization/zh-TW/servant/etc/servant-pattern.png differ diff --git a/localization/zh-TW/sharding/README.md b/localization/zh-TW/sharding/README.md new file mode 100644 index 000000000000..34db73830212 --- /dev/null +++ b/localization/zh-TW/sharding/README.md @@ -0,0 +1,29 @@ +--- +title: Sharding +shortTitle: Sharding +category: Behavioral +language: zh +tag: + - Performance + - Cloud distributed +--- + +## 含義 +分片模式是指將資料儲存劃分為水平分割槽或分片。每個分片都有相同的模式,但持有自己獨特的資料子集。 + +一個分片本身就是一個資料儲存(它可以包含許多不同型別的實體的資料),執行在作為儲存節點的伺服器上。 + +## 類圖 +![alt text](./etc/sharding.urm.png "Sharding pattern class diagram") + +## 適用場景 +這種設計模式提供了一下的好處: + +- 你可以透過增加在額外的儲存節點上,執行的更多分片來實現系統擴容。 +- 系統可以使用現成的廉價硬體,而不是為每個儲存節點使用專門(或者昂貴)的伺服器硬體。 +- 你可以透過平衡各分片之間的工作負載來減少競爭,以提高效能。 +- 在雲環境中,分片可以在物理上靠近訪問該節點資料的使用者。 + +## 引用 + +* [Sharding pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/sharding) \ No newline at end of file diff --git a/localization/zh-TW/sharding/etc/sharding.urm.png b/localization/zh-TW/sharding/etc/sharding.urm.png new file mode 100644 index 000000000000..e7f412af3f01 Binary files /dev/null and b/localization/zh-TW/sharding/etc/sharding.urm.png differ diff --git a/localization/zh-TW/singleton/README.md b/localization/zh-TW/singleton/README.md new file mode 100644 index 000000000000..61f942ffa67b --- /dev/null +++ b/localization/zh-TW/singleton/README.md @@ -0,0 +1,94 @@ +--- +title: Singleton +shortTitle: Singleton +category: Creational +language: zh +tag: + - Gang of Four +--- + +## 目的 + +確保一個類只有一個例項,併為其提供一個全域性訪問點。 + +## 解釋 + +情境示例 + +> 巫師們之在一個象牙塔中學習他們的魔法,並且始終使用同一座附魔的象牙塔。 +> +> 這裡的象牙塔是一個單例物件。 + +通俗來說 + +> 對於一個特定的類,確保只會建立一個物件。 + +維基百科說 + +> 在軟體工程中,單例模式是一種軟體設計模式,它將類的例項化限制為一個物件。當系統中只需要一個物件來協調各種操作時,這種模式非常有用。 + +**程式示例** + +詳見 Joshua Bloch, Effective Java 2nd Edition p.18。 + +> 一個只有一個元素的列舉型別是實現單例模式的最佳方式。 + +```java +public enum EnumIvoryTower { + INSTANCE +} +``` + +使用: + +```java + var enumIvoryTower1 = EnumIvoryTower.INSTANCE; + var enumIvoryTower2 = EnumIvoryTower.INSTANCE; + LOGGER.info("enumIvoryTower1={}", enumIvoryTower1); + LOGGER.info("enumIvoryTower2={}", enumIvoryTower2); +``` + +控制檯輸出: + +``` +enumIvoryTower1=com.iluwatar.singleton.EnumIvoryTower@1221555852 +enumIvoryTower2=com.iluwatar.singleton.EnumIvoryTower@1221555852 +``` + +## 類圖 + +![alt text](./etc/singleton.urm.png "Singleton pattern class diagram") + +## 適用性 + +當滿足以下情況時,使用單例模式: + +* 確保一個類只有一個例項,並且客戶端能夠透過一個眾所周知的訪問點訪問該例項。 +* 唯一的例項能夠被子類擴充套件, 同時客戶端不需要修改程式碼就能使用擴充套件後的例項。 + +一些典型的單例模式用例包括: + +* logging類 +* 管理與資料庫的連結 +* 檔案管理器(File manager) + +## 已知使用 + +* [java.lang.Runtime#getRuntime()](http://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#getRuntime%28%29) +* [java.awt.Desktop#getDesktop()](http://docs.oracle.com/javase/8/docs/api/java/awt/Desktop.html#getDesktop--) +* [java.lang.System#getSecurityManager()](http://docs.oracle.com/javase/8/docs/api/java/lang/System.html#getSecurityManager--) + + +## 影響 + +* 透過控制例項的建立和生命週期,違反了單一職責原則(SRP)。 +* 鼓勵使用全域性共享例項,組織了物件及其使用的資源被釋放。 +* 程式碼變得耦合,給客戶端的測試帶來難度。 +* 單例模式的設計可能會使得子類化(繼承)單例變得幾乎不可能 + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Effective Java](https://www.amazon.com/gp/product/0134685997/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0134685997&linkCode=as2&tag=javadesignpat-20&linkId=4e349f4b3ff8c50123f8147c828e53eb) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/zh-TW/singleton/etc/singleton.urm.png b/localization/zh-TW/singleton/etc/singleton.urm.png new file mode 100644 index 000000000000..46584af40039 Binary files /dev/null and b/localization/zh-TW/singleton/etc/singleton.urm.png differ diff --git a/localization/zh-TW/state/README.md b/localization/zh-TW/state/README.md new file mode 100644 index 000000000000..2b75b61abe69 --- /dev/null +++ b/localization/zh-TW/state/README.md @@ -0,0 +1,154 @@ +--- +title: State +shortTitle: State +category: Behavioral +language: zh +tag: + - Gang of Four +--- + +## 又被稱為 +物件狀態 + +## 目的 +允許物件在內部狀態改變時改變它的行為。物件看起來好像修改了它的類。 + +## 解釋 +真實世界例子 + +> 當在長毛象的自然棲息地觀察長毛象時,似乎它會根據情況來改變自己的行為。它開始可能很平靜但是隨著時間推移當它檢測到威脅時它會對周圍的環境感到憤怒和危險。 + +通俗的說 + +> 狀態模式允許物件改變它的行為。 + +維基百科說 + +> 狀態模式是一種允許物件在內部狀態改變時改變它的行為的行為型設計模式。這種模式接近於有限狀態機的概念。狀態模式可以被理解為策略模式,它能夠透過呼叫在模式介面中定義的方法來切換策略。 + +**程式設計示例** + +這裡是模式介面和它具體的實現。 + +```java +public interface State { + + void onEnterState(); + + void observe(); +} + +public class PeacefulState implements State { + + private static final Logger LOGGER = LoggerFactory.getLogger(PeacefulState.class); + + private final Mammoth mammoth; + + public PeacefulState(Mammoth mammoth) { + this.mammoth = mammoth; + } + + @Override + public void observe() { + LOGGER.info("{} is calm and peaceful.", mammoth); + } + + @Override + public void onEnterState() { + LOGGER.info("{} calms down.", mammoth); + } +} + +public class AngryState implements State { + + private static final Logger LOGGER = LoggerFactory.getLogger(AngryState.class); + + private final Mammoth mammoth; + + public AngryState(Mammoth mammoth) { + this.mammoth = mammoth; + } + + @Override + public void observe() { + LOGGER.info("{} is furious!", mammoth); + } + + @Override + public void onEnterState() { + LOGGER.info("{} gets angry!", mammoth); + } +} +``` + +然後這裡是包含狀態的長毛象。 + +```java +public class Mammoth { + + private State state; + + public Mammoth() { + state = new PeacefulState(this); + } + + public void timePasses() { + if (state.getClass().equals(PeacefulState.class)) { + changeStateTo(new AngryState(this)); + } else { + changeStateTo(new PeacefulState(this)); + } + } + + private void changeStateTo(State newState) { + this.state = newState; + this.state.onEnterState(); + } + + @Override + public String toString() { + return "The mammoth"; + } + + public void observe() { + this.state.observe(); + } +} +``` + +然後這裡是長毛象隨著時間的推移後的整個行為示例。 + +```java + var mammoth = new Mammoth(); + mammoth.observe(); + mammoth.timePasses(); + mammoth.observe(); + mammoth.timePasses(); + mammoth.observe(); + + // The mammoth gets angry! + // The mammoth is furious! + // The mammoth calms down. + // The mammoth is calm and peaceful. +``` + +## 類圖 +![alt text](./etc/state_urm.png "State") + +## 適用性 + +在以下兩種情況下,請使用State模式 + +* 物件的行為取決於它的狀態,並且它必須在執行時根據狀態更改其行為。 +* 根據物件狀態的不同,操作有大量的條件語句。此狀態通常由一個或多個列舉常量表示。通常,幾個操作將包含此相同的條件結構。狀態模式把條件語句的分支分別放入單獨的類中。這樣一來,你就可以將物件的狀態視為獨立的物件,該物件可以獨立於其他物件而變化。 + +## Java中例子 + +* [javax.faces.lifecycle.Lifecycle#execute()](http://docs.oracle.com/javaee/7/api/javax/faces/lifecycle/Lifecycle.html#execute-javax.faces.context.FacesContext-) controlled by [FacesServlet](http://docs.oracle.com/javaee/7/api/javax/faces/webapp/FacesServlet.html), the behavior is dependent on current phase of lifecycle. +* [JDiameter - Diameter State Machine](https://github.com/npathai/jdiameter/blob/master/core/jdiameter/api/src/main/java/org/jdiameter/api/app/State.java) + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/zh-TW/state/etc/state_urm.png b/localization/zh-TW/state/etc/state_urm.png new file mode 100644 index 000000000000..c2cf9f562943 Binary files /dev/null and b/localization/zh-TW/state/etc/state_urm.png differ diff --git a/localization/zh-TW/step-builder/README.md b/localization/zh-TW/step-builder/README.md new file mode 100644 index 000000000000..09d46925c4c4 --- /dev/null +++ b/localization/zh-TW/step-builder/README.md @@ -0,0 +1,25 @@ +--- +title: Step Builder +shortTitle: Step Builder +category: Creational +language: zn +tag: + - Instantiation +--- + +## 又被稱為 +分步構建 + +## 目的 +這是構建者模式的一個擴充套件,完全指導使用者建立物件,沒有混淆的機會。 +使用者體驗會大大提升,因為他只能看到下一個步驟的方法,直到適當的時機才會出現構建物件的“build”方法。 + +## 類圖 +![alt text](./etc/step-builder.png "Step Builder") + +## 應用 +使用分佈構建模式當建立複雜物件的演算法需要獨立於組成物件的部分以及它們的組裝方式,且構造過程必須允許物件有不同的表示形式,並且在此過程中順序很重要時。 + +## 鳴謝 + +* [Marco Castigliego - Step Builder](http://rdafbn.blogspot.co.uk/2012/07/step-builder-pattern_28.html) diff --git a/localization/zh-TW/step-builder/etc/step-builder.png b/localization/zh-TW/step-builder/etc/step-builder.png new file mode 100644 index 000000000000..b7b623657c58 Binary files /dev/null and b/localization/zh-TW/step-builder/etc/step-builder.png differ diff --git a/localization/zh-TW/strategy/README.md b/localization/zh-TW/strategy/README.md new file mode 100644 index 000000000000..19de6403ec04 --- /dev/null +++ b/localization/zh-TW/strategy/README.md @@ -0,0 +1,132 @@ +--- +title: Strategy +shortTitle: Strategy +category: Behavioral +language: zh +tag: + - Gang of Four +--- + +## 又被稱為 +政策(方針)模式 + +## 目的 + +定義一個家族演算法,並封裝好其中每一個,使它們可以互相替換。策略模式使演算法的變化獨立於使用它的客戶。 + +## 解釋 + +現實世界例子 + +> 屠龍是一項危險的職業。有經驗將會使它變得簡單。經驗豐富的屠龍者對不同型別的龍有不同的戰鬥策略。 + +直白點說 + +> 策略模式允許在執行時選擇最匹配的演算法。 + +維基百科上說 + +> 在程式程式設計領域,策略模式(又叫政策模式)是一種啟用在執行時選擇演算法的行為型軟體設計模式。 + +**程式設計例項** + +讓我們先介紹屠龍的策略模式介面和它的實現。 + +```java +@FunctionalInterface +public interface DragonSlayingStrategy { + + void execute(); +} + +@Slf4j +public class MeleeStrategy implements DragonSlayingStrategy { + + @Override + public void execute() { + LOGGER.info("With your Excalibur you sever the dragon's head!"); + } +} + +@Slf4j +public class ProjectileStrategy implements DragonSlayingStrategy { + + @Override + public void execute() { + LOGGER.info("You shoot the dragon with the magical crossbow and it falls dead on the ground!"); + } +} + +@Slf4j +public class SpellStrategy implements DragonSlayingStrategy { + + @Override + public void execute() { + LOGGER.info("You cast the spell of disintegration and the dragon vaporizes in a pile of dust!"); + } +} +``` + +現在有一個強力的屠龍者要基於上面的元件來選擇他的戰鬥策略。 + +```java +public class DragonSlayer { + + private DragonSlayingStrategy strategy; + + public DragonSlayer(DragonSlayingStrategy strategy) { + this.strategy = strategy; + } + + public void changeStrategy(DragonSlayingStrategy strategy) { + this.strategy = strategy; + } + + public void goToBattle() { + strategy.execute(); + } +} +``` + +最後是屠龍者的行動。 + +```java + LOGGER.info("Green dragon spotted ahead!"); + var dragonSlayer = new DragonSlayer(new MeleeStrategy()); + dragonSlayer.goToBattle(); + LOGGER.info("Red dragon emerges."); + dragonSlayer.changeStrategy(new ProjectileStrategy()); + dragonSlayer.goToBattle(); + LOGGER.info("Black dragon lands before you."); + dragonSlayer.changeStrategy(new SpellStrategy()); + dragonSlayer.goToBattle(); + + // Green dragon spotted ahead! + // With your Excalibur you sever the dragon's head! + // Red dragon emerges. + // You shoot the dragon with the magical crossbow and it falls dead on the ground! + // Black dragon lands before you. + // You cast the spell of disintegration and the dragon vaporizes in a pile of dust! +``` + +## 類圖 +![alt text](./etc/strategy_urm.png "Strategy") + +## 應用 +使用策略模式當 + +* 許多相關的類只是行為不同。策略模式提供了一種為一種類配置多種行為的能力。 +* 你需要一種演算法的不同變體。比如,你可能定義反應不用時間空間權衡的演算法。當這些演算法的變體使用類的層次結構來實現時就可以使用策略模式。 +* 一個演算法使用的資料客戶不應該對其知曉。使用策略模式來避免暴露覆雜的,特定於演算法的資料結構。 +* 一個類定義了許多行為,這些行為在其操作中展現為多個條件語句。移動相關的條件分支到它們分別的策略類中來代替這些條件語句。 + +## 教學 + +* [Strategy Pattern Tutorial](https://www.journaldev.com/1754/strategy-design-pattern-in-java-example-tutorial) + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions](https://www.amazon.com/gp/product/1937785467/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1937785467&linkCode=as2&tag=javadesignpat-20&linkId=7e4e2fb7a141631491534255252fd08b) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/zh-TW/strategy/etc/strategy_urm.png b/localization/zh-TW/strategy/etc/strategy_urm.png new file mode 100644 index 000000000000..67d19acae62d Binary files /dev/null and b/localization/zh-TW/strategy/etc/strategy_urm.png differ diff --git a/localization/zh-TW/table-module/README.md b/localization/zh-TW/table-module/README.md new file mode 100644 index 000000000000..2a46ec2b9e9f --- /dev/null +++ b/localization/zh-TW/table-module/README.md @@ -0,0 +1,136 @@ +--- +title: Table Module +shortTitle: Table Module +category: Structural +language: zh +tag: + - Data access +--- + +## 又被稱為 +表模組 + +## Intent +表模組模式將域邏輯按資料庫中的每個表組織為一個類,並且一個類的單個例項包含將對資料執行的各種過程。 + +## Explanation + +現實世界例子 + +> 當處理一個使用者系統時,我們需要在使用者表上進行一些操作。在這種情況下,我們可以使用表模組模式。我們可以建立一個名為 UserTableModule 的類,並初始化該類的一個例項,來處理使用者表中所有行的業務邏輯。 + +直白點說 + +> 一個單獨的例項,處理資料庫表或檢視中所有行的業務邏輯。 + +**程式設計例項** + +在使用者系統的示例中,我們需要處理使用者登入和使用者註冊的域邏輯。我們可以使用表模組模式,並建立UserTableModule類的一個例項來處理使用者表中所有行的業務邏輯。 + +以下是基本的User實體。 + +```java +@Setter +@Getter +@ToString +@EqualsAndHashCode +@AllArgsConstructor +public class User { + private int id; + private String username; + private String password; +} +``` + +下面的是 `UserTableModule` 類. + +```java +public class UserTableModule { + private final DataSource dataSource; + private Connection connection = null; + private ResultSet resultSet = null; + private PreparedStatement preparedStatement = null; + + public UserTableModule(final DataSource userDataSource) { + this.dataSource = userDataSource; + } + + /** + * Login using username and password. + * + * @param username the username of a user + * @param password the password of a user + * @return the execution result of the method + * @throws SQLException if any error + */ + public int login(final String username, final String password) throws SQLException { + // Method implementation. + + } + + /** + * Register a new user. + * + * @param user a user instance + * @return the execution result of the method + * @throws SQLException if any error + */ + public int registerUser(final User user) throws SQLException { + // Method implementation. + } +} +``` + +在App類中,我們使用UserTableModule的一個例項來處理使用者登入和註冊。 + +```java +// Create data source and create the user table. +final var dataSource = createDataSource(); +createSchema(dataSource); +userTableModule = new UserTableModule(dataSource); + +//Initialize two users. +var user1 = new User(1, "123456", "123456"); +var user2 = new User(2, "test", "password"); + +//Login and register using the instance of userTableModule. +userTableModule.registerUser(user1); +userTableModule.login(user1.getUsername(), user1.getPassword()); +userTableModule.login(user2.getUsername(), user2.getPassword()); +userTableModule.registerUser(user2); +userTableModule.login(user2.getUsername(), user2.getPassword()); + +deleteSchema(dataSource); +``` + +程式輸出: + +```java +12:22:13.095 [main] INFO com.iluwatar.tablemodule.UserTableModule - Register successfully! +12:22:13.117 [main] INFO com.iluwatar.tablemodule.UserTableModule - Login successfully! +12:22:13.128 [main] INFO com.iluwatar.tablemodule.UserTableModule - Fail to login! +12:22:13.136 [main] INFO com.iluwatar.tablemodule.UserTableModule - Register successfully! +12:22:13.144 [main] INFO com.iluwatar.tablemodule.UserTableModule - Login successfully! +``` + +## 類圖 + +![](etc/table-module.urm.png "table module") + +## 應用 +使用表模組模式當: + +- 域邏輯簡單且資料呈表格形式。 +- 應用程式僅使用少量共享的常見的面向表格的資料結構。 + +## 教學 + +- [Transaction Script](https://java-design-patterns.com/patterns/transaction-script/) + +- [Domain Model](https://java-design-patterns.com/patterns/domain-model/) + +## 鳴謝 + +* [Table Module Pattern](http://wiki3.cosc.canterbury.ac.nz/index.php/Table_module_pattern) +* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321127420&linkId=18acc13ba60d66690009505577c45c04) +* [Architecture patterns: domain model and friends](https://inviqa.com/blog/architecture-patterns-domain-model-and-friends) \ No newline at end of file diff --git a/localization/zh-TW/table-module/etc/table-module.urm.png b/localization/zh-TW/table-module/etc/table-module.urm.png new file mode 100644 index 000000000000..9c4bc0b189db Binary files /dev/null and b/localization/zh-TW/table-module/etc/table-module.urm.png differ diff --git a/localization/zh-TW/template-method/README.md b/localization/zh-TW/template-method/README.md new file mode 100644 index 000000000000..7025095dce4c --- /dev/null +++ b/localization/zh-TW/template-method/README.md @@ -0,0 +1,144 @@ +--- +title: Template method +shortTitle: Template method +category: Behavioral +language: zh +tag: + - Gang of Four +--- + +## 目的 +在一個操作中定義演算法的骨架,將某些步驟推遲到子類。模板方法允許子類重新定義演算法的某些步驟,而無需更改演算法的結構。 + +## 解釋 +真實世界例子 + +> 偷東西的一般步驟是相同的。 首先,選擇目標,然後以某種方式使其迷惑,最後,你偷走了該物品。然而這些步驟有很多實現方式。 + +通俗的說 + +> 模板方法模式在父類中列出一般的步驟然後讓具體的子類定義實現細節。 + +維基百科說 + +> 在物件導向的程式設計中,模板方法是Gamma等人確定的行為設計模式之一。在《設計模式》一書中。模板方法是父類中一個方法,通常是一個抽象父類,根據許多高階步驟定義了操作的骨架。這些步驟本身由與模板方法在同一類中的其他幫助程式方法實現。 + +**程式設計示例** + +讓我們首先介紹模板方法類及其具體實現。 + +```java +public abstract class StealingMethod { + + private static final Logger LOGGER = LoggerFactory.getLogger(StealingMethod.class); + + protected abstract String pickTarget(); + + protected abstract void confuseTarget(String target); + + protected abstract void stealTheItem(String target); + + public void steal() { + var target = pickTarget(); + LOGGER.info("The target has been chosen as {}.", target); + confuseTarget(target); + stealTheItem(target); + } +} + +public class SubtleMethod extends StealingMethod { + + private static final Logger LOGGER = LoggerFactory.getLogger(SubtleMethod.class); + + @Override + protected String pickTarget() { + return "shop keeper"; + } + + @Override + protected void confuseTarget(String target) { + LOGGER.info("Approach the {} with tears running and hug him!", target); + } + + @Override + protected void stealTheItem(String target) { + LOGGER.info("While in close contact grab the {}'s wallet.", target); + } +} + +public class HitAndRunMethod extends StealingMethod { + + private static final Logger LOGGER = LoggerFactory.getLogger(HitAndRunMethod.class); + + @Override + protected String pickTarget() { + return "old goblin woman"; + } + + @Override + protected void confuseTarget(String target) { + LOGGER.info("Approach the {} from behind.", target); + } + + @Override + protected void stealTheItem(String target) { + LOGGER.info("Grab the handbag and run away fast!"); + } +} +``` + +這是包含模板方法的半身賊類。 + +```java +public class HalflingThief { + + private StealingMethod method; + + public HalflingThief(StealingMethod method) { + this.method = method; + } + + public void steal() { + method.steal(); + } + + public void changeMethod(StealingMethod method) { + this.method = method; + } +} +``` +最後,我們展示半身人賊如何利用不同的偷竊方法。 + +```java + var thief = new HalflingThief(new HitAndRunMethod()); + thief.steal(); + thief.changeMethod(new SubtleMethod()); + thief.steal(); +``` + +## 類圖 +![alt text](./etc/template_method_urm.png "Template Method") + +## 適用性 + +使用模板方法模式可以 + +* 一次性實現一個演算法中不變的部分並將其留給子類來實現可能變化的行為。 +* 子類之間的共同行為應分解並集中在一個共同類中,以避免程式碼重複。如Opdyke和Johnson所描述的,這是“重構概括”的一個很好的例子。你首先要確定現有程式碼中的差異,然後將差異拆分為新的操作。最後,將不同的程式碼替換為呼叫這些新操作之一的模板方法。 +* 控制子類擴充套件。你可以定義一個模板方法,該方法在特定點呼叫“ 鉤子”操作,從而僅允許在這些點進行擴充套件 + +## 教程 + +* [Template-method Pattern Tutorial](https://www.journaldev.com/1763/template-method-design-pattern-in-java) + +## Java例子 + +* [javax.servlet.GenericServlet.init](https://jakarta.ee/specifications/servlet/4.0/apidocs/javax/servlet/GenericServlet.html#init--): +Method `GenericServlet.init(ServletConfig config)` calls the parameterless method `GenericServlet.init()` which is intended to be overridden in subclasses. +Method `GenericServlet.init(ServletConfig config)` is the template method in this example. + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/zh-TW/template-method/etc/template_method_urm.png b/localization/zh-TW/template-method/etc/template_method_urm.png new file mode 100644 index 000000000000..b7babccff96d Binary files /dev/null and b/localization/zh-TW/template-method/etc/template_method_urm.png differ diff --git a/localization/zh-TW/trampoline/README.md b/localization/zh-TW/trampoline/README.md new file mode 100644 index 000000000000..3bd8011a12d2 --- /dev/null +++ b/localization/zh-TW/trampoline/README.md @@ -0,0 +1,136 @@ +--- +title: Trampoline +shortTitle: Trampoline +category: Behavioral +language: zh +tag: +- Performance +--- + +## 目的 + +蹦床模式是用於在 Java 中遞迴地實現演算法,而不會破壞堆疊,並且可以交錯地執行函式,而無需將它們編碼在一起。 + +## 解釋 + +遞迴是一種常用的技術,用於以分而治之的方式解決演算法問題。例如,計算斐波那契累加和與階乘。在這類問題中,遞迴比迴圈更簡單直接。此外,遞迴可能需要更少的代 +碼並且看起來更簡明。有一種說法是,每個遞迴問題都可以使用迴圈來解決,但代價是編寫更難以理解的程式碼。然而,遞迴型解決方案有一個很大的警告。對於每個 +遞迴呼叫,通常需要儲存一箇中間值,並且可用的棧記憶體有限。棧記憶體不足會導致棧溢位錯誤並停止程式執行。蹦床模式是一種允許在 Java 中定義遞迴演算法而無需破壞 +堆疊的技巧。 + +現實世界例子 + +> 使用蹦床模式進行遞迴斐波那契計算,不存在堆疊溢位問題。 + +通俗地說 + +> 蹦床模式允許遞迴而不會耗盡棧記憶體。 + +維基百科上說 + +> 在 Java 中,蹦床是指使用反射來避免使用內部類,例如在事件偵聽器中。反射呼叫的整理操作時間換成了內部類的整理操作空間。 Java 中的蹦床通常涉及建立 GenericListener 以將事件傳遞到外部類。 + +**程式設計例項** + +這是 Java 中的蹦床實現。 + +當在返回的蹦床上呼叫 `get` 時,只要返回的具體例項是蹦床,內部就會在返回的蹦床上迭代呼叫跳轉,並在返回的例項完成後停止。 + +```java +public interface Trampoline { + + T get(); + + default Trampoline jump() { + return this; + } + + default T result() { + return get(); + } + + default boolean complete() { + return true; + } + + static Trampoline done(final T result) { + return () -> result; + } + + static Trampoline more(final Trampoline> trampoline) { + return new Trampoline() { + @Override + public boolean complete() { + return false; + } + + @Override + public Trampoline jump() { + return trampoline.result(); + } + + @Override + public T get() { + return trampoline(this); + } + + T trampoline(final Trampoline trampoline) { + return Stream.iterate(trampoline, Trampoline::jump) + .filter(Trampoline::complete) + .findFirst() + .map(Trampoline::result) + .orElseThrow(); + } + }; + } +} +``` + +使用蹦床獲取斐波那契值。 + +```java +public static void main(String[] args) { + LOGGER.info("Start calculating war casualties"); + var result = loop(10, 1).result(); + LOGGER.info("The number of orcs perished in the war: {}", result); +} + +public static Trampoline loop(int times, int prod) { + if (times == 0) { + return Trampoline.done(prod); + } else { + return Trampoline.more(() -> loop(times - 1, prod * times)); + } +} +``` + +程式輸出: + +```java +19:22:24.462 [main] INFO com.iluwatar.trampoline.TrampolineApp - Start calculating war casualties +19:22:24.472 [main] INFO com.iluwatar.trampoline.TrampolineApp - The number of orcs perished in the war: 3628800 +``` + +## 類圖 + +![alt text](./etc/trampoline_urm.png "Trampoline pattern class diagram") + +## 適用場景 + +使用蹦床模式時 +* 用於實現尾遞迴函式。該模式允許切換無堆疊操作。 +* 用於在同一執行緒上交錯執行兩個或多個函式。 + +## 現實案例 + +* [cyclops-react](https://github.com/aol/cyclops-react) + +## 鳴謝 + +* [Trampolining: a practical guide for awesome Java Developers](https://medium.com/@johnmcclean/trampolining-a-practical-guide-for-awesome-java-developers-4b657d9c3076) +* [Trampoline in java ](http://mindprod.com/jgloss/trampoline.html) +* [Laziness, trampolines, monoids and other functional amenities: this is not your father's Java](https://www.slideshare.net/mariofusco/lazine) +* [Trampoline implementation](https://github.com/bodar/totallylazy/blob/master/src/com/googlecode/totallylazy/Trampoline.java) +* [What is a trampoline function?](https://stackoverflow.com/questions/189725/what-is-a-trampoline-function) +* [Modern Java in Action: Lambdas, streams, functional and reactive programming](https://www.amazon.com/gp/product/1617293563/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617293563&linkId=ad53ae6f9f7c0982e759c3527bd2595c) +* [Java 8 in Action: Lambdas, Streams, and functional-style programming](https://www.amazon.com/gp/product/1617291994/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617291994&linkId=e3e5665b0732c59c9d884896ffe54f4f) diff --git a/localization/zh-TW/trampoline/etc/trampoline_urm.png b/localization/zh-TW/trampoline/etc/trampoline_urm.png new file mode 100644 index 000000000000..f2e9c7439a32 Binary files /dev/null and b/localization/zh-TW/trampoline/etc/trampoline_urm.png differ diff --git a/localization/zh-TW/unit-of-work/README.md b/localization/zh-TW/unit-of-work/README.md new file mode 100644 index 000000000000..000cb76266cf --- /dev/null +++ b/localization/zh-TW/unit-of-work/README.md @@ -0,0 +1,204 @@ +--- +title: Unit Of Work +shortTitle: Unit Of Work +category: Architectural +language: zn +tag: + - Data access + - Performance +--- + +## 又被稱為 +工作單元 + +## 目的 + +當一個業務事務完成時,所有的更新都作為一個大的工作單元一次性傳送,以最小化資料庫的往返次數進行持久化。 + +## 解釋 + +現實世界例子 + +> 武器商人擁有一個包含武器資訊的資料庫。 +> 全城的商販們都在不斷地更新這些資訊,這導致資料庫伺服器的負載很高。 +> 為了使負載更易於管理,我們應用了工作單元模式,將許多小的更新批次傳送。 + +用直白的話來說 + +> 工作單元將許多小的資料庫更新合併成一個批次。 +> 以最佳化往返次數。 + +[MartinFowler.com](https://martinfowler.com/eaaCatalog/unitOfWork.html) 中說 + +> 維護一個受業務事務影響的物件列表。 +> 並協調寫出更改和解決併發問題。 + +**程式設計樣例** + +以下是要持久化到資料庫中的 `Weapon` 的實體。 + +```java +@Getter +@RequiredArgsConstructor +public class Weapon { + private final Integer id; + private final String name; +} +``` + +實現的核心是 `ArmsDealer` 實現了工作單元模式。 +它維護了一個需要完成的資料庫操作對映 (`context`) 當呼叫 `commit` 時 +它會一次性批次應用這些操作。 + +```java +public interface IUnitOfWork { + + String INSERT = "INSERT"; + String DELETE = "DELETE"; + String MODIFY = "MODIFY"; + + void registerNew(T entity); + + void registerModified(T entity); + + void registerDeleted(T entity); + + void commit(); +} + +@Slf4j +@RequiredArgsConstructor +public class ArmsDealer implements IUnitOfWork { + + private final Map> context; + private final WeaponDatabase weaponDatabase; + + @Override + public void registerNew(Weapon weapon) { + LOGGER.info("Registering {} for insert in context.", weapon.getName()); + register(weapon, UnitActions.INSERT.getActionValue()); + } + + @Override + public void registerModified(Weapon weapon) { + LOGGER.info("Registering {} for modify in context.", weapon.getName()); + register(weapon, UnitActions.MODIFY.getActionValue()); + + } + + @Override + public void registerDeleted(Weapon weapon) { + LOGGER.info("Registering {} for delete in context.", weapon.getName()); + register(weapon, UnitActions.DELETE.getActionValue()); + } + + private void register(Weapon weapon, String operation) { + var weaponsToOperate = context.get(operation); + if (weaponsToOperate == null) { + weaponsToOperate = new ArrayList<>(); + } + weaponsToOperate.add(weapon); + context.put(operation, weaponsToOperate); + } + + /** + * All UnitOfWork operations are batched and executed together on commit only. + */ + @Override + public void commit() { + if (context == null || context.size() == 0) { + return; + } + LOGGER.info("Commit started"); + if (context.containsKey(UnitActions.INSERT.getActionValue())) { + commitInsert(); + } + + if (context.containsKey(UnitActions.MODIFY.getActionValue())) { + commitModify(); + } + if (context.containsKey(UnitActions.DELETE.getActionValue())) { + commitDelete(); + } + LOGGER.info("Commit finished."); + } + + private void commitInsert() { + var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue()); + for (var weapon : weaponsToBeInserted) { + LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName()); + weaponDatabase.insert(weapon); + } + } + + private void commitModify() { + var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue()); + for (var weapon : modifiedWeapons) { + LOGGER.info("Scheduling {} for modification work.", weapon.getName()); + weaponDatabase.modify(weapon); + } + } + + private void commitDelete() { + var deletedWeapons = context.get(UnitActions.DELETE.getActionValue()); + for (var weapon : deletedWeapons) { + LOGGER.info("Scrapping {}.", weapon.getName()); + weaponDatabase.delete(weapon); + } + } +} +``` + +以下描述了整個應用是如何組裝起來的。 + +```java +// create some weapons +var enchantedHammer = new Weapon(1, "enchanted hammer"); +var brokenGreatSword = new Weapon(2, "broken great sword"); +var silverTrident = new Weapon(3, "silver trident"); + +// create repository +var weaponRepository = new ArmsDealer(new HashMap>(), new WeaponDatabase()); + +// perform operations on the weapons +weaponRepository.registerNew(enchantedHammer); +weaponRepository.registerModified(silverTrident); +weaponRepository.registerDeleted(brokenGreatSword); +weaponRepository.commit(); +``` + +以下是控制檯輸出。 + +``` +21:39:21.984 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering enchanted hammer for insert in context. +21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering silver trident for modify in context. +21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering broken great sword for delete in context. +21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit started +21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Inserting a new weapon enchanted hammer to sales rack. +21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scheduling silver trident for modification work. +21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scrapping broken great sword. +21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit finished. +``` + +## 類圖 + +![alt text](./etc/unit-of-work.urm.png "unit-of-work") + +## 應用 + +在以下情況時使用單元模式 + +* 為了最佳化資料庫事務所需的時間。 +* 作為工作單元將更改傳送到資料庫,確保事務的原子性。 +* 為了減少資料庫呼叫的次數。 + +## 教程 + +* [Repository and Unit of Work Pattern](https://www.programmingwithwolfgang.com/repository-and-unit-of-work-pattern/) +* [Unit of Work - a Design Pattern](https://mono.software/2017/01/13/unit-of-work-a-design-pattern/) + +## 鳴謝 + +* [Design Pattern - Unit Of Work Pattern](https://www.codeproject.com/Articles/581487/Unit-of-Work-Design-Pattern) +* [Unit Of Work](https://martinfowler.com/eaaCatalog/unitOfWork.html) +* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321127420&linkCode=as2&tag=javadesignpat-20&linkId=d9f7d37b032ca6e96253562d075fcc4a) diff --git a/localization/zh-TW/unit-of-work/etc/unit-of-work.urm.png b/localization/zh-TW/unit-of-work/etc/unit-of-work.urm.png new file mode 100644 index 000000000000..bb192af54477 Binary files /dev/null and b/localization/zh-TW/unit-of-work/etc/unit-of-work.urm.png differ diff --git a/localization/zh-TW/update-method/README.md b/localization/zh-TW/update-method/README.md new file mode 100644 index 000000000000..0a65ba446422 --- /dev/null +++ b/localization/zh-TW/update-method/README.md @@ -0,0 +1,37 @@ +--- +title: Update Method +shortTitle: Update Method +category: Behavioral +language: zn +tag: + - Game programming +--- + +## 又被稱為 +更新方法 + +## 目的 +更新方法模式透過告訴每個物件一次處理一個行為幀來模擬一組獨立的物件。 + +## 解釋 +遊戲世界維護了一個物件的集合。每個物件都實現了一個更新方法,用來模擬該物件行為的一幀。在每一幀中,遊戲會更新集合中的每一個物件。 + +要了解更多關於遊戲迴圈是如何執行的,以及何時呼叫更新方法,請參考“遊戲迴圈模式”。 + +## 類圖 +![alt text](./etc/update-method.urm.png "Update Method pattern class diagram") + +## 應用 +如果說遊戲迴圈模式是自切麵包以來最好的東西,那麼更新方法模式就是它的奶油。有很多玩家與動態實體互動的遊戲都以某種形式使用這個模式。如果遊戲裡有宇航兵、龍、火星人、幽靈或運動員,那麼它很可能使用了這種模式。 + +並且,如果遊戲更為抽象,且移動的部分不像是生動的角色,而更像是棋盤上的棋子,那麼這種模式往往並不合適。在像國際象棋這樣的遊戲中,你不需要同時模擬所有的棋子,也可能不需要告訴每一個兵卒在每一幀都更新自己。 + +當以下情況發生時,更新方法工作得很好: + +- 你的遊戲中有許多物件或系統需要同時執行。 +- 每個物件的行為大部分都獨立於其他物件。 +- 這些物件需要隨時間進行模擬。 + +## 鳴謝 + +* [Game Programming Patterns - Update Method](http://gameprogrammingpatterns.com/update-method.html) diff --git a/localization/zh-TW/update-method/etc/update-method.urm.png b/localization/zh-TW/update-method/etc/update-method.urm.png new file mode 100644 index 000000000000..ddc47b5fe145 Binary files /dev/null and b/localization/zh-TW/update-method/etc/update-method.urm.png differ diff --git a/localization/zh-TW/value-object/README.md b/localization/zh-TW/value-object/README.md new file mode 100644 index 000000000000..d238c113eac6 --- /dev/null +++ b/localization/zh-TW/value-object/README.md @@ -0,0 +1,97 @@ +--- +title: Value Object +shortTitle: Value Object +category: Creational +language: zn +tag: + - Instantiation +--- + +## 又被稱為 +值物件 + +## 目的 + +提供的物件應遵循值語義而不是引用語義。這意味著兩個值物件的相等性不是基於它們的身份。只要兩個值物件的值相同,即使它們不是同一個物件,它們也被認為是相等的。 + +## 解釋 + +現實世界例子 + +> 在一個角色扮演遊戲中,有一個用於英雄屬性統計的類。 +> 這些統計屬性包括力量、智慧和運氣等特徵。 +> 當所有的屬性都相同時,不同英雄的統計資料應被認為是相等的。 + +用直白的話來說 + +> 當值物件的屬性有相同的值時,它們是相等的。 + +維基百科中說 + +> 在電腦科學中,值物件是一個代表簡單實體的小物件。 +> 其相等性不是基於身份的:即當兩個值物件有相同的值的時候。 +> 它們是相等的,而不必要是同一個物件。 + +**程式設計樣例** + +這裡是作為值物件的 `HeroStat` 類。 請注意使用了 +[Lombok's `@Value`](https://projectlombok.org/features/Value) 註解。 + +```java +@Value(staticConstructor = "valueOf") +class HeroStat { + + int strength; + int intelligence; + int luck; +} +``` + +這個示例建立了三個不同的 `HeroStat`s 並比較了它們的相等性。 + +```java +var statA = HeroStat.valueOf(10, 5, 0); +var statB = HeroStat.valueOf(10, 5, 0); +var statC = HeroStat.valueOf(5, 1, 8); + +LOGGER.info(statA.toString()); +LOGGER.info(statB.toString()); +LOGGER.info(statC.toString()); + +LOGGER.info("Is statA and statB equal : {}", statA.equals(statB)); +LOGGER.info("Is statA and statC equal : {}", statA.equals(statC)); +``` + +以下是控制檯的輸出。 + +``` +20:11:12.199 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=10, intelligence=5, luck=0) +20:11:12.202 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=10, intelligence=5, luck=0) +20:11:12.202 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=5, intelligence=1, luck=8) +20:11:12.202 [main] INFO com.iluwatar.value.object.App - Is statA and statB equal : true +20:11:12.203 [main] INFO com.iluwatar.value.object.App - Is statA and statC equal : false +``` + +## 類圖 + +![alt text](./etc/value-object.png "Value Object") + +## 應用 + +當滿足以下情況時,使用值物件: + +* 物件的相等性需要基於物件的值 + +## 現實世界的案例 + +* [java.util.Optional](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html) +* [java.time.LocalDate](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html) +* [joda-time, money, beans](http://www.joda.org/) + +## 鳴謝 + +* [Patterns of Enterprise Application Architecture](http://www.martinfowler.com/books/eaa.html) +* [ValueObject](https://martinfowler.com/bliki/ValueObject.html) +* [VALJOs - Value Java Objects : Stephen Colebourne's blog](http://blog.joda.org/2014/03/valjos-value-java-objects.html) +* [Value Object : Wikipedia](https://en.wikipedia.org/wiki/Value_object) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) diff --git a/localization/zh-TW/value-object/etc/value-object.png b/localization/zh-TW/value-object/etc/value-object.png new file mode 100644 index 000000000000..69a244c80691 Binary files /dev/null and b/localization/zh-TW/value-object/etc/value-object.png differ diff --git a/localization/zh-TW/version-number/README.md b/localization/zh-TW/version-number/README.md new file mode 100644 index 000000000000..5d40c75c1b81 --- /dev/null +++ b/localization/zh-TW/version-number/README.md @@ -0,0 +1,158 @@ +--- +title: Version Number +shortTitle: Version Number +category: Concurrency +language: zh +tag: + - Data access + - Microservices +--- + +## 名字 / 分類 + +版本號 + +## 或稱 + +實體版本控制,樂觀鎖。 + +## 目的 + +解決多個客戶端嘗試同時更新同一實體時的併發衝突。 + +## 解釋 + +現實世界的例子 + +> 愛麗絲(Alice)和鮑勃(Bob)正在管理書,該書儲存在資料庫中。 我們的英雄們正在同時進行更改,我們需要某種機制來防止他們相互覆蓋。 + +通俗地說 + +> 版本號模式可防止對同一實體進行併發更新。 + +維基百科說 + +> 樂觀併發控制假設多個事務可以頻繁完成而不會互相干擾。 在執行時,事務使用資料資源而不獲取這些資源的鎖。 在提交之前,每個事務都將驗證沒有其他事務修改了已讀取的資料。如果檢查發現有衝突的修改,則提交的事務將回滾並可以重新啟動。 + +**程式示例** + +我們有`Book` 已版本化的實體,它有一個複製建構函式。 + +```java +public class Book { + private long id; + private String title = ""; + private String author = ""; + + private long version = 0; // version number + + public Book(Book book) { + this.id = book.id; + this.title = book.title; + this.author = book.author; + this.version = book.version; + } + + // getters and setters are omitted here +} +``` + +我們還有一個 `BookRepository`, 它實現了併發控制。 + +```java +public class BookRepository { + private final Map collection = new HashMap<>(); + + public void update(Book book) throws BookNotFoundException, VersionMismatchException { + if (!collection.containsKey(book.getId())) { + throw new BookNotFoundException("Not found book with id: " + book.getId()); + } + + var latestBook = collection.get(book.getId()); + if (book.getVersion() != latestBook.getVersion()) { + throw new VersionMismatchException( + "Tried to update stale version " + book.getVersion() + + " while actual version is " + latestBook.getVersion() + ); + } + + // update version, including client representation - modify by reference here + book.setVersion(book.getVersion() + 1); + + // save book copy to repository + collection.put(book.getId(), new Book(book)); + } + + public Book get(long bookId) throws BookNotFoundException { + if (!collection.containsKey(bookId)) { + throw new BookNotFoundException("Not found book with id: " + bookId); + } + + // return copy of the book + return new Book(collection.get(bookId)); + } +} +``` + +這是實踐中的併發控制: + +```java +var bookId = 1; +// Alice and Bob took the book concurrently +final var aliceBook = bookRepository.get(bookId); +final var bobBook = bookRepository.get(bookId); + +aliceBook.setTitle("Kama Sutra"); // Alice has updated book title +bookRepository.update(aliceBook); // and successfully saved book in database +LOGGER.info("Alice updates the book with new version {}", aliceBook.getVersion()); + +// now Bob has the stale version of the book with empty title and version = 0 +// while actual book in database has filled title and version = 1 +bobBook.setAuthor("Vatsyayana Mallanaga"); // Bob updates the author +try { + LOGGER.info("Bob tries to update the book with his version {}", bobBook.getVersion()); + bookRepository.update(bobBook); // Bob tries to save his book to database +} catch (VersionMismatchException e) { + // Bob update fails, and book in repository remained untouchable + LOGGER.info("Exception: {}", e.getMessage()); + // Now Bob should reread actual book from repository, do his changes again and save again +} +``` + +程式輸出: + +```java +Alice updates the book with new version 1 +Bob tries to update the book with his version 0 +Exception: Tried to update stale version 0 while actual version is 1 +``` + +## 類圖 + +![alt text](./etc/version-number.urm.png "Version Number pattern class diagram") + +## 適用性 + +將版本號用於: + +* 解決對資料的併發寫訪問 +* 強的資料一致性 + +## 教程 +* [Version Number Pattern Tutorial](http://www.java2s.com/Tutorial/Java/0355__JPA/VersioningEntity.htm) + +## 已知用途 + * [Hibernate](https://vladmihalcea.com/jpa-entity-version-property-hibernate/) + * [Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-versioning) + * [Apache Solr](https://lucene.apache.org/solr/guide/6_6/updating-parts-of-documents.html) + +## 意義 +版本號模式允許實現併發控制,通常透過樂觀離線鎖模式來完成。 + +## 相關模式 +* [Optimistic Offline Lock](https://martinfowler.com/eaaCatalog/optimisticOfflineLock.html) + +## 鳴謝 +* [Optimistic Locking in JPA](https://www.baeldung.com/jpa-optimistic-locking) +* [JPA entity versioning](https://www.byteslounge.com/tutorials/jpa-entity-versioning-version-and-optimistic-locking) +* [J2EE Design Patterns](http://ommolketab.ir/aaf-lib/axkwht7wxrhvgs2aqkxse8hihyu9zv.pdf) diff --git a/localization/zh-TW/version-number/etc/version-number.urm.png b/localization/zh-TW/version-number/etc/version-number.urm.png new file mode 100644 index 000000000000..95a5819b4b6e Binary files /dev/null and b/localization/zh-TW/version-number/etc/version-number.urm.png differ diff --git a/localization/zh-TW/visitor/README.md b/localization/zh-TW/visitor/README.md new file mode 100644 index 000000000000..2e1ad097aae6 --- /dev/null +++ b/localization/zh-TW/visitor/README.md @@ -0,0 +1,223 @@ +--- +title: Visitor +shortTitle: Visitor +category: Behavioral +language: zh +tag: + - Gang of Four +--- + +## 目的 + +表示要在物件結構的元素上執行的操作。訪問者可讓你定義新操作,而無需更改其所操作元素的類。 + +## 解釋 + +真實世界例子 + +> 考慮有一個帶有軍隊單位的樹形結構。指揮官下有兩名中士,每名中士下有三名士兵。基於這個層級結構實現訪問者模式,我們可以輕鬆建立與指揮官,中士,士兵或所有人員互動的新物件 + +通俗的說 + +> 訪問者模式定義可以在資料結構的節點上執行的操作。 + +維基百科說 + +> 在物件導向的程式設計和軟體工程中,訪問者設計模式是一種將演算法與操作物件的結構分離的方法。這種分離的實際結果是能夠在不修改結構的情況下向現有物件結構新增新操作。 + +**程式示例** + +使用上面的軍隊單元的例子,我們首先由單位和單位訪問器型別。 + +```java +public abstract class Unit { + + private final Unit[] children; + + public Unit(Unit... children) { + this.children = children; + } + + public void accept(UnitVisitor visitor) { + Arrays.stream(children).forEach(child -> child.accept(visitor)); + } +} + +public interface UnitVisitor { + + void visitSoldier(Soldier soldier); + + void visitSergeant(Sergeant sergeant); + + void visitCommander(Commander commander); +} +``` + +然後我們有具體的單元。 + +```java +public class Commander extends Unit { + + public Commander(Unit... children) { + super(children); + } + + @Override + public void accept(UnitVisitor visitor) { + visitor.visitCommander(this); + super.accept(visitor); + } + + @Override + public String toString() { + return "commander"; + } +} + +public class Sergeant extends Unit { + + public Sergeant(Unit... children) { + super(children); + } + + @Override + public void accept(UnitVisitor visitor) { + visitor.visitSergeant(this); + super.accept(visitor); + } + + @Override + public String toString() { + return "sergeant"; + } +} + +public class Soldier extends Unit { + + public Soldier(Unit... children) { + super(children); + } + + @Override + public void accept(UnitVisitor visitor) { + visitor.visitSoldier(this); + super.accept(visitor); + } + + @Override + public String toString() { + return "soldier"; + } +} +``` + +然後有一些具體的訪問者。 + +```java +public class CommanderVisitor implements UnitVisitor { + + private static final Logger LOGGER = LoggerFactory.getLogger(CommanderVisitor.class); + + @Override + public void visitSoldier(Soldier soldier) { + // Do nothing + } + + @Override + public void visitSergeant(Sergeant sergeant) { + // Do nothing + } + + @Override + public void visitCommander(Commander commander) { + LOGGER.info("Good to see you {}", commander); + } +} + +public class SergeantVisitor implements UnitVisitor { + + private static final Logger LOGGER = LoggerFactory.getLogger(SergeantVisitor.class); + + @Override + public void visitSoldier(Soldier soldier) { + // Do nothing + } + + @Override + public void visitSergeant(Sergeant sergeant) { + LOGGER.info("Hello {}", sergeant); + } + + @Override + public void visitCommander(Commander commander) { + // Do nothing + } +} + +public class SoldierVisitor implements UnitVisitor { + + private static final Logger LOGGER = LoggerFactory.getLogger(SoldierVisitor.class); + + @Override + public void visitSoldier(Soldier soldier) { + LOGGER.info("Greetings {}", soldier); + } + + @Override + public void visitSergeant(Sergeant sergeant) { + // Do nothing + } + + @Override + public void visitCommander(Commander commander) { + // Do nothing + } +} +``` + +最後,來看看實踐中訪問者模式的力量。 + +```java +commander.accept(new SoldierVisitor()); +commander.accept(new SergeantVisitor()); +commander.accept(new CommanderVisitor()); +``` + +程式輸出: + +``` +Greetings soldier +Greetings soldier +Greetings soldier +Greetings soldier +Greetings soldier +Greetings soldier +Hello sergeant +Hello sergeant +Good to see you commander +``` + +## Class diagram + +![alt text](./etc/visitor_1.png "Visitor") + +## 適用性 + +使用訪問者模式當 + +* 物件結構包含許多具有不同介面的物件類,並且你希望根據這些物件的具體類對這些物件執行操作。 +* 需要對物件結構中的物件執行許多不同且不相關的操作,並且你想避免使用這些操作“汙染”它們的類。 訪問者可以透過在一個類中定義相關操作來將它們保持在一起。當許多應用程式共享物件結構時,請使用訪問者模式將操作僅放在需要它們的那些應用程式中 +* 定義物件結構的類很少變化,但是你經常想在結構上定義新的操作。更改物件結構類需要重新定義所有訪問者的介面,這可能會導致成本高昂。如果物件結構類經常更改,則最好在這些類中定義操作。 + +## 真例項子 + +* [Apache Wicket](https://github.com/apache/wicket) component tree, see [MarkupContainer](https://github.com/apache/wicket/blob/b60ec64d0b50a611a9549809c9ab216f0ffa3ae3/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java) +* [javax.lang.model.element.AnnotationValue](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/AnnotationValue.html) and [AnnotationValueVisitor](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/AnnotationValueVisitor.html) +* [javax.lang.model.element.Element](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/Element.html) and [Element Visitor](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/ElementVisitor.html) +* [java.nio.file.FileVisitor](http://docs.oracle.com/javase/8/docs/api/java/nio/file/FileVisitor.html) + +## 鳴謝 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/zh-TW/visitor/etc/visitor_1.png b/localization/zh-TW/visitor/etc/visitor_1.png new file mode 100644 index 000000000000..de5285d7fdc3 Binary files /dev/null and b/localization/zh-TW/visitor/etc/visitor_1.png differ