软件耦合的分类及解决方法
文章目錄
- 什么是耦合、解耦
- 高內聚與低耦合
- 耦合
- 內容耦合
- 公共耦合
- 外部耦合
- 控制耦合
- 標(印)記耦合
- 數據耦合
- 非直接耦合
- 內聚
- 什么是解耦
- 公共耦合 c語言例子,小小的公共庫,大大的耦合,你痛過嗎?
- 用C語言實例描述程序中的內聚和耦合
- **一、原理篇**
- **二、示例篇**
- 用C語言編寫低耦合程序
- 耦合的定義
- 低耦合的優點
- 實現例子
- 普通的實現方式
- 低耦合的實現方式
- C語言使用注冊回調方式實現模塊間交互(解耦合)
- 一、實現說明
- 二、代碼
- 1、模塊A代碼
- 1)A.h
- 2)A.C
- 2、模塊B代碼
- 1)B.h
- 2)B.C
- 3、模塊C代碼
- 1)C.h
- 2)C.C
- 4、模塊D代碼
- 1)D.h
- 2)D.C
- 5、main函數代碼
- 三、輸出
什么是耦合、解耦
已剪輯自: https://www.cnblogs.com/zhjblogs/p/14221560.html
一、耦合(可以稱為關聯性)
1、耦合是指兩個或兩個以上的體系或兩種運動形式間通過相互作用而彼此影響以至聯合起來的現象。
3、分類:有軟硬件之間的耦合,還有軟件各模塊之間的耦合。耦合性是程序結構中各個模塊之間相互關聯的度量。它取決于各個模塊之間的接口的復雜程度、調用模塊的方式以及哪些信息通過接口。
二、解耦
1、解耦,字面意思就是解除耦合關系。
之前組內同學問我耦合的關系,我沒給對方講清楚,今天借這個機會來深入講講模塊之間的耦合關系這個事情。
本文將用圖文詳細講解七種耦合的不同之處。
高內聚與低耦合
高內聚與低耦合是每個軟件開發者追求的目標,那么內聚和耦合分別是什么意思呢?
內聚是從功能角度來度量模塊內的聯系,一個好的內聚模塊應當恰好做一件事。它描述的是模塊內的功能聯系。
耦合是軟件結構中各模塊之間相互連接的一種度量,耦合強弱取決于模塊間接口的復雜程度、進入或訪問一個模塊的點以及通過接口的數據。
耦合
不同模塊之間的關系就是耦合,根據耦合程度可以分為7種,耦合度依次變低。
- 內容耦合
- 公共耦合
- 外部耦合
- 控制耦合
- 標記耦合
- 數據耦合
- 非直接耦合
下面我們來說說每種耦合是什么,開始之前先來說下要實現的功能。m1和m2是兩個獨立的模塊,其中m2種會顯示m1的輸入,m1會顯示m2的輸入。
很顯然,m1和m2兩個模塊之間會有一些聯系(耦合),你也可以想想如何實現這個功能,下面用7種不同的方式來實現這個功能。
注:項目的代碼我放到了github,項目的demo,可以在這里查看。
內容耦合
如果發生下列情形,兩個模塊之間就發生了內容耦合。
一個模塊直接訪問另一個模塊的內部數據;
一個模塊不通過正常入口轉到另一模塊內部;
兩個模塊有一部分程序代碼重疊(只可能出現在匯編語言中);
一個模塊有多個入口。
在內容耦合的情形,所訪問模塊的任何變更,或者用不同的編譯器對它再編譯,
都會造成程序出錯。好在大多數高級程序設計語言已經設計成不允許出現內容
耦合。它一般出現在匯編語言程序中。這種耦合是模塊獨立性最弱的耦合。
內容耦合是最緊的耦合程度,一個模塊直接訪問另一模塊的內容,則稱這兩個模塊為內容耦合。
為了實現功能,我們將m1的輸入放到m2.m1input上,將m2的輸入放到m1.m2input上。
// m1.js root.m2.m1input = this.value; m2.update();// m2.js root.m1.m2input = this.value; m1.update();PS:不知道誰會這么寫代碼,除了我為了做演示之外。。。
查看完整代碼和demo。
公共耦合
若一組模塊都訪問同一個公共數據環境,則它們之間的耦合就稱為公共耦合。公共的數據環境可以是全局數據結構、共享的通信區、內存的公共覆蓋區等。 這種耦合會引起下列問題:
所有公共耦合模塊都與某一個公共數據環境內部各項的物理安排有關,若修改某個數據的大小,將會影響到所有的模塊。
無法控制各個模塊對公共數據的存取,嚴重影響軟件模塊的可靠性和適應性。
公共數據名的使用,明顯降低了程序的可讀性。
公共耦合的復雜程度隨耦合模塊的個數增加而顯著增加。若只是兩個模塊之間有公共數據環境,則公共耦合有兩種情況。
若一個模塊只是往公共數據環境里傳送數據,而另一個模塊只是從公共數據環境中取數據,則這種公共耦合叫做松散公共耦合。若兩個模塊都從公共數據環境中取數據,又都向公共數據環境里送數據,則這種公共耦合叫做緊密公共耦合。只有在模塊之間共享的數據很多,且通過參數表傳遞不方便時,才使用公共耦合。否則,還是使用模塊獨立性比較高的數據耦合好些。
一組模塊都訪問同一個全局數據結構,則稱之為公共耦合。
在這種case中,m1和m2將自己的輸入放到全局的data上。
// m1.js root.data.m1input = this.value; m2.update();// m2.js root.data.m2input = this.value; m1.update();查看完整代碼和demo。
外部耦合
一組模塊都訪問同一全局簡單變量而不是同一全局數據結構,而且不是通過參數表傳遞該全局變量的信息,則稱之為外部耦合。例如C語言程序中各個模塊都訪問被說明為extern類型的外部變量。外部耦合引起的問題類似于公共耦合,區別在于在外部耦合中不存在依賴于一個數據結構內部各項的物理安排。
一組模塊都訪問同一全局簡單變量,而且不通過參數表傳遞該全局變量的信息,則稱之為外部耦合。外部耦合和公共耦合很像,區別就是一個是簡單變量,一個是復雜數據結構。
在這種case中,m1和m2都將自己的輸入放到全局上。
// m1.js root.m1input = this.value; m2.update();// m2.js root.m2input = this.value; m1.update();查看完整代碼和demo。
控制耦合
如果一個模塊通過傳送開關、標志、名字等控制信息,明顯地控制選擇另一模塊的功能,就是控制耦合。這種耦合的實質是在單一接口上選擇多功能模塊中的某項功能。因此,對所控制模塊的任何修改,都會影響控制模塊。另外,控制耦合也意味著控制模塊必須知道所控制模塊內部的一些邏輯關系,這些都會降低模塊的獨立性。
模塊之間傳遞的不是數據信息,而是控制信息例如標志、開關量等,一個模塊控制了另一個模塊的功能。
從控制耦合開始,模塊的數據就放在自己內部了,不同模塊之間通過接口互相調用。
在這個case中,得增加一個需求,就是當m1的輸入為空時,隱藏m2的顯示信息。
// m1.js root.m1input = this.value; m2.update();m2.toggle(!!this.value); // 傳遞flag上面的代碼中m1直接控制了m2的顯示和隱藏。
查看完整代碼和demo。
標(印)記耦合
如果一組模塊通過參數表傳遞記錄信息,就是標記耦合。事實上,這組模塊共享了這個記錄,它是某一數據結構的子結構,而不是簡單變量。這要求這些模塊都必須清楚該記錄的結構,并按結構要求對此記錄進行操作。在設計中應盡量避免這種耦合,它使在數據結構上的操作復雜化了。如果采取“信息隱蔽”的方法,把在數據結構上的操作全部集中。
調用模塊和被調用模塊之間傳遞數據結構而不是簡單數據,同時也稱作特征耦合。
在這個case中,m1傳給m2的是一個對象。
// m1.js me.m1input = this.value; m2.update(me); // 傳遞引用// m2.js me.m2input = this.value; m1.update(me);查看完整代碼和demo。
數據耦合
如果一個模塊訪問另一個模塊時,彼此之間是通過數據參數(不是控制參數、公共數據結構或外部變量)來交換輸入、輸出信息的,則稱這種耦合為數據耦合。由于限制了只通過參數表傳遞數據,按數據耦合開發的程序界面簡單、安全可靠。因此,數據耦合是松散的耦合,模塊之間的獨立性比較強。在軟件程序結構中至少必須有這類耦合。
調用模塊和被調用模塊之間只傳遞簡單的數據項參數。相當于高級語言中的值傳遞。
在這個case中,m1傳給m2的是一個簡單數據結構。
// m1.js me.m1input = this.value; m2.update(me.m1input); // 傳遞值// m2.js me.m2input = this.value; m1.update(me.m2input);查看完整代碼和demo。
非直接耦合
兩個模塊之間沒有直接關系,它們之間的聯系完全是通過主模塊的控制和調用來實現的。耦合度最弱,模塊獨立性最強。
子模塊無需知道對方的存在,子模塊之間的聯系,全部變成子模塊和主模塊之間的聯系。
在這個case種,增加一個index.js作為主模塊。
// index.js var m1 = root.m1; var m2 = root.m2;m1.init(function (str) {m2.update(str); });m2.init(function (str) {m1.update(str); });// m1.js me.m1input = this.value; inputcb(me.m1input); // inputcb是回調函數// m2.js me.m2input = this.value; inputcb(me.m2input);查看完整代碼和demo。
內聚
其實關于內聚也分為很多種,如下所示,如果你感興趣可以自己研究研究,我們下次再來分享內聚的問題。
- 偶然內聚
- 邏輯內聚
- 時間內聚
- 通信內聚
- 順序內聚
- 功能內聚
原文網址:http://yanhaijing.com/program/2016/09/01/about-coupling/
什么是解耦
解耦:假設生產者和消費者分別是兩個類。如果讓生產者直接調用消費者的某個方法,那么生產者對于消費者就會產生依賴(也就是耦合)。將來如果消費者的代碼發生變化,可能會影響到生產者。而如果兩者都依賴于某個緩沖區,兩者之間不直接依賴,耦合也就相應降低了。生產者直接調用消費者的某個方法,還有另一個弊端。由于函數調用是同步的(或者叫阻塞的),在消費者的方法沒有返回之前,生產者只好一直等在那邊。萬一消費者處理數據很慢,生產者就會白白糟蹋大好時光。緩沖區還有另一個好處。如果制造數據的速度時快時慢,緩沖區的好處就體現出來了。當數據制造快的時候,消費者來不及處理,未處理的數據可以暫時存在緩沖區中。等生產者的制造速度慢下來,消費者再慢慢處理掉。
因為太抽象,看過網上的說明之后,通過我的理解,我舉了個例子:吃包子。
假如你非常喜歡吃包子(吃起來根本停不下來),今天,你媽媽(生產者)在蒸包子,廚房有張桌子(緩沖區),你媽媽將蒸熟的包子盛在盤子(消息)里,然后放到桌子上,你正在看巴西奧運會,看到蒸熟的包子放在廚房桌子上的盤子里,你就把盤子取走,一邊吃包子一邊看奧運。在這個過程中,你和你媽媽使用同一個桌子放置盤子和取走盤子,這里桌子就是一個共享對象。生產者添加食物,消費者取走食物。桌子的好處是,你媽媽不用直接把盤子給你,只是負責把包子裝在盤子里放到桌子上,如果桌子滿了,就不再放了,等待。而且生產者還有其他事情要做,消費者吃包子比較慢,生產者不能一直等消費者吃完包子把盤子放回去再去生產,因為吃包子的人有很多,如果這期間你好朋友來了,和你一起吃包子,生產者不用關注是哪個消費者去桌子上拿盤子,而消費者只去關注桌子上有沒有放盤子,如果有,就端過來吃盤子中的包子,沒有的話就等待。
原文:https://www.jianshu.com/p/5543b2eee223
公共耦合 c語言例子,小小的公共庫,大大的耦合,你痛過嗎?
已剪輯自: https://blog.csdn.net/weixin_39568781/article/details/117160353
什么是耦合?
耦合,是架構中,本來不相干的代碼、模塊、服務、系統因為某些原因聯系在一起,各自獨立性差,影響則相互影響,變動則相互變動的一種架構狀態。
感官上,怎么發現系統中的耦合?
作為技術人,每每在心中罵上下游,罵兄弟部門,“這個東西跟我有什么關系?為什么需要我來配合做這個事情?”。明明不應該聯動,卻要被動受影響,就可能有潛在的耦合。
因為公共庫,導致相互受影響,就是一個耦合的典型案例。
場景還原
一個看似“公共”的業務庫(.so.jar .dll.php),很多業務系統都依賴于這個公共庫,這個庫使得這些系統都耦合在了一起。
注:這里的公共庫不是指像“字符串操作”這樣的不變化的工具庫,更多是指通用業務的公共庫。
耦合如何導致相互影響?
業務1,業務2,業務3都依賴于某一個biz.jar,業務1因為某個需求需要升級biz.jar。上線前,業務1的QA進行了大量的測試,確保無誤后,代碼發布,發布完線上驗證無誤后,上線完成,閃人。
突然,bug群里有人反饋,業務2的系統掛了,業務3的系統也掛了,一下炸開了鍋:
業務2的大boss首先發飆:“技術都干啥了,怎么系統掛了”
業務2的rd一臉無辜:“業務1上線了,所以我們掛了”
額,然而,這個理由,好像在大boss那解釋不通…
業務2的大boss:“業務1上線?業務1上線前測試了么”
業務1的qa自信滿滿:“測試了呀,上線前上線后都驗證了,沒問題呀”
業務2的大boss對業務2的rd吼道“還想甩鍋,拖出去祭天”
不知道大家工作中會不會遇到這樣的場景,因為公共庫的耦合,兄弟部門上線,影響的確是你,此時你心里可能就在罵娘了,這幫不靠譜的**隊友。
特別的,如果公共庫的使用方很廣,這個耦合很嚴重,可能影響很大的范圍。
如何解除公共庫耦合?
方案一:代碼拷貝一份
別嘲笑這個方案,誰敢說自己寫代碼的時候沒這么干過?
我們都知道這不是一個好的方案,但不可否認,拷貝之后,代碼各自演化,一個地方升級出錯,只影響一方,拷貝方只要不動原有代碼,至少是不會受影響的。
代碼拷貝缺點很多,系統拆分時,萬不得已不要使用這個方案。
方案二:垂直拆分,將公共庫里業務個性化的代碼拆到調用方去,不要放在公共庫里
需要把業務個性的代碼拆分到各個業務線自己的工程,自己的業務庫里去,例如s1.jar / s2.jar / s3.jar,修改各自的代碼,至少不會擴大影響范圍。
大家為什么都把代碼往一個公共庫里塞?
很多時候,因為惰性,一點一點的惰性,日積月累,終成大坑。
這個垂直拆分是一個架構重構的過程,需要各業務方配合。
方案三:服務化,將公共庫里通用業務代碼拆到下層去
完成了第一步,業務個性化的代碼提取到業務側上游。
接下來是第二步,業務通用的代碼,下沉抽取一層服務,服務對上游提供RPC接口:
每次修改底層接口,需要測試接口的兼容性,保證不影響舊調用方
如果是新的業務,則建議新增接口
最終,達到通過服務RPC調用的方式來解除耦合。
有朋友會問:
底層服務接口的測試
上游業務層對公共庫的測試
都是測試,為何前者能控制影響范圍呢?
底層接口,所有人調用,接口沒問題則調用方都沒問題
上游業務層對公共庫測試,只能保證自己的業務沒有問題,并不能保證其他業務方沒有問題
個性業務代碼上浮,共性業務代碼服務化下沉,只是一個很小的優化點,但對于公共庫解耦卻是非常的有效。
用C語言實例描述程序中的內聚和耦合
已剪輯自: https://cloud.tencent.com/developer/article/1866225
編程時,我們講究的是高內聚低耦合,在協同開發、代碼移植、維護等環節都起到很重要的作用。
一、原理篇
而低耦合,是指模塊之間盡可能的使其獨立存在,模塊之間不產生聯系不可能,但模塊與模塊之間的接口應該盡量少而簡單。這樣,高內聚從整個程序中每一個模塊的內部特征角度,低耦合從程序中各個模塊之間的關聯關系角度,對我們的設計提出了要求。
程序設計和軟件工程發展過程中產生的很多技術、設計原則,都可以從內聚和耦合的角度進行解讀。作為C語言程序設計的初學者,結合當前對于函數的理解可達到的程度,我們探討一下如何做到高內聚低耦合。
針對低耦合。耦合程度最低的是非直接耦合,指兩個函數之間的聯系完全是通過共同的調用函數的控制和調用來實現的,耦合度最弱,函數的獨立性最強。但一組函數之間沒有數據傳遞顯然不現實,次之追求數據耦合,調用函數和被調用函數之間只傳遞簡單的數據參數,例如采用值傳遞方式的函數。
有些函數數在調用時,利用形式參數傳地址的方式,在函數體內通過指針可以修改其指向的作用域以外的存儲單元,這構成了更強的耦合,稱為特征耦合,在這里,使函數之間產生聯系的是地址這樣的特征標識。另外,有兩個函數可能會打開同一個文件進行操作,這也構成了特征耦合的一種形式。
更強的耦合是外部耦合,這里,一組模塊都訪問同一全局變量,而且不通過參數表傳遞該全局變量的信息,當發現程序執行結果異常時,很難定位到是在哪個函數中出了差錯。不少初學者覺得參數傳遞麻煩,將要處理的數據盡可能地定義為全局變量,這樣,函數之間的接口簡單了,但形成的是耦合性很強的結構。
在C語言中,還可以通過靜態局部變量,在同一個程序的兩次調用之間共享數據,這也可以視為是一種外部耦合,只不過靜態局部變量的作用域限于函數內部,其影響也只在函數內部,耦合程度比使全局變量也還是弱很多。由此,我們可以理解前述在使用全局變量、靜態局部變量時提出的“用在合適的時候,不濫用”的原則。
針對高內聚。內聚程度最高的是功能內聚,模塊內所有元素的各個組成部分全部都為完成同一個功能而存在,共同完成一個單一的功能,模塊已不可再分。這樣的函數功能非常清晰、明確,一般出現在程序結構圖的較低被調用的層次上。
次之的是順序內聚,一個函數中各個處理元素和同一個功能密切相關,通常前一個處理元素的輸出是后一個處理元素的輸入。對于這樣的函數,如果不致于產生高耦合的話,可以分開兩個函數實現。
有的函數,其中的不同處理功能僅僅是由于都訪問某一個公用數據而發生關聯,這稱為通信內聚和信息內聚,內聚程度進一步下降。內聚程度再低的情況就不再一一列舉,最差的偶然內聚中,一個函數內的各處理元素之間沒有任何聯系,只是偶然地被湊到一起。
可以想像這樣的模塊東一榔頭西一錘子,類似一個毫無凝聚力的團伙,對應的是低質量??傊?#xff0c;在解決問題劃分函數時,要遵循“一個函數,一個功能”的原則,盡可能使模塊達到功能內聚。
要做到高內聚低耦合,重點是要在寫代碼之前花些時間做好設計。在下面的例子中,將討論結合具體的問題,如何將以上的因素考慮進去。
二、示例篇
本例受裘宗燕老師《從問題到程序——程序設計與C語言引論啟發》。
任務 輸出200以內的完全平方數(一個數如果是另一個整數的完全平方,那么我們就稱這個數為完全平方數,也叫做平方數),要求每隔5個數據要輸出一個換行。
解決方案及點評 對于這個簡單任務,我們在一個main函數中完成了任務。程序如方案1:
//方案1:內聚性較高的單模塊實現方案 #include <stdio.h> int main() {int m, num=0;for (m = 1; m * m <= 200; m++){printf("%d ", m * m);num++;if (num%5==0)printf("\n");}return 0; }由于任務本身簡單,將之在一個main函數中實現后,這個函數的內聚程度接近功能內聚,已經相當高了,就任務本身,不需再進行分解。為使讀者能深入理解模塊質量方面的技術,我們將試圖將內聚程序再提高一些,然后考察耦合程度不同的各種解決方案。
要提高上面解決方案中函數(僅main一個函數)的內聚程度,我們考察程度的功能“找出完全平方數并輸出”——“找出完全平方數”和“輸出”這本身就是兩個功能,再細分輸出時還有“要求5個數據在一行”的要求,這些功能的實現細節都在一個函數當中,可見是有余地再提高內聚程度的。
在實現的應用中,幾乎所有的處理都可以分解為“輸入-計算-輸出”的模式,優秀的解決方案往往至少要將這三個模塊都獨立出來,對于“計算”模塊而言,其內部不再包括輸入輸出,專門接受輸入的數據,計算完成后返回結果即可。當然,對于復雜的問題,在各個環節上可能還需要再做分解。
下面,我們探討將“找出完全平方數輸出”和“每5個數據后換行”分開實現的方案。這樣的分解有助于提高內聚性,與此同時,分解后的兩個模塊間的耦合程度,成為我們要關注的焦點。
現在將“找出完全平方數并輸出”的功能仍放在main函數中(獨立成為單獨的函數也可以,但不必要了),而“每5個數據后換行”的功能,設計一個名稱為format的函數,它每調用一次就輸出一個空格作為兩個完全平方數間的分隔,而每調用到第5次時,輸出的是一個換行。
這兩個模塊之間,需要有一個“現在是第幾次調用”的信息需要傳遞,不可能用耦合程度最松散的非直接耦合.我們考慮數據耦合,用簡單形式參數傳值,得到方案2。
//方案2:一個耦合度低,但不能完成功能要求的解決方案 #include <stdio.h> void format(int); int main() {int m, num=0;for (m = 1; m * m <= 200; m++){printf("%d", m * m);format(num);}return 0; } void format(int n) {n++;if (n%5==0)printf("\n");elseprintf(" ");return; }在這個程序結構中,format與main函數的耦合程度為數據耦合。在main中定義了局部變量num,在一次都未輸出時,置初值為0是合理的。在調用format時,將num傳遞來的表示第幾次輸出(第幾個完全平方數)的形式參數n,n自增1,然后再控制輸出空格或換行。
然而分析和運行程序發現,“每隔5個數據輸出一個換行”的功能并未實現。因為形式參數n在函數format內的改變對應的實在參數num占不同的內存空間,n++修改的結果,對num無任何的影響,導致了在下一次調用時,丟失了“輸出的是第幾個”的重要信息。
一個補救的方法,是由format將變化后的n值作為返回值,再傳回給main函數,得到如下方案3的程序:
//方案3:利用了返回值使耦合度增大,但功能得以實現的方案 #include <stdio.h> int format(int); int main() {int m, num=0;for (m = 1; m * m <= 200; m++){printf("%d", m * m);num = format(num);}return 0; } int format(int n) {n++;if (n%5==0)printf("\n");elseprintf(" ");return n; }維持原函數返回值為void,而將參數改為傳地址,得到下面的方案4。這個方案的耦合度更高一些,但功能還是能夠實現的。
//方案4:傳地址實現功能的方案,耦合度更大 #include <stdio.h> void format(int*); int main() {int m, num=0;for (m = 1; m * m <= 200; m++){printf("%d", m * m);format(&num);}return 0; } void format(int *p) {(*p)++;if ((*p)%5==0)printf("\n");elseprintf(" ");return; }一定有人想到了用全局變量的解決方案。這樣,可以將num定義為全局變量,num的生存周期不再依賴于函數調用,其值也能在函數的調用之間保持不變(只要其間沒有另外給它賦值),從而可以完成傳遞信息的任務。這時,format因為無需參數傳遞,可以設計為無參函數,得到如下方案5的程序:
//方案5:耦合度最高的全局變量方案 #include <stdio.h> void format(); int num=0; int main() {int m ;for (m = 1; m * m <= 200; m++){printf("%d", m * m);format();}return 0; } void format() {num++;if (num%5==0)printf("\n");elseprintf(" ");return; }這是解決這個問題的耦合程度最高的一個方案。將num定義為外部變量,意味著如果還有其他函數,num是可以被任何函數修改的,當發 format 計數錯誤時,尋找錯誤困難,而修改后又可能會帶來其他地方的錯誤。在這么一個短小的程序中,這種方案可能尚可接受,當程度的規模稍變大,可能帶來的問題必須高度重視。因此,在實際應用中,強調全局變量要慎用(不是不用)。
考慮到num是在format中應用的私用數據——只有format才關心這到底是第幾個數據,main本來都不用關心的。這樣,可以考慮將num定義為format中的局部靜態變量,得到方案6的程序:
//方案6:用靜態局部變量,耦合度偏高但封裝性最好的方案 #include <stdio.h> void format(); int main() {int m ;for (m = 1; m * m <= 200; m++){printf("%d", m * m);format();}return 0; } void format() {static int num=0;num++;if (num%5==0)printf("\n");elseprintf(" ");return; }在這里,靜態局部變量num的作用域是局部的,定義在函數體里,封裝性在所有方案里是最好的,從而能保證信息的隱蔽性,避免其他函數無意的越權訪問;不過,num的生存期是全局的,可以跨越函數的不同次調用,在兩次調用間傳遞信息,耦合程度(自己和自己的耦合)要高一些,但使main函數和format函數的耦合達到了最理想的程度,既保證了功能的正確,又保證了局部數據的安全性,表現出靜態局部變量的優勢。綜上所述,在解決一個問題時,存在著諸多的方案。方案1可以接受,但希望提高內聚性而做出改進;方案2用簡單的參數傳值方式實現耦合程度低,但很可惜不能完成功能;在其他方案中,對于這個問題,選擇的優先順序是:
?方案6、方案3 > 方案4 > 方案5 ?
建議讀者回顧前面的內容,想一想這樣排序的理由。在上述探討各個方案的過程中,我們應該體會到在程序設計能力提高的過程中,不斷地學習新的技術,懂得新的評判標準,這也就是一個不斷拓寬眼蜀的過程。在稍后的練習中,不妨多想一些方案,也能夠從專業的角度評判方案的優劣,最終做到的,就是出手就是最佳方案的專業水平。
來源:https://helijian.blog.csdn.net/article/details/79401703
用C語言編寫低耦合程序
已剪輯自: https://zhuanlan.zhihu.com/p/270880191
耦合的定義
耦合,是對模塊間關聯程度的度量。 模塊間的耦合度是指模塊之間的依賴關系,其耦合性越強,同時表明其獨立性越差。
低耦合的優點
降低模塊間的耦合度能減少模塊間的影響,防止對某一模塊修改所引起的“牽一發動全身”的水波效應,保證系統設計順利進行。
實現例子
實現兩個功能:
1、統計字符串的單詞總數。
2、統計字符串中長度大于n的個數。
在這兩個功能中,都需要將字符串中的每個單詞分離單獨處理。
普通的實現方式
typedef enum _parse_words_mode_t {MODE_SUM = 0, /* 模式:統計單詞總數 */MODE_SUM_GT, /* 模式:統計長度大于n單詞數 */ } parse_words_mode_t;int count_words(const char* str, parse_words_mode_t mode, int len) {int ret = 0;if(NULL == str || len < 0){return FALSE;}bool ret = TRUE;for (const char* iter = str; '\0' != *iter; iter++) {/** 獲取單詞word和單詞長度word_len(省略) **/if(MODE_SUM == mode){ret++;}else if(MODE_SUM_GT == mode){if(word_len > len){ret++;}}}return ret; }int main() {char str[64] = "Read Text, demo.kun abcdefg abcdefg";int32_t sum_word = count_words(str, MODE_SUM, 0);if (sum_word >= 0) {printf("\nword num:%d\n", sum_word);}int32_t gt6_word = count_words(str, MODE_SUM_GT, 6);if (gt6_word >= 0) {printf("\ngreat 6 letter : word num:%d\n", gt6_word);}return 0; }這個方法看上去好像沒什么問題,但如果功能增加了十多個,那么count_words函數的選擇結構就會越來越龐大,修改就會變得麻煩。
低耦合的實現方式
使用函數指針調用功能函數來代替使用選擇結構(if else; switch case)調用函數。
函數指針類型參數使用void*類型的ctx變量作為功能函數的上下文,即功能函數的返回值或一些參數。
函數指針類型返回值用于判斷函數執行是否成功。
typedef int bool; #define TRUE 1 #define FALSE 0/* 定義函數指針類型名 */ typedef bool (*on_word_t)(void* ctx, const char* word, unsigned int size);/*** @method parse_words* 解析字符串中的單詞* @param {const char*} str 需要解析單詞的字符串。* @param {on_word_t} word_func 單詞處理函數指針。* @param {void*} ctx 單詞處理函數指針的上下文。** @return {bool} 解析成功則返回TRUE。*/ bool parse_words(const char* str, on_word_t word_func, void* ctx) {if(NULL == str || NULL == word_func){return FALSE;}bool ret = TRUE;for (const char* iter = str; '\0' != *iter; iter++) {/** 獲取單詞word和單詞長度len(省略) **/ret = word_func(ctx, word, len + 1); /* 通過函數指針調用功能函數 */}return ret; }/* 統計單詞總數 */ static bool words_sum(void* ctx, const char* word, unsigned int size){if(NULL == ctx){return FALSE;}int* p_count = ctx;(*p_count)++; /* 單詞數+1 */return TRUE; }/*** @method count_word_sum* 計算單詞總數。* @param {const char*} str 需要計算單詞總數的字符串。** @return {int} 單詞總數(若計算的字符串為空指針,返回值為-1)。*/ int count_words_sum(const char* str) {int ret = 0;return (TRUE == parse_words(str, words_sum, &ret)) ? ret : -1; } /* 統計長度大于n的單詞數 */ /* count_word_sum_gt()函數內部使用的相關參數 */ typedef struct _ctx_word_sum_gt {int count; /* 單詞數 */const unsigned int word_len; /* 單詞長度 */ } ctx_word_sum_gt;static bool words_sum_gt(void* ctx, const char* word, unsigned int size) {if(NULL == ctx){return FALSE;}ctx_word_sum_gt* sum_gt_ctx = ctx;if ((size - 1) > sum_gt_ctx->word_len) { /* 長度是否大于word_len */sum_gt_ctx->count++; /* 單詞數+1 */}return TRUE; }/*** @method count_word_sum_gt* 計算單詞長度大于word_len的數量。(word_len為0時為單詞總數)* @param {const char*} str 需要計算單詞數量的字符串。* @param {int} word_len 單詞長度。** @return {int} 單詞數量(若計算的字符串為空指針或單詞長度小于0,返回值為-1)。*/ int count_words_sum_gt(const char* str, int word_len) {if(word_len < 0){return -1;}ctx_word_sum_gt ret = {.count = 0,.word_len = word_len,};return (TRUE == parse_words(str, words_sum_gt, &ret)) ? ret.count : -1; } int main() {char str[64] = "Read Text, demo.kun abcdefg abcdefg";int32_t sum_word = count_word_sum(str);if (sum_word >= 0) {printf("\nword num:%d\n", sum_word);}int32_t gt6_word = count_word_sum_gt(str, 6);if (gt6_word >= 0) {printf("\ngreat 6 letter : word num:%d\n", gt6_word);}return 0; }使用低耦合的實現方式將不變的代碼和易變的代碼隔離,在添加新功能時,無需再改動原有的函數,更加安全和高效。
C語言使用注冊回調方式實現模塊間交互(解耦合)
已剪輯自: https://cchang.blog.csdn.net/article/details/122279130?spm=1001.2014.3001.5502
目錄
一、實現說明
二、代碼
1、模塊A代碼
1)A.h
2)A.C
2、模塊B代碼
1)B.h
2)B.C
3、模塊C代碼
1)C.h
2)C.C
4、模塊D代碼
1)D.h
2)D.C
5、main函數代碼
三、輸出
一、實現說明
在C語言中,若需實現A模塊滿足某個條件,調用B模塊、C模塊、D模塊…等模塊中某個函數,一般采用如下寫法(偽代碼):
void AFunc() {if (condition1) {BFunc();}if (condition2) {CFunc();}if (condition3) {DFunc();} }可看出模塊間偶爾較多,若B、C、D模塊需要做什么修改,均需要嵌入的修改A模塊,不滿足C語言“對擴展開放,對修改關閉”的原理,因此采用了注冊回調的方式,A模塊提供注冊回調的接口,B、C、D各模塊在本模塊內部注冊回調,并根據需要進行對應的實現,在A模塊滿足某個條件時,依次調用各個回調函數即可。
如下所示,A模塊提供回調注冊接口,B、C、D模塊通過注冊接口注冊自身的回調函數,當模塊A滿足某個條件,觸發了FuncA時,在FuncA中依次調用其他模塊注冊的回調函數。
二、代碼
1、模塊A代碼
1)A.h
#ifndef A_H #define A_H #include <stdio.h> #include <stdbool.h>#define MAX_CALLBACK_LEN 15 typedef void (*Func)(int);bool RegisterCallback(Func callback); void FuncA(int arg);#endif2)A.C
#include <stdio.h> #include <stdbool.h> #include "A.h"Func callbackMap[MAX_CALLBACK_LEN] = { NULL }; static int g_maxRegisterCallback = 0;bool RegisterCallback(Func callback) {if (callback == NULL) {return false;}if (g_maxRegisterCallback >= MAX_CALLBACK_LEN) {return false;}callbackMap[g_maxRegisterCallback] = callback;g_maxRegisterCallback += 1;return true; }void FuncA(int arg) {g_maxRegisterCallback = (g_maxRegisterCallback > MAX_CALLBACK_LEN) ? MAX_CALLBACK_LEN : g_maxRegisterCallback;printf("[FuncA]MaxRegisterCallback= %d, arg = %d\r\n", g_maxRegisterCallback, arg);for (int i = 0; i < g_maxRegisterCallback; i++) {callbackMap[i](arg);} }2、模塊B代碼
1)B.h
#ifndef B_H #define B_H void RegisterFuncB(); #endif2)B.C
#include <stdio.h> #include <stdbool.h> #include "A.h" #include "B.h"void FuncB(int arg) {printf("[FuncB]arg = %d\r\n", arg); }void RegisterFuncB() {bool ret = RegisterCallback(FuncB);printf("[RegisterFuncB]Register Ret = %d\r\n", ret); }3、模塊C代碼
1)C.h
#ifndef C_H #define C_H void RegisterFuncC(); #endif2)C.C
#include <stdio.h> #include <stdbool.h> #include "A.h" #include "C.h"void FuncC(int arg) {printf("[FuncC]arg = %d\r\n", arg); }void RegisterFuncC() {bool ret = RegisterCallback(FuncC);printf("[RegisterFuncC]Register Ret = %d\r\n", ret); }4、模塊D代碼
1)D.h
#ifndef D_H #define D_H void RegisterFuncD();2)D.C
#include <stdio.h> #include <stdbool.h> #include "A.h" #include "D.h"void FuncD(int arg) {printf("[FuncD]arg = %d\r\n", arg); }void RegisterFuncD() {bool ret = RegisterCallback(FuncD);printf("[RegisterFuncD]Register Ret = %d\r\n", ret); }5、main函數代碼
#include <stdio.h> #include "A.h" #include "B.h" #include "D.h" #include "C.h"int main() {RegisterFuncB();RegisterFuncC();RegisterFuncD();FuncA(10);return 0; }三、輸出
總結
以上是生活随笔為你收集整理的软件耦合的分类及解决方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么是DNS服务器?
- 下一篇: SystemVerilog之interf