最近在搞代碼重構(gòu)业崖,這是一個(gè)很好的學(xué)習(xí)軟件設(shè)計(jì)原則委粉、設(shè)計(jì)模式呜师、架構(gòu)設(shè)計(jì)并實(shí)踐的機(jī)會(huì),本文是以一個(gè)iOS開發(fā)人員對(duì)軟件設(shè)計(jì)原則的一個(gè)概括總結(jié)贾节。
一汁汗、概況
軟件設(shè)計(jì)原則和設(shè)計(jì)模式是緊密相關(guān)的兩個(gè)概念,但它們有著不同的焦點(diǎn)和目的栗涂。軟件設(shè)計(jì)原則主要關(guān)注如何設(shè)計(jì)“好的”軟件知牌,強(qiáng)調(diào)的是架構(gòu)設(shè)計(jì)方面的規(guī)范和指導(dǎo)思想;而設(shè)計(jì)模式則是針對(duì)具體的問題和場(chǎng)景斤程,提供精細(xì)的解決方案角寸,這些方案包含了具體實(shí)現(xiàn)細(xì)節(jié)和代碼結(jié)構(gòu)。
軟件設(shè)計(jì)原則是指導(dǎo)軟件開發(fā)的通用規(guī)范和指導(dǎo)思想暖释,是從更高層面上指導(dǎo)軟件設(shè)計(jì)的袭厂。常見的軟件設(shè)計(jì)原則有SOLID
原則墨吓、KISS
原則球匕、DRY
原則、YAGNI
原則等等帖烘。
下面來(lái)對(duì)具體的"原則"做個(gè)了解.
二亮曹、SOLID原則
開放封閉原則(Open Close Principle)
對(duì)修改封閉,對(duì)擴(kuò)展開放。比如工廠方法模式和策略模式照卦,工廠方法模式通過(guò)定義一個(gè)抽象的工廠類來(lái)管理產(chǎn)品對(duì)象的實(shí)例化式矫,以便于在不改變?cè)写a的基礎(chǔ)上新增產(chǎn)品對(duì)象的類型。
簡(jiǎn)單工廠役耕、工廠方法采转、抽象工廠設(shè)計(jì)模式-iOS依賴倒置原則(Dependence Inversion Principle)
依賴于抽象,而不依賴于具體瞬痘。
依賴倒置原則中的核心思想是:高層模塊不應(yīng)該依賴于低層模塊故慈,而雙方都應(yīng)該依賴于抽象。在UITableView中框全,我們可以看到UITableViewDataSource
和UITableViewDelegate
就是抽象察绷,而UITableView
就是依賴于這兩個(gè)協(xié)議實(shí)現(xiàn)列表的數(shù)據(jù)源和代理。單一職責(zé)原則(Simple Responsibility Principle)
設(shè)計(jì)的類只負(fù)責(zé)一種類型的職責(zé)津辩,只有一個(gè)引起它變化的原因拆撼。這樣可以使代碼更加清晰,避免出現(xiàn)過(guò)度復(fù)雜的繼承關(guān)系和耦合喘沿。比如在UIViewController中闸度,可以將不同類型的任務(wù)交給不同的類去實(shí)現(xiàn),保證職責(zé)單一蚜印,代碼也清晰易懂筋岛。接口隔離原則(interface Segregation Principle)
用多個(gè)專門的接口,而不使用單一的總接口晒哄,客戶端不應(yīng)該依賴它不需要的接口睁宰。在設(shè)計(jì)時(shí)應(yīng)該注意:
1)一個(gè)類對(duì)另一個(gè)類的依賴應(yīng)該建立在最小接口上。
2)建立單一接口寝凌,不要建立龐大臃腫的接口柒傻。
3)盡量細(xì)化接口,接口中的方法盡量少(不是越少越好较木,而是適度)红符。里氏替換原則(Liskov Substitution Principle)
要求程序中,使用基類的對(duì)象可以被其子類的對(duì)象所代替伐债,而不會(huì)產(chǎn)生任何錯(cuò)誤或異常预侯,使用者不需要關(guān)心實(shí)現(xiàn)的具體類。在iOS開發(fā)中峰锁,我們可以利用多態(tài)的特性萎馅,讓不同的子類對(duì)象通過(guò)父類來(lái)使用,從而增加代碼的靈活性和擴(kuò)展性虹蒋。
三糜芳、迪米特法則(Law of Demeter)
也稱最少知道原則飒货,Least Knowledge Principle
, LKP。
指一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象保持最少的了解峭竣,盡量降低類與類之間的耦合度塘辅。一個(gè)類應(yīng)依賴于那些最直接合理的類,而不是依賴于很多其他類皆撩。主要是:
- 一個(gè)對(duì)象只應(yīng)該調(diào)用直接朋友(即與其它對(duì)象有直接關(guān)系的對(duì)象)的方法扣墩。在對(duì)象之間建立一條明確的通信路徑可以降低耦合度,使系統(tǒng)更容易維護(hù)和修改扛吞。
- 謹(jǐn)慎使用外觀模式: 外觀模式可以幫助我們降低耦合性沮榜,但是在使用時(shí)需要注意,放置太多的業(yè)務(wù)邏輯代碼到外觀模式中會(huì)導(dǎo)致對(duì)象之間互相依賴喻粹,不利于擴(kuò)展和維護(hù)蟆融。
- 不暴露任何細(xì)節(jié):一個(gè)好的設(shè)計(jì)應(yīng)該將系統(tǒng)的實(shí)現(xiàn)細(xì)節(jié)封裝在類內(nèi)部,不向外暴露任何不必要的細(xì)節(jié)信息守呜。這樣可以降低類之間的耦合度型酥,使系統(tǒng)更加穩(wěn)定和靈活。
例如:在iOS開發(fā)中查乒,UIView
只知道與其相關(guān)聯(lián)的UIViewController
弥喉,而不需要知道UIViewController
背后的一層層業(yè)務(wù)邏輯和數(shù)據(jù)存儲(chǔ)的實(shí)現(xiàn),以此來(lái)實(shí)現(xiàn)類間的解耦合玛迄。另外一個(gè)例子是KVO
.
四由境、合成復(fù)用原則(Composite/Aggregate Reuse Principle)
用組合和聚合關(guān)系來(lái)代替繼承實(shí)現(xiàn)代碼復(fù)用。這里的組合指的是通過(guò)將一個(gè)或多個(gè)對(duì)象(組合部分)組合成一個(gè)更大的蓖议、有著更高層次抽象的整體(組合整體)虏杰,而聚合則是指在一個(gè)類中引用另一個(gè)類的實(shí)例。
五勒虾、KISS原則(Keep It Simple And Stupid)
KISS原則
強(qiáng)調(diào)在設(shè)計(jì)軟件時(shí)應(yīng)力求簡(jiǎn)單纺阔,避免復(fù)雜和不必要的細(xì)節(jié)和冗余,以保持代碼的簡(jiǎn)潔修然、易理解笛钝、可維護(hù)和可擴(kuò)展。
六愕宋、YAGNI原則(You Ain't Gonna Need It)
YAGNI原則
是一種迭代開發(fā)和極限編程中的設(shè)計(jì)哲學(xué)玻靡,它告訴程序員不要在軟件中添加除了當(dāng)前需要之外的任何功能,避免浪費(fèi)時(shí)間和精力開發(fā)無(wú)用功能中贝,或在被證明是需要之前預(yù)測(cè)未來(lái)的需求囤捻。
七、DRY原則(Don’t Repeat Yourself)
DRY原則
要求程序員避免代碼重復(fù)雄妥,避免重復(fù)造輪子最蕾,復(fù)用已有的模塊和代碼依溯。這可以幫助減少錯(cuò)誤率老厌,提高代碼的可讀性瘟则、可維護(hù)性和可擴(kuò)展性。
八枝秤、GRASP原則(General Responsibility Assignment Software Parren)
職責(zé)分配軟件模式醋拧,GRASP原則
提供了一些模式和約束條件,幫助程序員正確分配和選擇各個(gè)類和對(duì)象所應(yīng)負(fù)責(zé)的職責(zé)淀弹,以提高代碼的可讀性丹壕、可維護(hù)性和可擴(kuò)展性。主要有以下九個(gè)
- 建造者(Creator):盡量將對(duì)象的創(chuàng)建和初始化工作封裝到一個(gè)專門的類中薇溃。
- 控制器(Controller):控制器模式用于協(xié)調(diào)和控制系統(tǒng)中的各種活動(dòng)和任務(wù)菌赖。
- 信息專家(Infomation Export):?jiǎn)栴}的解決應(yīng)該由盡可能具有相關(guān)知識(shí)的對(duì)象來(lái)處理。
- 低耦合(Low Coupling): 盡量減少不同對(duì)象之間的依賴關(guān)系沐序,從而降低耦合琉用。
- 高內(nèi)聚(High Cohesion): 在單個(gè)對(duì)象中將相關(guān)的屬性和方法組織在一起,以提高其內(nèi)聚性策幼。
- 間接性(Indirection): 使用一個(gè)中間對(duì)象來(lái)封裝和管理不同對(duì)象之間的通信邑时。
- 多態(tài)性(Polymorphism): 通過(guò)多態(tài)來(lái)處理對(duì)象可能的狀態(tài)變化和狀態(tài)轉(zhuǎn)換,以增強(qiáng)程序的靈活性特姐。
- 受保護(hù)的變化(Protected Variations): 在可能發(fā)生改變的地方創(chuàng)建保護(hù)殼晶丘,以防止變化對(duì)系統(tǒng)的影響。具體來(lái)說(shuō)唐含,它建議在設(shè)計(jì)類或模塊時(shí)浅浮,將可能受變化影響的部分封裝在一些具有抽象接口的類中,通過(guò)抽象層和多態(tài)性來(lái)限制變化對(duì)系統(tǒng)的影響捷枯,從而提高系統(tǒng)的可維護(hù)性和可擴(kuò)展性脑题。
- 純虛構(gòu)(Pure Fabrication):創(chuàng)建一個(gè)虛擬的類或?qū)ο髞?lái)表示某種行為或任務(wù),并將其從任何現(xiàn)有的類中分離出來(lái)铜靶。
下面是對(duì)其中幾個(gè)不太好理解的原則的舉例說(shuō)明:
1. 接口隔離原則的實(shí)例
一個(gè)常見的應(yīng)用場(chǎng)景是網(wǎng)絡(luò)請(qǐng)求叔遂,我們可以通過(guò)封裝一個(gè)網(wǎng)絡(luò)請(qǐng)求庫(kù)來(lái)方便地對(duì)外提供網(wǎng)絡(luò)請(qǐng)求的功能。在實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求庫(kù)時(shí)争剿,我們可以應(yīng)用接口隔離原則已艰,將網(wǎng)絡(luò)請(qǐng)求接口拆分成更加細(xì)粒度的接口,如下所示:
protocol NetworkRequestProtocol {
associatedtype ResponseDataType
func get(url: URL, parameters: [String: Any]?,
completion: ((Result<ResponseDataType, NetworkError>) -> Void)?)
func post(url: URL, parameters: [String: Any]?,
completion: ((Result<ResponseDataType, NetworkError>) -> Void)?)
}
protocol NetworkRequestConfigurableProtocol {
func setHTTPHeaderFields(_ headers: [String: String])
func setSerializationType(_ type: NetworkRequestSerializationType)
}
2. 間接性(Indirection)的實(shí)例
使用了MusicProvider
這個(gè)中間對(duì)象作為間接層蚕苇。通過(guò)使用 MusicProvider
對(duì)象哩掺,我們可以強(qiáng)制對(duì)歌曲訪問進(jìn)行驗(yàn)證,而不會(huì)直接使用 AudioPlayer
對(duì)象涩笤,從而實(shí)現(xiàn) Indirection
原則.
protocol MusicProviderProtocol {
func playSong(_ song: Song, completionHandler: @escaping (Error?) -> Void)
}
class MusicProvider: MusicProviderProtocol {
let audioPlayer: AudioPlayerProtocol
let accessManager: AccessManagerProtocol
init(audioPlayer: AudioPlayerProtocol, accessManager: AccessManagerProtocol) {
self.audioPlayer = audioPlayer
self.accessManager = accessManager
}
func playSong(_ song: Song, completionHandler: @escaping (Error?) -> Void) {
guard accessManager.hasAccess(to: song) else {
// 提示用戶登錄或升級(jí)賬戶以獲得足夠的訪問權(quán)限
completionHandler(MyErrors.insufficientAccess)
return
}
audioPlayer.play(song) { error in
completionHandler(error)
}
}
}
3.受保護(hù)的變化(Protected Variations)原則的實(shí)例
假設(shè)我們正在開發(fā)一個(gè)iOS應(yīng)用程序嚼吞,該應(yīng)用程序包含一個(gè)視頻播放器功能盒件。我們希望能夠?qū)⒉煌囊曨l播放程序集成到應(yīng)用程序中,例如AVFoundation或第三方播放器SDK等舱禽。為了實(shí)現(xiàn)這個(gè)目標(biāo)炒刁,我們可以通過(guò)下面的方式來(lái)應(yīng)用Protected Variations原則:
我們創(chuàng)建一個(gè)名為VideoPlayerProtocol的協(xié)議,它定義了播放器的基本行為和接口誊稚。在具體的播放器類中翔始,我們將實(shí)現(xiàn)視頻播放的具體邏輯。協(xié)議和具體類的劃分允許我們?cè)谖磥?lái)從應(yīng)用程序中修改具體的播放器實(shí)現(xiàn)里伯,而無(wú)需改變播放器到應(yīng)用程序的接口城瞎。這可以為應(yīng)用程序帶來(lái)極大的靈活性和可維護(hù)性。
// VideoPlayerProtocol defines the interface for the video player
protocol VideoPlayerProtocol {
var url: URL { get set }
func play()
}
// We implement the VideoPlayerProtocol for AVFoundation
class AVFoundationVideoPlayer: VideoPlayerProtocol {
var url: URL
init(url: URL) {
self.url = url
}
func play() {
// Play with AVPlayer
}
}
// We can add other video players using the same VideoPlayerProtocol
class ThirdPartyVideoPlayer: VideoPlayerProtocol {
var url: URL
init(url: URL) {
self.url = url
}
func play() {
// Play with third-party player SDK
}
}
4. 純虛構(gòu)(Pure Fabrication)
通過(guò)PersistenceManager
和AuthService
兩個(gè)虛構(gòu)類將業(yè)務(wù)邏輯和非業(yè)務(wù)邏輯分離開來(lái)并提高代碼的復(fù)用性和可維護(hù)性疾瓮。
class AuthService {
static let shared = AuthService()
func validate(username: String, password: String, completion: @escaping (Bool) -> Void) {
// 發(fā)送網(wǎng)絡(luò)請(qǐng)求并驗(yàn)證用戶名和密碼
// 請(qǐng)求完成后調(diào)用 completion
}
}
class PersistenceManager {
static let shared = PersistenceManager()
func save(user: User) {
// 保存用戶信息
}
func load(completion: @escaping (User?) -> Void) {
// 加載用戶信息
// 加載完成后調(diào)用 completion
}
}
class User {
var username: String
var email: String
var password: String
func signIn(completion: @escaping (Bool) -> Void) {
AuthService.shared.validate(username: username, password: password) { isValid in
completion(isValid)
}
}
func save() {
PersistenceManager.shared.save(user: self)
}
}