# 軟體設計(成大)
[toc]
## ch2 OOP
### part 1. Encapsulation
```java
public class Duck {
public boolean canfly = false; // instance variable
public void quack(){
System.out.println("Quack!!");
}
}
```
#### 封裝 Encapsulation
將程式切割成一塊塊模組
降低程式的耦合度、增加可控度
避免被修改的風險
```java
public class Duck {
private boolean canfly = false; //**
public boolean getCanfly(){
return canfly;
}
…
}
```
#### 多型 Polymorphism
##### Method Overloading
在同個 calss,方法名一樣,相同或高相似度的行為,但不同實作方式的同名函式。
增加可讀性
```java
public class Duck {
…
public void quack(){
System.out.println("Quack!!");
}
public void quack(String sound){
System.out.println(sound);
}
…
}
public class Farm {
public static void main(String[] args) {
Duck duck = new Duck(true);
…
duck.quack();
duck.quack("Ga Ga Ga");
}
}
```
### part 2. Call by Reference
`ToyClass sampleVariable = new ToyClass("JS", 42)`

``` java
variable2 = sampleVariable
variable2 // 指向同一個 sampleVariable
```
```java
public class Cat {
int age = 1;
public static void main(String[] args)
{
Cat cat1 = new Cat();
Cat cat2 = cat1;
cat1.age = 2;
System.out.println(cat2.age); // 2
}
```
```java
public class PrimitiveParameterDemo {
public static void main(String[] args)
{
int speed = 50;
System.out.println("argument value:" + speed);
changer(speed); // Call by Value
System.out.println("argument value:" + speed); //50
}
public static void changer(int speed)
{
speed = 100;
System.out.println("parameter value:" + speed);
}
}
```
argument value: 50
parameter value: 100
argument value: 50 (Call by Value)
```java
public class ClassParameterDemo
{
public static void main(String[] args)
{
ToyClass anObject = new ToyClass("Robot Dog", 10);
System.out.println(anObject);
changer(anObject); // Call by Reference
System.out.println(anObject);
}
public static void changer(ToyClass aParameter)
{
aParameter.set("Robot Cat",20);
}
}
```
Robot Dog 10
Robot Cat 20 (Call by Reference)
### part 3. 繼承 Inheritance
讓子類別能夠使用父類別的行為、屬性跟方法,
也可以藉由抽象類別跟介面,先定義規格,而在不同的子類別做出不一樣的實作方式。
properties = member
蘋果繼承水果
鋼筆繼承筆
是 super 跟 Derived Class (Subclass) 的關係
#### overriding
不同class,同名方法,父親的行為能被兒子使用,但實作可以不同。
overloading 則是同個 class。
```java
public class Employee {
protected String name;
protected Date hireDate;
public Employee(){}
public Employee(String theName, Date theDate){
name = theName;
hireDate = theDate;
}
public Date getHireDate(){
return hireDate;
}
public String getName(){
return name;
}
}
```
```java
import java.util.Date;
public class HourlyEmployee extends Employee{
private double wageRate;
public HourlyEmployee(String theName, Date theDate, double rate){
name = theName;
hireDate = theDate;
wageRate = rate;
}
public String getName(){
return "Hourly Employee:" + name; // 不同實作方式
}
}
```
### part 4. 抽象類別 Abstract Class & 介面 Interface
#### Abstract Class
抽象,未被實作的,實作留給相關類去實作。
用以定義規格或api,通常在父親。
```java
public abstract class Animal {
public abstract void run();
public void sit(){ System.out.println(“Sit down…”); }
}
```
必讓兒子去實作run(),在下面層級能讓其做不同的行為
```java
public class Dog extends Animal {
public void run(){
System.out.println("The dog is running");
}
}
```
```java
public class Cat extends Animal{
public void run(){
System.out.println("The cat is running");
}
}
```
#### Interface
```java
public interface Shape {
int color = 1; // => public static final int color = 1; 常數
}
```
```java
public class Paint {
public static void main(String[] args) {
System.out.println(Shape.color);
}
}
```
```java
public interface Shape {
int color = 1; // => public static final int color = 1;
public abstract double area(); //=> 或是 double area();
// 只能為抽象方法
}
```
Abstract Classes can Implementing Interfaces
### part 5. 多型 Polymorphism
一個抽象行為有不同的實作
#### Early binding (through overriding)
compiler time 時就決定,靜態決定。
```java
public class SayHello {
public String sayHello(String name){
return "Hello! "+ name;
}
public String sayHello(String name, String gender){
if(gender.equals("boy")){
return "Hello! Mr. "+ name;
}
else if(gender.equals("girl")){
return "Hello! Miss. "+ name;
}else{
return "Hello! "+ name;
}
}
public static void main(String[] args){
SayHello hello = new SayHello();
System.out.println(hello.sayHello("S.J.")); //decided at compile time
System.out.println(hello.sayHello("S.J.","boy")); //decided at compile time
}
}
```
#### Late binding (through overriding)
run time時才決定,動態決定。
```java
public class Payment {
public void pay(){
System.out.println("Pay in cash");
}
public void checkout(){
pay();
}
}
```
```java
public class CreditCardPayment extends Payment{
public void pay() {
System.out.println("Pay with credit card");
}
}
```
```java
public class Store {
public static void main(String[] args) {
Payment p1 = new Payment();
p1.checkout();
Payment p2 = new CreditCardPayment();
p2.checkout(); // ("Pay with credit card")
}
}
```
會往上尋找實作
1. checkout() 只有被父親 Payment() 實作
2. 父親裡的 pay() 有被兒子 CreditCardPayment() 所實作
3. 所以最後是 call 兒子的 pay()
得到 "Pay with credit card"
#### Upcasting and Downcasting
##### Upcasting
```java
Payment p2 = new CreditCardPayment();
p2.checkout();
```
##### Downcasting
```java
Payment p1 = new Payment();
CreditCardPayment p2 = (CreditCardPayment)p1; //runtime error
```
## ch3 UML Class Diagram
### Class Notation

#### Abstract and Interface
**Class name** and **Method** 為斜體字

### Relationship
#### Generalization 概括(抽象化)
是否有 <font color=red>is-a</font> 的關係?
為 **class** 跟 **Interface/Abstract** class 的差別


#### Dependency 依賴
是否有 <font color=red>uses-a</font> 的關係?
通常為 Method 的 **Parameters** 或 **Local Variable**
虛線:關係較薄弱

```java
public class Tourist {
public void buy(TicketCounter tc) {
tc.sellTicket(); // use TicketCounter's Method
}
}
```
```java
public class TicketCounter {
public String sellTicket() {
return "Random Ticket No.";
}
}
```

#### Association
是否有 <font color=red>has-a</font> 的關係?
實線:關係跟狀態比較穩固跟強烈

```java
import java.util.ArrayList;
public class Library {
private ArrayList<Book> books = new ArrayList<Book>();
public void addBook(Book book) { // 創建了穩固的 book 實例
books.add(book);
}
}
```
```java
public class Book {
private String title;
}
```

#### Bidirectional Association 雙向關係
無箭頭直線
缺點:難以加入新的元素,如學生的課程分數

```java
import java.util.ArrayList;
import java.util.List;
public class Student {
private List<Course> courses =
new ArrayList<>();
public void enroll(Course course) {
courses.add(course);
}
}
```
```java
import java.util.ArrayList;
import java.util.List;
public class Course {
private List<Student> students =
new ArrayList<>();
public void add(Student student) {
students.add(student);
}
}
```
##### Association Class 關聯類別
改善方式:用新的 class 重構

```java
public class Enrollment {
private Student student;
private Course course;
public Enrollment(Student student,Course course) {
this.student = student;
this.course = course;
}
}
```
```java
public class Student {
private List<Enrollment> enrollments = new ArrayList<>();
public void enroll(Course course) {
Enrollment enrollment = new Enrollment(this, course);
enrollments.add(enrollment);
course.addEnrollment(enrollment);
}
```
```java
public class Course {
private List<Enrollment> enrollments = new ArrayList<>();
public void add(Enrollment enrollment){
enrollments.add(enrollment);
}
```
##### Aggregation 聚合
一個類別「包含/擁有」另一個類別
為「較弱」的聚合(Whole-Part)關係
whole消失,part可繼續存在
使用 <font color=red> 空菱形 </font>
如:即使班級不存在,學生也能在其他班級中繼續存在。
如:餐廳有許多顧客,但餐廳倒了,顧客可以去其他餐廳。
如:電腦有cpu,電腦壞了,cpu可以拿到其他電腦使用。


但兩者實作方式一樣
##### Composition
為較「強烈」「包含/擁有」的關係
whole消失,part**不**可繼續存在
使用 <font color=red> 實心菱形 </font>


1. 封裝在裡面
```java
class Car {
private Engine engine;
public Car() {
this.engine = new Engine();
}
private class Engine {
...
}
}
```
2. 放在外面,確保其他物件不持有 part 物件的 Reference
```java
class Car {
private Engine engine;
public Car() {
this.engine = new Engine();
}
```
```java
class Engine {
...
}
```
3. WeakReference
```java
import java.lang.ref.WeakReference;
class Car {
private Engine engine;
public Car() {
this.engine = new Engine();
}
public WeakReference<Engine> getEngineReference() {
return new WeakReference<>(engine);
}
}
```
#### Tips
* code 不能完全實現 UML
* UML工具 才能實現一樣的 code

無法產生菱形箭頭,實作一樣
### 例題
1. A country <font color=red>has a</font> capital city.

:::spoiler
```java
public class Country {
private String name;
private City CapitalCity;
public Country(String name, City city) {
this.name = name;
this.CapitalCity = city;
}
}
```
```java
public class City {
private String name;
}
```
:::
2. A dining philosopher is <font color=red>using a</font> fork.

:::spoiler
```java
public class DiningPhilosopher {
public void useFork(Fork fork) {
fork.eat();
}
}
```
```java
public class Fork {
public void eat() {
// 實作
}
}
```
:::
3. A file <font color=red>is an</font> ordinary file or a directory file.

:::spoiler
```java
public abstract class File {
private String name;
private Record records;
public File(String name) {
this.name = name;
}
public abstract void open();
public abstract void close();
}
```
```java
public class OrdinaryFile extends File{
public OrdinaryFile(String name) {
super(name);
}
@Override
public void open() {// 實作}
@Override
public void close() {// 實作}
}
```
```java
public class DirectoryFile extends File{
public DirectoryFile(String name) {
super(name);
}
@Override
public void open() {// 實作}
@Override
public void close() {// 實作}
}
```
:::
4. Files <font color=red>contain(包含)</font> records.

:::spoiler
```java
public class File {
private String name;
private Record records;
}
```
```java
public class Record {
private int id;
}
```
:::
5. A polygon <font color=red>is composed of</font> points.

:::spoiler
```java
import java.util.List;
import java.util.ArrayList;
public class Polygon {
private List<Point> points;
public Polygon() {
points = new ArrayList<>();
}
}
```
```java
public class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
}
```
:::
6. A drawing object <font color=red>is a</font> text, a geometrical object, or a group.

:::spoiler
```java
public interface DrawingObject {
void draw();
}
```
```java
public class TextObject implements DrawingObject{
private String text;
public void TextObject(String text) {
this.text = text;
}
public void draw() {
System.out.println("Drawing text: " + text);
}
}
```
```java
public class GeometricObject implements DrawingObject{
private String shape;
public GeometricObject(String shape) {
this.shape = shape;
}
public void draw() {
System.out.println("Drawing shape: " + shape);
}
}
```
```java
public class Group implements DrawingObject{
private List<DrawingObject> objects;
public Group() {
objects = new ArrayList<>();
}
public void draw() {// 實作}
}
```
:::
### 總結
| Inheritance(繼承) | Implementation(實作) |
| -------- | -------- |
| **class** | **Interface/Abstract** |
| is-a | is-a |
| 三角形實線 | 三角形虛線 |
| |  |
| Dependency(依賴) | Association(關聯) |
| -------- | -------- |
| Parameters 或 Local Variable | 被設為Attribute |
| uses-a 短期使用(臨時) | has-a 長期擁有(結構) |
| 箭頭虛線 | 箭頭實線 |
| |  |
| Bidirectional Association(雙向關聯) | Aggregation(聚合) | Composition |
| -------- | -------- | -------- |
| 互相被設為Attribute | whole消失 part不消失 | whole消失 part也消失 |
| 互相擁有 | 弱引用(擁有) | 強引用(擁有) |
| 實線無箭頭 | 實線空心菱形 | 實線實心菱形 |
| |  | |
## ch3 Code Structure View via UML Class Diagram
* Legacy Code 理解既有程式碼
* Trace code 逆向工程過程:透過 zoom in/out 理解code
* Structure (靜態結構) ➔ 地圖
* Behavior (動態行為) ➔ 路徑
### Class Diagram三步驟
1. 設定New Class Diagram
add dependency

2. 更新遺漏的Dependency
修正如下【Ctrl + a 全選】➔【在任一class上右鍵】➔【Add】➔【Dependencies】
3. 更新Layout
【畫面右鍵】➔【Layout Diagram】
### Levels of Abstraction in Java Code Structure

#### Package Level

* Cyclic dependency:問題
* Unidirectional dependency:理想
##### 資訊減量
只看重要的部分

#### Class Level(Intra-Package)
1. 關注依賴於高階抽象(interfaces、abstract classes)或低階實體(sub-classes)
2. 關注bidirectional dependencies

##### 資訊減量
* 可隱藏核心外如data classes、utility classes、exception classes、enums、composition root、UI-layer classes
* 可隱藏dependencies,只顯示實線(inheritance,implementation與association)

* Isolated classes (獨立的classes)
* 重複的association lines
* composition root

##### 何謂Composition Root
* 負責初始化和組合應用程式中所有相互依賴物件的類別
A Composition Root is a single, logical location in an
application where modules are composed together.
* Close to the application’s entry point
* Takes care of composing object graphs of loosely coupled classes.
* Takes a direct dependency on all modules in the system.
Composition Root很混亂,可以先移除整理後再加入

#### Class Level (Cross-Package)

##### 資訊減量



* 直接將大量所有package中的class關係結構全部一起視
覺化會相當困難,遊走(zoom in/out)於levels of
abstraction是個較可行的做法
* 透過各個level的觀察重點與資訊減量有助於理解code
structure
* 視覺化後的code structure有時顯得複雜,但不代表就是
不好的結構設計,需要搭配design principle進行評估與
權衡
* 請注意,其他code structure visualization工具可能會與
ObjectAid有差異,甚至有些程式語言的逆向工程工具
不是產生UML Class Diagram,但都值得進一步了解
## ch4 Code Smells
不好的味道:不好的程式碼
### Code Smells
#### Unresolved warnings
有 warning
比如:使用被棄用的code、null pointer

#### Memory Leak
memory deallocated
佔用記憶體空間
可能 out of memory

#### Long method
沒有標準:一個畫面的長度、參考行數、程式語意跟 Method name 是否吻合
問題:
1. 不易讀、難理解
2. 不易命名(方法的語意)
3. 不易reuse
方法:用 Extract Method 抽出來成短 method

#### Feature Envy
大量使用其他(自己以外) class 的變數、內容

#### Unsuitable naming
不適當命名

#### Downcasting
向下轉型


但有不得不的狀況:
1. api 不給動
2. 舊程式
3. Deserialization
#### Loop termination conditions are obvious and invariably achievable
結束條件不明顯

#### Parentheses are used to avoid ambiguity
不明確的括號,造成問題或不易讀

#### Lack of comments
缺少註解:不易讀
- 結論:有需要再加,不需要就不用
- 避免 comments 跟 code 不一致,修改 code 但 comments 沒有更新
#### Files are checked for existence before attempting to access them
讀檔要確認有正確載入
```c
if (inputFileStream.is_open()) {
// do something
}
```
#### Duplicated Code
重複的 code

#### Access modifiers
All methods have appropriate access modifiers and return types
適當的存取和返回

#### Redundant or Unused variables
沒用到、沒用的變數
### Indexes or subscripts are properly initialized, just prior to the loop
沒有初始化或沒有宣告值

#### Is overflow or underflow?
數字是否超過型態的範圍
注意大數字

#### Are divisors tested for zero?
分母為'0',除法時要做判斷
#### Inconsistent coding standard
應符合程式風格

#### Data clumps

重複的 data 變數可以抽出class

#### Simulated Polymorphism

使用時會有分類、擴展時再使用:
比如有新的動物,減少去修改既有的class

#### Large class
沒有 Single Responsibility Principle (SRP)

能根據語意再次拆分,使用者/設定/log/檔案處理。
#### Long parameter list
參數多,代表參數可能也可以分群

#### Message Chains

如果一個區塊改變,那後續交錯或串聯的都會受影響,不好維護。

##### 重構1:新增Method
不跟陌生人講話,透過窗口對話。
Client only talks to Company(Demeter’s Law)



違反RSP
##### 重構2:新增中介Class(Façade Pattern)
新增一個窗口或API(CompanyService)幫我呼叫
讓這個窗口符合它的SRP,就是為了幫我呼叫的任務


##### 總結

#### Literal constants
常數應被替代,增加可讀性

#### uncalled or unneeded procedures or any unreachable code
不需要存在

#### switch statement have a default

#### comparing floating-point numbers for equality

#### Divergent Change(發散式改變)(*)
一個類別會因為要因應太多的變更原因而需修改
方法:利用 Extract Class 重構
* 範例:

* 重構:

符合SRP(是否同一個Class中的Methods相互依賴或共用屬性)
舉例:
會因為編碼方式、傳輸方式、解碼方式多種原因需要修改該class
```java
class VideoService {
public void encodeVideo(String format, String filePath) {
// 將影片編碼成指定格式
}
public void transmitVideo(String protocol, String filePath) {
// 通過指定協議傳輸影片
}
public void decodeVideo(String filePath) {
// 解碼影片
}
}
```
```java
// 負責影片編碼的類別
class VideoEncoder {
public void encode(String format, String filePath) {
// 將影片編碼成指定格式
}
}
// 負責影片傳輸的類別
class VideoTransmitter {
public void transmit(String protocol, String filePath) {
// 通過指定協議傳輸影片
}
}
// 負責影片解碼的類別
class VideoDecoder {
public void decode(String filePath) {
// 解碼影片
}
}
```
#### Shotgun Surgery(*)散彈式修改
* 每次為了因應一種變更,你必須同時在許多類別上做出許多修改。
* 當有太多需修改的地方時,將造成難以尋找所有需修改處,並容易遺漏。
* 常發生於Copy and Paste Programming

* 範例:
更改主題,要手動傳入新的主題設定,黑底要白字,白字要黑底,它們不會一起連動。
```java
public class ThemeApp {
public static void main(String[] args) {
ThemeApp app = new ThemeApp();
Button button = new Button("Dark");
TextBox textBox = new TextBox("White");
// 更改主題,要手動傳入新的主題設定,黑底要白字,白字要黑底,它們不會一起連動。
}
}
```
改善後
```java
// 抽象主題接口
interface Theme {
String getBackgroundColor();
String getTextColor();
}
// Dark主題
class DarkTheme implements Theme {
public String getBackgroundColor() {
return "Black";
}
public String getTextColor() {
return "White";
}
}
// Light主題
class LightTheme implements Theme {
public String getBackgroundColor() {
return "White";
}
public String getTextColor() {
return "Black";
}
}
public class ThemeApp {
public static void main(String[] args) {
Theme darkTheme = new DarkTheme();
Theme lightTheme = new LightTheme();
Button button = new Button(darkTheme);
TextBox textBox = new TextBox(darkTheme);
}
}
```
* 舉例(按鈕):
原本要修改所有按鈕的顏色要一個一個修改
```java
public class Button {
private String color;
public Button(String color) {
this.color = color;
}
public class BuildAllButton {
public void createButtons() {
Button button1 = new Button("Red");
Button button2 = new Button("Red");
// 修改顏色
button1 = new Button("Blue");
button2 = new Button("Blue");
}
}
```
這裡我可以一次修改所有按鈕顏色
```java
// Button 類別
public class Button {
public String getColor() {
return ButtonStyleManager.getButtonColor(); // 即時取得最新顏色
}
public void display() {
System.out.println("Button color: " + getColor());
}
}
// ButtonStyleManager 類別,管理按鈕的顏色
public class ButtonStyleManager {
private static String buttonColor = "Red"; // 默認顏色
// 設定顏色
public static void setButtonColor(String color) {
buttonColor = color;
}
// 取得顏色
public static String getButtonColor() {
return buttonColor;
}
}
// BuildAllButton 類別,負責建立並管理按鈕
public class BuildAllButton {
public void createButtons() {
Button button1 = new Button();
Button button2 = new Button();
// 顯示按鈕的初始顏色
button1.display(); // Button color: Red
button2.display(); // Button color: Red
// 修改顏色
ButtonStyleManager.setButtonColor("Blue");
// 顯示按鈕的更新後顏色
button1.display(); // Button color: Blue
button2.display(); // Button color: Blue
}
}
```
#### Primitive Obsession
* 堅持用基本型態表達
* Loss of Type Safety 容易犯錯
* Lack of Encapsulated Behavior 沒辦法表示行為
將String PostalCode 改為 object
就能有行為去做如 check 的動作
* Replacing Primitives with(Value) Objects

* 舉例:
```java
public class Person {
private String name;
private String idCard; // 身分證 ID 用 String 來表示
public Person(String name, String idCard) {
this.name = name;
this.idCard = idCard;
}
```
改善後:
```java
public class IdCard {
private String idCardNumber;
public IdCard(String idCardNumber) {
if (!isValidIdCard(idCardNumber)) {
throw new IllegalArgumentException("Invalid ID Card number");
}
this.idCardNumber = idCardNumber;
}
public class Person {
private String name;
private IdCard idCard; // 身分證 ID 現在是 IdCard 類別的實例
public Person(String name, IdCard idCard) {
this.name = name;
this.idCard = idCard;
}
```
#### Operation Class 行為物件
一般情況,class name應該要為名詞。
* Operation Class的Class Name通常為動詞(CreateReport),而非物件名詞(Report)
* 通常一個Class包含只有一個Method
* 由於Class Name已經限制了語意,因此很難再擴充Method,造成須相對創建了許多Class
* 由於Class Name為功能特性思維去命名,因此較難以物件導向思維去創建繼承關係以及動態多型的優勢
如果為動詞,通常只能做一個行為,那就會建立許多class來完成不同任務。
範例:

重構:

#### Alternative Classes with Different Interfaces
實現了類似的功能,但在不同的介面或是不同的實作
範例:

重構:


#### Refused Bequest
The unneeded methods may simply go unused or be redefined and give off exceptions.
兒子繼承父親,但不要父親全部的 methods
為什麼兒子不想繼承父親,可能要修改 method 或是拆解,使其合理化。
範例:

重構:

* 舉例:
飛行器中有翅膀的屬性,雖然直升機也是飛行器但它沒有翅膀。
```java
public class FlyingVehicle {
private String engine;
private String wings; // 不適用於所有飛行器,直升機不需要翅膀
}
public class Helicopter extends FlyingVehicle {
private String rotor;
}
```
改善後:
```java
public abstract class FlyingVehicle {
private String engine;
}
public class Airplane extends FlyingVehicle {
private String wings;
public Airplane(String engine, String wings) {
super(engine);
this.wings = wings;
}
}
public class Helicopter extends FlyingVehicle {
private String rotor;
public Helicopter(String engine, String rotor) {
super(engine);
this.rotor = rotor;
}
}
```
#### Parallel Inheritances Hierarchies 平行繼承
兩棵樹,如果一邊要增加,另一邊也要跟著增加。
問題:無法滿足兩個樹底下的物件互相有特定配對依賴關係的要求。
車子 搭配 駕駛員
飛機 搭配 飛行員
導致其有特定的配對

##### Defer Identification of State Variables Pattern
* 第一步(屬性降階層):將Vehicle的operator屬性移除,並在Car與Plane中各別加入欲配對的屬性型態
* 第二步(加**Abstract Accessor**):在Vehicle中加入getOperator (稱之為Abstract Accessor)讓Car與Plane實作,以達成維持原本Vehicle與Operator的關係

#### Middle Man
多餘的,沒有功能的中間人,只是在傳遞事情。
那個中間人同時也是 Feature Envy

* 舉例
```java
public class Order {
private String customerName;
private String product;
public Order(String customerName, String product) {
this.customerName = customerName;
this.product = product;
}
public String getCustomerName() {
return customerName;
}
public String getProduct() {
return product;
}
}
public class OrderProcessor {
private Order order;
public OrderProcessor(Order order) {
this.order = order;
}
public void processOrder() {
// 這些本來應該是 Order 類別的工作
String customerName = order.getCustomerName();
String product = order.getProduct();
// 然後傳遞給其他物件處理
System.out.println("Processing order for " + customerName + " who ordered " + product);
// 實際工作是由 Order 類別來處理的,OrderProcessor 只是中介人
}
}
public class Main {
public static void main(String[] args) {
Order order = new Order("John Doe", "Laptop");
OrderProcessor processor = new OrderProcessor(order);
processor.processOrder();
}
}
```
#### Speculative Generality
過分假設未來的情況,預留空間導致程式很複雜,overdesign。

* 舉例:
```java
// 訂單類別,可能會有多種不同的訂單類型
public abstract class Order {
public abstract void process();
}
public class PhysicalOrder extends Order {
@Override
public void process() {
// 處理實體商品訂單
System.out.println("Processing physical order...");
}
}
public class DigitalOrder extends Order {
@Override
public void process() {
// 處理數位商品訂單
System.out.println("Processing digital order...");
}
}
// 訂單處理器,過於通用,設計上沒有真正需要
public class OrderProcessor {
public void processOrder(Order order) {
// 處理任何類型的訂單
order.process();
}
}
```
## ch5 Design Principles
SOLID原則
### Single Responsibility Principle(SRP)
* A module should have one, and only one, reason to change.
* 因為單一理由才去改變它,而非多個理由。
* A module should be responsible to one, and only one, actor.
* 應該只扮演一個角色,只負責一個職責。
不同使用者,就必須要改變它的作法。

#### 具體判定法
1. 結構內聚力判定法
Method間的結構關係
2. 語意結合判定法
Class Name與Method Name間的結合語意
##### 結構內聚力判定法
1. they both access the same class-level variable, or
兩個 Method 都共用 variable 或 attribute
2. A calls B, or B calls A.
* 精神:將一個class內不互相依賴的method群拆解出去
* 範例:例如一個class中有method A, B, C, D, E,關係如下,因此可參考是否判定為兩個responsibility,進而拆分為兩個Class

* 重構範例:

把相互依賴的,經過拆解分群,以增加內聚力:

##### 語意結合判定法
* 用語意判定是否合理
* 依每個method填入下表,構成語句:The Automobile (主詞) starts (動詞) itself.
* 判定此句子是否具合理語意,若合理則留下,若不合理則考慮將此method移出此class

* 拆分以符合SRP

##### 總結:兩者判定法的限制
* 當一個class內method間結構關係複雜時,結構內聚力判定法可能較困難
* 當一個class name語意太general時(如XXXManager/Controller),會讓所有method name都可與class name語意結合,造成語意結合判定失效
### Open-Close Principle(開放關閉原則)OCP
* Open for extension, but closed for modification
一個模組必須有彈性的開放往後的擴充,並且避免因為修改而影響到使用此模組的程式碼。

不能有封閉迴路circle

* 舉例
根據不同的員工類型計算薪資
會因為增加不同的員工類型而受改變
```java
// Employee class and subclasses
abstract class Employee {
String name;
public abstract String getType();
}
class FullTimeEmployee extends Employee {
public String getType() { return "FullTime"; }
}
class PartTimeEmployee extends Employee {
public String getType() { return "PartTime"; }
}
// SalaryCalculator that violates OCP
class EmployeeSalaryCalculator {
public double calculateSalary(Employee employee) {
if (employee.getType().equals("FullTime")) {
return 50000; // Full-time salary
} else if (employee.getType().equals("PartTime")) {
return 30000; // Part-time salary
}
return 0;
}
}
```
改善後:
```java
// Employee class and subclasses
abstract class Employee {
String name;
public abstract double calculateSalary();
}
class FullTimeEmployee extends Employee {
public double calculateSalary() { return 50000; }
}
class PartTimeEmployee extends Employee {
public double calculateSalary() { return 30000; }
}
class Freelancer extends Employee {
public double calculateSalary() { return 20000; }
}
// SalaryCalculator that conforms to OCP
class EmployeeSalaryCalculator {
public double calculateSalary(Employee employee) {
return employee.calculateSalary();
}
}
```
### Liskov Substitution Principle(LSP)
T是父親S是兒子,兒子可以取代父親

* 舉例:正方形不是一種長方形,不能取代。
正方形不能被設定成不同長寬,所以不是長方形。

* 改善:
```java
interface Shape {
int getArea();
}
class Rectangle implements Shape {
protected int width;
protected int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
@Override
public int getArea() {
return width * height;
}
}
class Square implements Shape {
private int side;
public Square(int side) {
this.side = side;
}
public void setSide(int side) {
this.side = side;
}
@Override
public int getArea() {
return side * side;
}
}
```
### Interface Segregation Principle(ISP)
將大型接口分割成多個更專門的小接口,使得類別只需實作自己真正需要的接口。

窗口在interface介面上

* 舉例
```java
interface Worker {
void developSoftware();
void testSoftware();
void deploySoftware();
}
class Developer implements Worker {
@Override
public void developSoftware() {
System.out.println("Developer is developing software.");
}
@Override
public void testSoftware() {
// Developers一般不負責測試,這裡留空或隨便實作
throw new UnsupportedOperationException("Developer does not test software.");
}
@Override
public void deploySoftware() {
System.out.println("Developer is deploying software.");
}
}
```
改善後:
```java
interface Developer {
void developSoftware();
void deploySoftware();
}
interface Tester {
void testSoftware();
}
class SoftwareDeveloper implements Developer {
@Override
public void developSoftware() {
System.out.println("Developer is developing software.");
}
@Override
public void deploySoftware() {
System.out.println("Developer is deploying software.");
}
}
class SoftwareTester implements Tester {
@Override
public void testSoftware() {
System.out.println("Tester is testing software.");
}
}
```
### Dependency Inversion Principle(依賴反向原則)(DIP)(*)
* 軟體設計的程序開始於簡單高層次的概念(Conceptual),慢慢的增
加細節和特性,使得越來越複雜
* 從高層次的模組開始,再設計低層詳細的模組。
* Dependency Inversion Principle (依賴反向原則)
* 高階模組不應該依賴低階模組,兩者必須依賴抽象(即抽象層)。
越低層次被變動的機率越高,所以要降低高階依賴低階的情況。

介入一層中間層(介面或抽象層):

利用 Dependency Injection 的概念。
```java
// 定義抽象的資料傳輸接口
interface DataTransport {
connect(url: string): void;
sendMessage(message: string): void;
onMessage(callback: (message: string) => void): void;
disconnect(): void;
}
// Dependency Injection
class WebSocketTransport implements DataTransport {
...
}
class WebRTCTransport implements DataTransport {
...
}
// 使用 WebSocket
const webSocketTransport = new WebSocketTransport();
const webSocketService = new DataService(webSocketTransport);
webSocketService.start("ws://example.com/socket");
webSocketService.send("Hello via WebSocket!");
webSocketService.stop();
// 使用 WebRTC
const webRtcTransport = new WebRTCTransport();
const webRtcService = new DataService(webRtcTransport);
webRtcService.start("wss://example.com/webrtc");
webRtcService.send("Hello via WebRTC!");
webRtcService.stop();
// 可以隨時做服務的切換
```
### Encapsulate what varies(封裝改變)
* 將易改變之程式碼部份封裝起來,以後若需修改或擴充這些部份時,能避免不影響到其他不易改變的部份。
* 換言之,將潛在可能改變的部份隱藏在一個介面(Interface)之後,並成為一個實作(Implementation),爾後當此實作部份改變時,參考到此介面的其他程式碼部份將不需更改。


* 舉例
攻擊寫在角色的類別中,假設有不同的角色,那麼攻擊行為就必須被擴充或修改
```java
class Character {
private String name;
public Character(String name) {
this.name = name;
}
public void attack() {
System.out.println(name + " is attacking with a sword!");
}
}
```
當需要新增或修改攻擊行為時,可以直接創建新的策略類別,而不需要更改 Character 類別,這樣符合開放-封閉原則(Open-Close Principle)OCP
```java
interface AttackStrategy {
void attack(String name);
}
class SwordAttack implements AttackStrategy {
@Override
public void attack(String name) {
System.out.println(name + " attacks with a sword!");
}
}
class Character {
private String name;
private AttackStrategy attackStrategy;
public Character(String name, AttackStrategy attackStrategy) {
this.name = name;
this.attackStrategy = attackStrategy;
}
}
public class Game {
public static void main(String[] args) {
// 劍士角色,初始攻擊方式為劍擊
Character knight = new Character("Knight", new SwordAttack());
}
}
```
### Favor composition over inheritance(善用合成取代繼承)
* 程式碼重用(Reuse)
* 不要一味的使用繼承,要有IS-A的關係
* 有別於繼承,Composition可在Runtime時更有彈性地動態新增或移除功能
### Least Knowledge Principle(最小知識原則)
* 必須注意類別的數量,並且避免製造出太多類別之間的耦合關係。
* 知道子系統中的元件越少越好
* 不需要懂太多細節

### Acyclic Dependencies Principle (ADP)


難以符合OCP
### Don’t Repeat Yourself (DRY)
* NO duplicate
### Keep It Simple Stupid(KISS)
* 簡潔是軟體系統設計的重要目標,應避免引入不必要的複雜性
* 考慮是否 over design
## ch6 Design Patterns
### Strategy Pattern
關鍵字:An algorithm
#### Requirements Statement
範例:文字編輯器
按照需求去增加:a new layout is required

重構:
1. Encapsulate what varies
會改變的做封裝處理,抽出

2. Generalize common features
建樹

3. Program to an interface, not an implementation
使用樹的父親,對口interface,方便擴充跟修改

#### Recurrent Problems
需要增加新的 algorithms 的問題,就拉出來封裝
結構:

### Composite & Decorator
關鍵字:Structure and composition of an object
面對的問題是由 object 組成
#### Requirements Statement
範例:小畫家
如果要畫三角形,就有新的問題

重構:
1. Generalize common features
建樹

2. Program to an interface, not an implementation.
使用父親


#### Recurrent Problem

### Decorator Pattern
關鍵字:Responsibilities of an object without subclassing
動態地為一個物件添加行為或職責,而不需要透過繼承來實現。
#### Requirements Statement
範例:Starbuzz Coffee
cost 需要因應新服務而改變,變得不穩定

重構:
1. Encapsulate what varies

2. Generalize common features
概念是讓 condiment 也是一種食物配料

3. Program to an interface, not an implementation.

裝飾它

#### Recurrent Problem
被裝飾放左邊
右邊的裝飾品用來擴充

#### Composite vs. Decorator

### Factory Method & Abstract Factory
關鍵字:Subclass of object that is instantiated
#### Requirements Statement
範例:披薩店

新增新的披薩會有問題

重構:
1. Encapsulate what varies

2. Generalize common features

左樹:生產披薩
右樹:被生產的物件

#### Recurrent Problem
工廠生產產品,新增產品的生產步驟

#### 補充:Parallel Inheritances Hierarchies問題
產品跟生產方式可能有配對的關係

能夠讓兒子去強制配對

### Abstract Factory Pattern
關鍵字:Families of product objects
#### Requirements Statement
範例:佈景主題

重構:
1. Encapsulate what varies

2. Generalize common features

3. Program to an interface, not an implementation.
生產一堆東西,抽出成工廠,限制其必須要實作

#### Recurrent Problem

#### Abstract Factory vs. Factory Method
* Factory Method
* creates single products
* Abstract Factory
* consists of multiple factory methods
* each factory method creates a related or dependent product
### Template Method
關鍵字:Steps of an algorithm
動作是否有雷同或一樣的地方,如煮咖啡、泡茶
#### Requirements Statement
範例:煮咖啡、泡茶
1,3步驟一樣

重構:
1. Encapsulate what varies

2. Generalize common features

#### Recurrent Problem

### Adapter
關鍵字:Interface to an object
Interface 被改變了
#### Requirements Statement
範例:
New Vendor in Existing Software
範例1:Object Adapter

更改API

重構:
1. Encapsulate what varies

2. Generalize common features
兒子去綁定不同的API

範例2:Class Adapter

重構:
1. Encapsulate what varies

2. Generalize common features

#### Recurrent Problem
* Object Adapter

* Class Adapter

* Object Adapter vs. Class Adapter

### State
關鍵字:states of an object
什麼狀態做什麼事,討論狀態的變化
#### Requirements Statement
範例:口香糖機器

需要考慮到狀態的變化:投錢、退錢、售完、按下按鈕等等


重構:
1. Encapsulate what varies
將狀態抽出

2. Generalize common features
每個class負責一個狀態,該狀態應該怎麼做

3. Program to an interface, not an implementation

#### Recurrent Problem

### Visitor
關鍵字:Operations that can be applied to objects without changing their classes
可以動態加入一群物件的行為
#### Requirements Statement
範例:Compiler and AST

父親擴充了新的方法,兒子也要增加
重構:
1. Encapsulate what varies

分成一個class,但兩個不同的方法
2. Generalize common features

3. Program to an interface, not an implementation

#### Recurrent Problem

Node 如果是病人的種類:兒童、成年人、老年人
Visitor 如果是醫生的種類:骨科、耳鼻喉科、內科
醫生根據不同病人做檢查跟觀察狀態,讓醫生作為一個Visitor檢查不同種類人的身體狀態。
## ch7 Unit Testing
### 單元測試是什麼
* Michael Feature
* 小的、快的,快速定位問題所在:0.1秒內
* 不是單元測試
* 與資料庫有互動
* 進行了網路通訊
* 接觸到檔案系統
* 需要你對環境做特定的準備(如編輯設定檔案)才能夠執行
* Roy Osherove
* 一個單元測試是一段自動化的程式碼,這段程式會呼叫被測試的工作單元,之後對這個單元的單一最終結果的某些假設或期望進行驗證
* 用於確保某個工作是否正確執行
* 一個單元測試範圍,可以小到一個方法(Method),大到實現某個功能的多個類別與函數
* 測試某個工作單元,其範圍大小不限於一對一的單元測試與方法
### 何時撰寫測試案例
* 程式開發前
* For TDD (Test-Driven Development)
* 程式開發後
* For Regression Testing
* 程式變成Legacy Code時
* For Refactoring
* 已經寫好,可以運作的程式,但有一些氣味,需要被重構的
* 確保重構後,還能保持功能正常運行
* 書本

### Refactor untestable code to testable code
#### Untestable Code
* 當被測試的物件依賴於另一個無法控制(或尚未實作)的物件時,要造成無法進行單元測試
* 例如依賴於一個Web Service、系統時間、執行緒、資料庫、本地檔案等
* 此時可利用Stub概念來重構解耦 (Refactor untestable code to testable code)


* 本來需要登入後才能驗證,利用重構,新增假資料(假裝有登入)進行測試。
* 可以讓它只為了一項工作進行單元驗證
* 如果加入登入的動作,首先速度慢,再來是增加為多個工作,那就變成整合測試,而非單元測試
##### 解方
Steps:
1. Extract Interface as Seam

2. Create Stub Class

3. Program to an interface, not an implementation

4. Dependency Injection

* 結果

* 利用 ILoginManager 作為依賴注入的物件,只要 `new StubLoginManager()` 作為**假資料**就變得**可以測試**。

#### Tip (Stub vs. Mock)

* 因此,單元測試類型除了
* 驗證回傳值
* 驗證系統狀態
* 運用Mock即可增加一種測試類型
* **驗證互動**
## clean code
強調可讀性
### Meaningful Names
#### Use Intention-Revealing Names
Choosing good names takes time but saves more than it takes
命名要有意義

#### Avoid Disinformation
容易誤會、過長、太一般性的名稱
```c
int a = l;
if ( O == l )
a = O1;
else
l = 01;
```
#### Make Meaningful Distinctions
盡量使用 source and destination
```java
public static void copyChars(char a1[], char a2[]) {
for (int i = 0; i < a1.length; i++) {
a2[i] = a1[i];
}
}
```
是否是一樣的?
```java
getActiveAccount();
getActiveAccounts();
getActiveAccountInfo();
```
#### Use Pronounceable Names
好發音的
```
class DtaRcrd102 {
private Date genymdhms;
private Date modymdhms;
private final String pszqint = "102";
/* ... */
};
```
應改成
```
class Customer {
private Date generationTimestamp;
private Date modificationTimestamp;;
private final String recordId = "102";
/* ... */
};
```
#### Use Searchable Names
容易被搜尋
```
for (int j=0; j<34; j++) {
s += (t[j]*4)/5;
}
```
應改成
```
int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j=0; j < NUMBER_OF_TASKS; j++) {
int realTaskDays = taskEstimate[j] *
realDaysPerIdealDay;
int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
sum += realTaskWeeks;
}
```
#### Member Prefixes
多餘的
```
public class Part {
private String m_dsc; // The textual description
void setName(String name) {
m_dsc = name;
}
}
```
應改成
```
public class Part {
String description;
void setDescription(String description) {
this.description = description;
}
}
```
#### Don’t Be Cute
不使用俚語或是幽默性的命名
HolyHandGrenade => DeleteItems
#### Pick One Word per Concept
意思一樣,應統一用一樣的詞
比如雷同的 fetch, retrieve,and get
統一用 get
或是 DeviceManager and a Protocol-Controller
### Functions
#### Small
程式碼短小,看起來要 "**eye-full**"
#### One Level of Abstraction per Function
分層次,抽象地去理解系統
能夠用適當的命名以及function去包裝功能。
function 可以多也可以短
#### Reading Code from Top to Bottom: The Stepdown Rule
盡可能將被呼叫的擺在呼叫人的附近
#### Prefer Exceptions to Returning Error Codes
利用 try exceptions 除錯

#### Extract Try/Catch Blocks
try exceptions 的內容不要太長
#### How Do You Write Functions Like This?
程式很難一開始就乾淨
### Comments
#### Comments Do Not Make Up for Bad Code
不好的code
重構 > 補充註解
#### Explain Yourself in Code
用 function 的名稱替代多餘的註解

### Good Comments
#### Legal Comments
#### Informative Comments
不得不去解釋

#### Explanation of Intent
解釋動機、當初的決策 WHY TO DO

#### Clarification
無法修改,但概念不清楚或模糊的

#### Warning of Consequences
解釋後果

#### TODO Comments
該去寫,但還未寫
### Bad Comments
#### Mumbling
讀完註解,但還是不清楚

#### Redundant Comments
不需要去解釋,冗餘的註解

#### Mandated Comments
除非外部需要,否則內部使用不用加

#### Noise Comments

#### Don’t Use a Comment When You Can Use a Function or a Variable
如果你可以用code直接表達清楚,使用重構,就不需要註解

#### Commented-Out Code
被註解的code,可以刪除就刪除掉。
#### Nonlocal Information
註解放的跟 code 太遠了
### Formatting
長度

#### Vertical Openness Between Concepts
斷空白行

#### Vertical Density
內容的密度高

#### Vertical Distance
呼叫跳來跳去
#### Variable Declarations
Variables should be declared as close to their usage as possible.
#### Dependent Functions
互相呼叫的如果可以就放在一起
#### Conceptual Affinity
概念一樣就放在一起
#### Horizontal Openness and Density
水平的空白,方便閱讀

#### Horizontal Alignment
水平對齊
應使用下方的code,讓type跟name較接近
也方便新增跟修改

#### Breaking Indentation
scopes 應隔開
應使用下方的code

### Objects and Data Structures
#### Data/Object Anti-Symmetry
不一定都要化為 Object 或切成多型。
#### Data Abstraction
抽象化,不需要接露實作細節

#### Data Structure
如果不會再新增了,就化繁為簡,不需要另外建樹了。

## Clean Architecture
### WHAT IS DESIGN AND ARCHITECTURE?
* 定義:
- 架構:通常指系統的高層結構。
- 設計:通常指較細節的部分。
但實際上,兩者是連續的,沒有明確的分界線,從高層到細節都是一體的,彼此密不可分。
* 目標:
減少建構和維護系統所需的人力和成本。
* 設計和架構是一個連續的過程,好的架構設計可以降低開發和維護的成本,讓系統更易於管理和擴展。
* ARCHITECTURE
* software
* soft: 軟的,易修改、有彈性的。
* ware: 產品。
* 架構大於功能:容易改變與修改,比能用更重要。
* 功能:往往會需要更高的成本、壓力。
* 功能通常緊急但不重要。
* 架構通常重要但不緊急,這才是重點。
### Design principles
How to arrange the bricks into walls and room
如何將磚塊排列成牆壁和房間
### Component principles
How to arrange the rooms intobuilding
如何將房間佈置成建築物
* Componebts
* units of deployment
* COMPONENT COHESION 內聚力(裡面)
* REP: 元件必須有明確的發布流程和版本管理,才能確保被有效且可靠地重用。
* CCP: 把一起改動的類別放在同一個元件裡,把不同原因改動的類別分開,避免無謂的影響擴散(SRP的延伸)。
* CRP: 把經常一起使用的類別放在同一個元件中,避免把不相關的類別放在一起,減少不必要的依賴關係,內聚力就高。
* COMPONENT COUPLING (外面)
* 不要循環依賴
* DIP 依賴反轉原則: 元件之間應透過抽象進行依賴,而不是直接依賴具體實現,從而降低耦合性並提高系統的可擴展性和穩定性。
### The Clean Architecture
* Architecture 就是畫線:畫出邊界跟區塊

* 精神:把重要跟不重要的做切割
* Clean Architecture:獨立且易於維護的軟體系統,將不同部分進行明確的分層,讓系統具備靈活性和可測試性。

* 獨立於框架
* 可測試姓
* 獨立於UI
* 獨立於資料庫
* 獨立於外部機制
* 讓其完全解耦,達到靈活性、可測試性和可維護性。
* 外到內進行依賴
## 演講課 - Domain-Driven Design
### Domain-Driven的概念
* 對某個區域有控制跟興趣
* 理解需求,找到問題,提出策略
* Domain-Driven rather then Data-Driven
我認為DD更注重在了解業務的背景與需求,所以提出的方法跟策略才趨近於實際的情況。這也比起以往工程師著重在程式或是資料層面上更為彈性,這些技術應該是服務於業務的工具,而非終點。
DD我覺得就像是建構了一種語言,讓Code更容易被理解跟詮釋,不管是透過圖像化還是流程圖,這讓工程師與開發團隊之間更好溝通,更甚至在缺乏技術的客戶之間,不會因為術語不統一而帶來誤解,是提升溝通效率的好方式。
### Reverse design
- Data-Driven -> Domain-Driven
- 系統被建構於資料庫的結構上
- Program -> System
- 業務複雜邏輯被分散在不同程序、不純粹、耦合度高
- Functional -> Scenario
- 業務與技術的翻譯問題
改變聚焦的問題點:適度依賴數據,但不要被其框架束縛,這種方式不僅僅是改進技術,而是整個想法的轉變。重點應在於需求與業務本身,不應該去迎合資料而做修改。
Program是分散且缺乏交流:過去的問題常常過於片面,每個模組的邏輯只看資料、功能或程序,但並不應該侷限在如何實現,而讓我們針對實際需求以及全局的角度去做設計,才能確保系統邏輯的完整性,並且有彈性地去應對業務的變化。
透過 Scenario Mapping 讓人與人之間有了共通的語言:這是一種橋梁,能幫助技術團隊與業務團隊打破溝通障礙。
### Object in Domain
- Domain 由 object 組成跟流動
#### Object
- Identity: 可以被 identity(唯一的)
- Operations: 可以被操作(被打開、被使用)
- Attributes, State: 擁有屬性跟狀態
#### Class
- 就是一個集合
- Responsibility: 單一職責
- Operations: 可以被操作
這裡提到的概念,我認為是 OOP 程式設計與 DDD 之間的配合。OOP 提供了物件、類別這些實作方式,而 DDD 在這些基礎上去賦予某種目標或使命。透過 Domain 的語義化與模型化,我會知道該如何設計以及設計哪些類別來符合實際的需求,讓軟體系統是符合現實的邏輯去進行與設計。
### Domain Storytelling:以實際案例演示情境
- Building Blocks: 分類成員的屬性
- Good language styles: 圖像化、便條紙、情境圖、流動圖
- Something about Principles: 沒有條件、who, what, whom
### Domain-Driven Principle
- Collaboration
- Ubiquitous Language
- Domain modeling -> Domain Storytelling
### Domain-Driven Design
- Strategic Design 戰略設計
- Problem Space
- Domain, Subdomain: 從流程,將領域以**Logic**分成幾個 Domain
<img src="https://hackmd.io/_uploads/rkOkv5Cm1l.png" width="300">
- Bounded context: 在程序中的 **value stream**
- Context Mapping: Mapping value stream
- Tactical Design 戰術設計
- solution space
- **組織**細節
- 依照商業流程做**架構分層**,而非功能
- 辨識價值流中的**價值物件**,而非資料
- Entities: identity(唯一識別), state(生命週期,期間發生改變), operation(角色職責) -> 銀行發行
- Value Objects: no identity(固定值), attributes(不可變), operation(操作方法) -> 信用卡
- Aggregate
- Aggregate Root(群組基礎)
- Entity base(識別群組)
- Persistence base(一致性持久化)
- operation base(提供統一職責與操作)
- Lift base(狀態改變生命週期)
- Boundary
- 群組界線
### Services
- Application(商業流)
- domain(商業邏輯)
- stateless
- domain-specific task
- out of place as a method
透過不同的演示方法,包括圖像化、Domain化、分層、價值流的概念等等,將複雜的業務邏輯轉為情境或是故事作呈現。我覺得對於設計一個「系統」有很大的幫助,讓我們能直觀地將這些需求轉為程式碼或是一個個模組,引導我們去設計相對完整的架構和流程,就像課堂常常提到的 Design Principles。
當我們按照DDD的這些流程去設計系統時,每個類別都能對應到一個職責或需求,自然就能符合SRP;當我們按照需求去設計程式邏輯時,可以因應變動去做調整跟擴充,自然就能符合OCP。所以我認為DDD也有助於我們去設計一個「好的系統」,除了能符合業務需求外,還能有好維護、好擴充、有條理的特點。
## 演講課 - TDD 與重構工作坊
### 工作
FizzBuzz遊戲
把工作做對、做好
還要做「對的工作」
努力在對的方向
### TDD
每件事都是對的事
每個功能都是正確
每個優化都不會「錯」
一次只做一件事
Test first:先寫測試

寫測試 寫程式 重構
符合 SOLID 原則
有效的測試
修改設計的需求 再去修改設計
專注於需求的修改
透過寫好的測試 保護重構後程式的完整性
先列測項 跟客戶確認好需求
而不是撰寫時再思考需求。
TDD每一步的大小
釐清需求 用測試來記錄下 來告訴程式的正確與否
### 心得
在該堂課中,我們進行了 FizzBuzz 遊戲 的實作,這是一個經典且簡單的練習,但課堂中最大的收穫不只是完成遊戲本身,而是模擬了面對客戶需求變更時 的情境,讓我學習到如何在有限的時間內保持程式的彈性並快速回應變化。
一開始,我們按照最基本的需求實作 FizzBuzz,例如輸出 1 到 100 的數字,其中 3 的倍數輸出 "Fizz",5 的倍數輸出 "Buzz",同時是 3 和 5 的倍數時輸出 "FizzBuzz"。然而在後續的過程中,老師(或模擬的客戶)陸續提出了新的需求,例如:
* 增加其他倍數的條件輸出特定文字。
這樣的變更讓我體會到,實務中不可能所有需求一開始就明確,客戶往往會根據他們看到的結果提出新的想法,這也導致說一個系統的設計,必須寫得有"彈性"並且易於"理解"與修改,才能在有限的時間內,去滿足客戶提出的需求。
#### 關於遊戲實作的想法
##### 遊戲實作的問題
```java
public class FizzBuzz {
public String convert(int number) {
List<String> result = new ArrayList<String>();
for (int i = 1; i <= number; i++) {
if (i % 3 == 0 && i % 5 == 0) {
result.add("FizzBuzz");
} else if (i % 3 == 0 && i % 7 == 0) {
result.add("FizzDizz");
} else if (i % 5 == 0 && i % 7 == 0) {
result.add("BuzzDizz");
} else if (i % 3 == 0 && i % 5 == 0 && i % 7 == 0) {
result.add("FizzBuzzDizz");
} else if (i % 3 == 0) {
result.add("Fizz");
} else if (i % 5 == 0) {
result.add("Buzz");
} else if (i % 7 == 0) {
result.add("Dizz");
} else {
result.add(""+i);
}
}
return String.join(" ", result);
}
}
```
原本的程式,是按照直覺以及需求的順序,一一將規則寫入其中,這樣的程式雖然可以正確地運行,但存在一些問題包括:
1. 難以修改跟擴充
每當新增或修改一個規則時,都必須重新編寫 if-else 邏輯。
2. 重複邏輯
程式碼變得冗長且不易理解。`i % 3 == 0、i % 5 == 0、i % 7 == 0`,不但具備重複的程式邏輯,還使用了魔術數字。
3. 違反SRP 和 OCP 原則
* SRP:convert 負責了兩件事情,包括遍歷數字與判斷輸出的內容。
* OCP:面對新規則(新需求)時,需要修改現有邏輯。
##### 實作如何改善
```java
public class FizzBuzz {
private final List<Rule> rules;
public FizzBuzz() {
this.rules = createDefaultRules();
}
// 輸出 1 ~ number 的 FizzBuzz 結果
public String convert(int number) {
List<String> result = new ArrayList<>();
for (int i = 1; i <= number; i++) {
result.add(applyRules(i));
}
return makeReturnValue(result);
}
// 將FizzBuzz的規則放入rules中
private List<Rule> createDefaultRules() {
List<Rule> rules = new ArrayList<>();
rules.add(new Rule(3, "Fizz"));
rules.add(new Rule(5, "Buzz"));
rules.add(new Rule(7, "Dizz"));
return rules;
}
// 將規則套用到number上
private String applyRules(int number) {
StringBuilder result = new StringBuilder();
for (Rule rule : rules) {
if (rule.appliesTo(number)) {
result.append(rule.getValue());
}
}
return handleEmptyValue(result.toString(), number);
}
// 處理空值
private String handleEmptyValue(String value, int number) {
return value.isEmpty() ? String.valueOf(number) : value;
}
// 將結果串接成字串
private String makeReturnValue(List<String> result) {
return String.join(" ", result);
}
// FizzBuzz 的判斷規則
private static class Rule {
private final int divisor;
private final String value;
public Rule(int divisor, String value) {
this.divisor = divisor;
this.value = value;
}
public boolean appliesTo(int number) {
return number % divisor == 0;
}
public String getValue() {
return value;
}
}
}
```
改善後的地方:
1. SRP:讓每個方法負責一件工作。
* `Rule` 負責遊戲規則的制定
* `convert` 負責輸出 1 ~ number 的 FizzBuzz 結果
2. OCP:易於修改與擴增
* 有新規則(需求)時,只要添加到 `createDefaultRules` 方法中即可:
```java
rules.add(new Rule(11, "Jazz"));
rules.add(new Rule(13, "Pop"));
```
這樣的設計符合 SOLID 原則,還讓程式碼能夠應對新的需求跟變化,更符合實際工作的情況。
#### 關於單元測試的想法
```java
class FizzBuzzTest {
@Test
void test21() {
FizzBuzz fizzBuzz = new FizzBuzz();
String actual = fizzBuzz.convert(21);
Assertions.assertEquals("1 2 Fizz 4 Buzz Fizz Dizz 8 Fizz Buzz 11 Fizz 13 Dizz FizzBuzz 16 17 Fizz 19 Buzz FizzDizz",
actual);
}
}
```
原本的單元測試,需要人工將規則一一列出,所以我萌發了一個想法:
**我能否重構單元測試呢?**
使用原本通過單元測試但尚未重構的程式碼,作為單元測試,用來測試重構後的程式:
```java
class FizzBuzzTest {
@Test
void test500() {
// 使用舊版 FizzBuzz 類別
FizzBuzz oldFizzBuzz = new FizzBuzz();
String oldResult = oldFizzBuzz.convert(500);
// 使用重構後的 FizzBuzz 類別
FizzBuzz refactoredFizzBuzz = new FizzBuzz(); // 假設這是重構後的程式
String refactoredResult = refactoredFizzBuzz.convert(500);
// 驗證兩個結果是否相同
Assertions.assertEquals(oldResult, refactoredResult, "重構後的結果與舊版結果不符");
}
}
```
這樣的測試確保了重構後的程式不會改變原本的功能,並且還能測試更大的數字(比如500, 1000)來避免潛在的邏輯錯誤或效能問題。
## 演講課 - 軟體架構設計
https://gelis-dotnet.blogspot.com/
### 工程師成長階段

* 基礎(學習):模仿→懂學習
* DRY(工作):能應付工作需求→懂程式
* 分層Design Pattern(解決技術債):問題解決,自我成長→懂Pattern(好程式)
* OOD(設計):抽象化→懂設計
* OOA(分析):溝通協作→懂協作
* 專案分享:傳承(透過教學了解細節)→懂細節
* 系統整合:流程改善(好方法)→有好的開發流程
* 獨立作業:顧問(獨立)→有自己的一套方法
* 跨團隊:專家
* 教練/工匠:勘誤
工程師的成長是一個漸進的過程,從基礎的學習到最終成為能夠帶領他人前進的技術領袖,每個階段都蘊含著深刻的進步與挑戰。一開始,工程師多以模仿和學習為主,理解基礎的工具與技術,逐步具備完成工作需求的能力。在這個過程中,他們開始領會程式的運作邏輯,並以「不重複自己」(DRY)為原則,提升效率和質量。
隨著經驗的累積,他們會遇到更多技術債的問題,進而學習如何運用設計模式(Design Pattern)來解決這些挑戰。此時,他們不僅寫出更好的程式,還在解決問題中實現自我成長。再往前,他們進一步理解抽象化的概念,進入物件導向設計(OOD)的領域,能夠創建出可擴展、可維護的系統設計。
成長的下一步是掌握面向物件分析(OOA),這需要更多的溝通與協作能力。他們開始關注如何與團隊成員共同設計解決方案,並透過專案分享和教學來傳承知識,這不僅幫助他人,也讓他們自己更深入地了解細節。在更高的層次,他們致力於系統整合,優化開發流程,並逐漸形成自己的一套方法論,成為能獨立承擔責任的顧問。
當工程師跨越團隊,成為專家時,他們的角色更多的是指導與協調,分享經驗並推動技術進步。而作為技術教練或工匠,他們不僅專注於技術本身,還致力於培養他人,幫助團隊避免錯誤,將精益求精的精神融入整個工程文化。
這一過程顯示,工程師的成長不僅是技術的提升,更是對溝通、協作、分享和領導的全方位鍛煉。真正成熟的工程師,不僅是解決問題的高手,更是知識的傳承者與團隊的推動者。
### 什麼是軟體架構設計
* 軟體架構的目的:不讓專案變成大泥球(亂)
* 沒有軟體架構的問題:
* 沒有版控
* 原始碼
* 沒有文件
* 沒有需求訪談
* 沒有規範
* 沒有時程規劃
* 沒有測試(環境)
* 不求好只求有
* 軟體架構是什麼:怎麼解決
* 簡化軟體開發作業
* 統一規範(Coding style)
* 組件重用性
* 一致的開發技術與架構,減少管理跟技術債
* 軟體架構**設計**
* 設計什麼?:設計一個能賺錢的系統
* 讓系統變得可控、可抽換性、可靠、可維護(讓成本變低)
* 成本:包含維護成本、學習門檻等
* 目標:最小化建置和維護「需求系統」的人力資源。
* 傳統階層式的問題
* 資料導向設計
* 強調底層實現細節
* 什麼是**設計**:低層次
* 聚焦於具體技術與模組實現的細節,如 API等。
* 什麼是**架構**:高層次
* 關注系統的整體結構與組織方式,如模組間的關係、業務邏輯的劃分等。
* 問題:雜亂比整潔來得快
* 設計缺乏規範會讓代碼迅速變得雜亂,隨之帶來開發與維護成本的無限增長。
* 什麼是好的架構?
* 問題:實務上難以完全想清楚需求,導致系統需要不斷修改
* 畢竟不能一次到位,那就做好 Core Domain 下應該要做的事情就好
軟體架構的核心目的在於,讓一個專案變得有序並且好維護,能用更低的成本達到客戶的需求。但是呢,往往需求是會隨時間變化,難以一步到位,這就導致我們開發時,需要一個開發的準則或原則,讓工程師遵循這個框架進行設計,這就是軟體設計架構的核心概念。
這些準則不僅僅是技術層面的規範,而是提供了一套應對變化與複雜性的策略。這跟我們軟體設計所學的Design pattern與Design principles有直接的關係,比如採用模組化設計可以讓系統的不同部分彼此獨立,減少變更對全局的影響;統一的代碼風格與規範則讓團隊之間的協作更加順暢。同時,透過像 SOLID 原則這樣的設計原則,系統能更具彈性和可擴展性,為未來的修改留有足夠的空間。
但實務上,還是要考量到開發與成本的平衡,過於複雜或超前的設計會增加技術負擔,而過於簡單的設計又可能在需求變化時無法適應。因此,好的軟體架構需要專注於核心領域(Core Domain),解決當下最重要的問題,同時具備足夠的彈性來應對未來的挑戰。
### 軟體架構設計:API 設計準則

* API 設計準則是什麼?
API 設計準則是指在設計和實作應用程式介面(API)時,遵循的一系列原則。
* 一致性:介面風格和命名規則統一。
* 易用性:使開發者能快速理解和使用。
* 可擴展性:適應未來需求的增長。
* 安全性:防止數據洩露和攻擊。
* 性能:確保響應快速,資源使用效率高。
* API 設計的目的
* 促進系統整合:使不同系統、服務和應用程式之間能無縫通信。
* 提高開發效率:為開發者提供清晰的介面,減少溝通成本和開發時間。
* 增強可維護性:通過一致的設計,降低後續修改和維護的難度。
* 支持業務需求:滿足當前需求並為未來的業務擴展留有空間。
* 提升用戶體驗:對於開放型 API,為開發者提供良好的使用體驗,增強產品競爭力。
* 如何做到 API 準則
* API First 思維:以產品化思維設計 API,從企業的商業能力出發,而非僅僅滿足單一客戶的需求。
* 領域驅動設計(DDD):透過事件風暴(Event Storming)等方法,確定系統的核心領域(Core Domain)和子領域,並以此驅動 API 的開發。
* 整潔架構(Clean Architecture):在程式碼實作中,遵循整潔架構的原則,確保系統的可維護性和可測試性。
* 模組化設計:將系統劃分為不同的上下文(Contexts),如購票、管理票卷、簡訊發送等,明確各自的系統邊界。
* 重視應用層服務(Application Services):在應用層實作中,定義清晰的服務介面,處理業務邏輯,並與領域層進行互動。
* 使用設計模式:在實作中,運用適當的設計模式,如工廠模式(Factory Pattern)來建立物件,確保程式碼的彈性和可維護性。
* 資料傳輸物件(DTO):在應用層與外部系統交互時,使用 DTO 來傳遞資料,避免直接暴露領域物件。
* 儲存庫模式(Repository Pattern):透過儲存庫介面(Repository Interface)來抽象資料存取層,促進程式碼的可測試性和維護性。
* 錯誤處理與驗證:在 API 設計中,考慮適當的錯誤處理和資料驗證機制,確保系統的穩定性和安全性。
* 文件化:為 API 提供詳細的文件,方便開發者理解和使用,提升開發效率和協作性。
* ADDR
* API Design-First 的重要性:
* 強調設計 API 的過程優先於其他開發工作。
* 將用戶與開發者的需求置於首位,降低技術使用門檻。
* 避免傳統開發中因重新設計而產生的冗長週期和失敗風險。
* 從 RESTful 到 API Design-First 優先設計
* 以 Resource-Based 為基礎,將網路中的一切視為「資源」。
* 強調資源與數據的關聯。
* API Design-First 的理念:
* 資源 ≠ 資料模型:
* API 應該專注於交付企業的數位能力,而非後端的資料結構。
* 適合跨企業資料交換,避免特定平台綁定,確保標準化與開放性。
* 資源 ≠ 物件或領域模型(Domain Model):
* API 應避免與後端程式碼直接耦合,提升維護性和穩定性。
* 強調模組化設計、封裝、低耦合和高內聚等基礎軟體設計原則。
* API Design-First 與傳統軟體開發方法的差異
* 設計順序
* API Design-First:
在撰寫程式碼和設計使用者介面(UI)之前,先設計 API。
* 傳統開發方法:
先完成核心應用程式邏輯和 UI,最後設計 API。
* 協作模式:
* API Design-First:
前端、後端及利害關係人早期參與,強調跨部門協作。
* 傳統開發方法:
開發流程較孤立,系統整合常在後期進行,存在更多風險。
* 敏捷開發的影響:
* API Design-First 實踐了敏捷開發的理念,快速回應需求變更。
ADDR(API Design-First Design & Runtime)是一種從領域驅動設計(DDD)延伸而來的實踐方法,它強調在開發 API 時以設計為優先,並將設計與執行環境緊密結合。這種方法的核心理念在於,API 不僅是一種技術實現工具,更是一種數位能力的表達方式,因此設計的重點應該放在如何清晰地傳達業務能力,而非僅僅參照後端的資料。
更強調跨團隊的協作,將前端、後端和業務人員緊密聯繫在一起,屬於一種團隊上理念與溝通的橋樑,讓開發過程更加敏捷和協作化。