程式設計有時候很像玩樂高。
大家通常看到的是一艘拼好的「樂高海盜船」,
而不是每一個小零件是怎麼拼起來的。
可是要蓋出穩固的船,還是得知道一些「放零件的規則」。
時常需要兼顧兩邊不同的要求。
今天繼續來學習 CCP 共同封閉原則吧:
那些會因著相同理由、在相同時間發生變化的類別搜集到相同的元件之中。
有點不了解 CCP 跟 REP,都是「放在一起」,卻有不同的理由。
原則 | 重點 | 範例 | 浮點數修改的例子 |
---|---|---|---|
REP共同重複使用原則 | 同一份定義放一起,避免重複 | PI 常數、單位換算表 | PI = 3.14159 :圓面積、圓周長都用到,所以要集中定義,不要每個地方各自寫一份 |
CCP共同封閉原則 | 會常常一起修改的功能放一起 | 加法 + 乘法演算法 | 當系統要從「只能算整數」→「也能算小數(浮點數)」時,加法和乘法的程式通常要同時修改,所以要放在同一個模組裡 |
現在傳遞訊息已經都不是單向的,所以文章留言功能特別重要。這就是海盜船容易被看見的地方。
但同時也要考慮輸入,會產生資安上的問題。
後端的程式可以進行保護,但也可以使用簡單的做法繞過,
今天新增了留言功能:
// math.js
function add(a, b) {
return parseInt(a) + parseInt(b);
}
function multiply(a, b) {
return parseInt(a) * parseInt(b);
}
console.log(add(2, 3)); // 5
console.log(multiply(2, 3)); // 6
console.log(add(2.5, 3.5)); // ❌ 只能算整數,結果錯誤
<template>
<section class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="bg-white rounded-lg shadow-sm border p-6">
<h2 class="text-2xl font-bold text-gray-900 mb-4">留言討論 (使用 GitHub Issues)</h2>
<div ref="container" id="utterances-container"></div>
</div>
</section>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
const props = defineProps<{
repo?: string
issueTerm?: string
theme?: string
articleTitle?: string
currentPath?: string
}>()
const container = ref<HTMLElement | null>(null)
const repo = props.repo ?? 'xxx'
const issueTerm = props.issueTerm ?? 'pathname'
const theme = props.theme ?? 'dark-blue'
let scriptEl: HTMLScriptElement | null = null
let previousTitle: string | undefined
function createUtterances() {
if (!container.value) return
// remove existing children
container.value.innerHTML = ''
// If issue-term is `title`, utterances reads document.title; only override when explicitly requested
if (issueTerm === 'title' && props.articleTitle) {
previousTitle = document.title
document.title = props.articleTitle
}
scriptEl = document.createElement('script')
scriptEl.setAttribute('src', 'https://utteranc.es/client.js')
scriptEl.setAttribute('repo', repo)
scriptEl.setAttribute('issue-term', issueTerm)
scriptEl.setAttribute('theme', theme)
scriptEl.setAttribute('crossorigin', 'anonymous')
scriptEl.setAttribute('async', 'true')
container.value.appendChild(scriptEl)
}
function removeUtterances() {
if (container.value) {
container.value.innerHTML = ''
}
if (previousTitle !== undefined) {
document.title = previousTitle
previousTitle = undefined
}
scriptEl = null
}
onMounted(() => {
createUtterances()
})
onBeforeUnmount(() => {
removeUtterances()
})
// re-create when title changes (useful for SPA navigation)
// re-create when title or currentPath changes (SPA navigation)
watch(
() => [props.articleTitle, props.currentPath],
() => {
removeUtterances()
createUtterances()
},
)
</script>
<style scoped>
</style>