Android常用設(shè)計(jì)模式之原型模式詳解
前言
什么是原型模式?
它是指創(chuàng)建對(duì)象的種類,并通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象。
它是用于創(chuàng)建重復(fù)的對(duì)象,同時(shí)又能保證性能。這種類型的設(shè)計(jì)模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對(duì)象的最佳方式。
這種模式是實(shí)現(xiàn)了一個(gè)原型接口,該接口用于創(chuàng)建當(dāng)前對(duì)象的克隆。當(dāng)直接創(chuàng)建對(duì)象的代價(jià)比較大時(shí),則采用這種模式。
原型模式的工作原理很簡(jiǎn)單:將一個(gè)原型對(duì)象傳給那個(gè)要發(fā)動(dòng)創(chuàng)建的對(duì)象,這個(gè)要發(fā)動(dòng)創(chuàng)建的對(duì)象通過(guò)請(qǐng)求原型對(duì)象拷貝自己來(lái)實(shí)現(xiàn)創(chuàng)建過(guò)程。由于在軟件系統(tǒng)中我們經(jīng)常會(huì)遇到需要?jiǎng)?chuàng)建多個(gè)相同或者相似對(duì)象的情況,因此原型模式在真實(shí)開(kāi)發(fā)中的使用頻率還是非常高的。
一、基本使用
固定的用法就是 實(shí)現(xiàn) Cloneable 接口 ,重寫(xiě) clone()方法。
public class UserProfile implements Cloneable { public String userId; public String name; public String age; public UserProfile() { } public UserProfile(String userId, String name, String age) { this.userId = userId; this.name = name; this.age = age; } @NonNull @Override public UserProfile clone() throws CloneNotSupportedException { return (UserProfile) super.clone(); } }
使用:
val userProfile = UserProfile("1", "張三", "30") YYLogUtils.w("userProfile:$userProfile name:" + userProfile.name + " age:" + userProfile.age + " skill:" + userProfile.skills) try { val newUser = userProfile.clone() newUser.name = "李四" YYLogUtils.w("userProfile:$newUser name:" + newUser.name + " age:" + newUser.age + " skill:" + newUser.skills) } catch (e: Exception) { e.printStackTrace() }
二、對(duì)象與集合的使用
對(duì)集合與對(duì)象的的處理需要額外的注意。
public class UserProfile implements Cloneable { public String userId; public String name; public String age; public List<String> skills; public UserProfile() { } public UserProfile(String userId, String name, String age) { this.userId = userId; this.name = name; this.age = age; } @NonNull @Override public UserProfile clone() throws CloneNotSupportedException { UserProfile profile = (UserProfile) super.clone(); profile.name = this.name; profile.age = this.age; profile.userId = this.userId; profile.skills = (List<String>) this.skills.clone(); return profile; } }
例如我們自己處理數(shù)據(jù)的賦值,那么就會(huì)出現(xiàn)這樣的問(wèn)題,List是無(wú)法clone的。
如果強(qiáng)制這么使用
val userProfile = UserProfile("1", "張三", "30") val skills = listOf("籃球", "游泳", "長(zhǎng)跑", "Java") userProfile.skills = skills YYLogUtils.w("userProfile:$userProfile name:" + userProfile.name + " age:" + userProfile.age + " skill:" + userProfile.skills) try { val newUser = userProfile.clone() newUser.name = "李四" newUser.skills.add("H5") YYLogUtils.w("userProfile:$newUser name:" + newUser.name + " age:" + newUser.age + " skill:" + newUser.skills) } catch (e: Exception) { e.printStackTrace() }
就會(huì)報(bào)錯(cuò):
我們需要改為ArrayList,因?yàn)橹挥兴棚@示了Cloneable,也就是只有內(nèi)部成員屬性都實(shí)現(xiàn) Cloneable 接口才可以使用
那我們修改類的成員定義為ArrayList
public class UserProfile implements Cloneable { public String userId; public String name; public String age; public ArrayList<String> skills = new ArrayList<>(); public UserProfile() { } public UserProfile(String userId, String name, String age) { this.userId = userId; this.name = name; this.age = age; } @NonNull @Override public UserProfile clone() throws CloneNotSupportedException { //兩種方法都可以 // return (UserProfile) super.clone(); UserProfile profile = (UserProfile) super.clone(); profile.name = this.name; profile.age = this.age; profile.userId = this.userId; profile.skills = (ArrayList<String>) this.skills.clone(); return profile; } }
我們就能打印正確的拷貝值:
集合是可以了,那么我們能不能添加自定義的對(duì)象呢?又會(huì)怎么樣?
我們?cè)賃serProfile類中添加一個(gè)Address的對(duì)象
public class UserProfile implements Cloneable { public String userId; public String name; public String age; public UserAddress address; public ArrayList<String> skills = new ArrayList<>(); public UserProfile() { } public UserProfile(String userId, String name, String age) { this.userId = userId; this.name = name; this.age = age; } @NonNull @Override public UserProfile clone() throws CloneNotSupportedException { return (UserProfile) super.clone(); } }
測(cè)試一下賦值與拷貝
fun prototypeTest() { val userProfile = UserProfile("1", "張三", "30") val skills = arrayListOf("籃球", "游泳", "長(zhǎng)跑", "Java") userProfile.address = UserAddress("武漢", "楚河漢街") userProfile.skills = skills YYLogUtils.w( "userProfile:$userProfile name:" + userProfile.name + " age:" + userProfile.age + " skill:" + userProfile.skills + "address:" + userProfile.address + " address-city:" + userProfile.address.city ) try { val newUser = userProfile.clone() newUser.name = "李四" newUser.skills.add("H5") newUser.address.city = "長(zhǎng)沙" YYLogUtils.w( "userProfile:$newUser name:" + newUser.name + " age:" + newUser.age + " skill:" + newUser.skills + "address:" + newUser.address + " address-city:" + newUser.address.city ) YYLogUtils.w( "userProfile:$userProfile name:" + userProfile.name + " age:" + userProfile.age + " skill:" + userProfile.skills + "address:" + userProfile.address + " address-city:" + userProfile.address.city ) } catch (e: Exception) { e.printStackTrace() } }
打印的結(jié)果如下:
可以看到 userProfile 與 newUser 中的 Address 是同一個(gè)對(duì)象,如果一個(gè)對(duì)象修改了 Address 的值,那么另一個(gè)對(duì)象的值就改了。
要修改起來(lái)其實(shí)很簡(jiǎn)單,我們把 Address 對(duì)象重寫(xiě) Cloneable 并實(shí)現(xiàn) clone 方法。
public class UserAddress implements Cloneable { public String city; public String address; public UserAddress() { } public UserAddress(String city, String address) { this.city = city; this.address = address; } @NonNull @Override public UserAddress clone() throws CloneNotSupportedException { return (UserAddress) super.clone(); } }
然后在UserProfile的clone方法中手動(dòng)的對(duì)對(duì)象賦值
public class UserProfile implements Cloneable { public String userId; public String name; public String age; public UserAddress address; public ArrayList<String> skills = new ArrayList<>(); public UserProfile() { } public UserProfile(String userId, String name, String age) { this.userId = userId; this.name = name; this.age = age; } @NonNull @Override public UserProfile clone() throws CloneNotSupportedException { UserProfile profile = (UserProfile) super.clone(); // 把地址也做一次克隆,達(dá)到深拷貝 profile.address = address.clone(); return profile; } }
我們?cè)俅芜\(yùn)行就可以得到我們想要的結(jié)果了:
三、淺拷貝與深拷貝
淺拷貝只會(huì)復(fù)制值,深拷貝不僅會(huì)復(fù)制值也會(huì)復(fù)制引用
淺拷貝又叫影子拷貝,上面我們?cè)诳截愇臋n時(shí)并沒(méi)有把原文檔中的字段都重新構(gòu)造了一遍,而只是拷貝了引用,也就是Address字段引用了之前的Address字段,這樣的話修改新的Address中的內(nèi)容就會(huì)連原Address也改掉了,這就是淺拷貝。
深拷貝就是在淺拷貝的基礎(chǔ)上,對(duì)于引用類型的字段也要采用拷貝的形式,比如上面的Address,而像String、int這些基本數(shù)據(jù)類型則沒(méi)關(guān)系
平常的開(kāi)發(fā)中更推薦大家使用深拷貝,就是對(duì)象實(shí)現(xiàn) Cloneable 并重寫(xiě) clone 方法。
例如這樣深拷貝:
@NonNull @Override public UserProfile clone() throws CloneNotSupportedException { UserProfile profile = (UserProfile) super.clone(); // 把地址也做一次克隆,達(dá)到深拷貝 profile.address = address.clone(); return profile; }
當(dāng)然還有另一種用法,既不是淺拷貝,也不是深拷貝,例如Intent的clone方法
@Override public Object clone() { return new Intent(this); } private Intent(Intent o, boolean all) { this.mAction = o.mAction; this.mData = o.mData; this.mType = o.mType; this.mPackage = o.mPackage; this.mComponent = o.mComponent; if (o.mCategories != null) { this.mCategories = new ArraySet<String>(o.mCategories); } }
Intent 的 clone 方法實(shí)際上是通過(guò)new對(duì)象的方法來(lái)實(shí)現(xiàn)的,并沒(méi)有調(diào)用super.clone()。
四、Kotlin的應(yīng)用
那么在Kotlin中的使用又有什么不同呢?
其實(shí)在Kotlin中調(diào)用普通的類是和Java一樣的,但是如果是data class 的數(shù)據(jù)類,內(nèi)部有copy方法可以快速實(shí)現(xiàn)clone的對(duì)象。
那么它是淺拷貝還是深拷貝呢?
data class Company( var name: String, var year: String, var address: UserAddress, )
使用:
val company = Company("百度", "2001年", UserAddress("北京", "海淀區(qū)")) YYLogUtils.w("company:$company") val newCompany = company.copy() newCompany.name = "網(wǎng)易" newCompany.address.city = "杭州" YYLogUtils.w("newCompany:$newCompany") YYLogUtils.w("company:$company")
我們?cè)?data class 中定義一個(gè)普通的對(duì)象 Address ,我們打印的值如下:
可以看到打印的結(jié)果,默認(rèn)的 data class copy 為淺拷貝。
那么如何實(shí)現(xiàn)深拷貝呢?我們仿造Java寫(xiě) Cloneable 和 clone() 試試。
data class Company( var name: String, var year: String, var address: UserAddress, ) : Cloneable { public override fun clone(): Company { val company: Company = super.clone() as Company company.address = address.clone() return company } }
使用的時(shí)候我們不使用copy()而使用clone()
val company = Company("百度", "2001年", UserAddress("北京", "海淀區(qū)")) YYLogUtils.w("company:$company addressCity:${company.address.city}") val newCompany = company.clone() newCompany.name = "網(wǎng)易" newCompany.address.city = "杭州" YYLogUtils.w("newCompany:$newCompany addressCity:${newCompany.address.city}") YYLogUtils.w("company:$company addressCity:${company.address.city}")
那么打印的結(jié)果確實(shí)成了深拷貝,就是我們想要的。
另外一些其他的方法,例如一些第三庫(kù) github.com/bennyhuo/Ko… 給對(duì)象 data class 加注解,讓其實(shí)現(xiàn)深拷貝的邏輯。
還有一些方法是通過(guò)Kotlin反射庫(kù)的一些方式實(shí)現(xiàn),擴(kuò)展方法如下
//data class 對(duì)象的深拷貝 fun <T : Any> T.deepCopy(): T { //如果不是數(shù)據(jù)類,直接返回 if (!this::class.isData) { return this } //拿到構(gòu)造函數(shù) return this::class.primaryConstructor!!.let { primaryConstructor -> primaryConstructor.parameters.map { parameter -> //轉(zhuǎn)換類型 //最終value=第一個(gè)參數(shù)類型的對(duì)象 val value = (this::class as KClass<T>).memberProperties.first { it.name == parameter.name }.get(this) //如果當(dāng)前類(這里的當(dāng)前類指的是參數(shù)對(duì)應(yīng)的類型,比如說(shuō)這里如果非基本類型時(shí))是數(shù)據(jù)類 if ((parameter.type.classifier as? KClass<*>)?.isData == true) { parameter to value?.deepCopy() } else { parameter to value } //最終返回一個(gè)新的映射map,即返回一個(gè)屬性值重新組合的map,并調(diào)用callBy返回指定的對(duì)象 }.toMap().let(primaryConstructor::callBy) } }
如果報(bào)錯(cuò)的話,可以看看是不是Kotlin反射庫(kù)沒(méi)有導(dǎo)入,比如我項(xiàng)目的Kotlin版本為1.5.31 , 那么我導(dǎo)入了一個(gè)反射庫(kù)依賴如下:
api 'org.jetbrains.kotlin:kotlin-reflect:1.5.31'
使用起來(lái)很簡(jiǎn)單,直接使用擴(kuò)展方法即可:
val company = Company("百度", "2001年", Address("北京", "海淀區(qū)")) YYLogUtils.w("company:$company addressCity:${company.address.city}") val newCompany = company.deepCopy() newCompany.name = "網(wǎng)易" newCompany.address.city = "杭州" YYLogUtils.w("newCompany:$newCompany addressCity:${newCompany.address.city}") YYLogUtils.w("company:$company addressCity:${company.address.city}")
打印的結(jié)果符合我們的預(yù)期:
這種方法需要注意的是只支持 data class ,內(nèi)部的對(duì)象也需要時(shí)data class 如果是普通類或Java類的對(duì)象都是不行的。并且大家需要注意的是這么,深拷貝的性能是比淺拷貝要差一點(diǎn)的。特別是使用這樣擴(kuò)展方式的形式,他的性能比重寫(xiě) Cloneable 和 clone()這樣的方式性能要差。如果沒(méi)有必要還是推薦使用淺拷貝實(shí)現(xiàn)。
比如之前的文章 MVI框架的展示。我們就是使用淺拷貝,我們就是要改變內(nèi)存中List的值
private val _viewStates: MutableLiveData<Demo14ViewState> = MutableLiveData(Demo14ViewState()) //只需要暴露一個(gè)LiveData,包括頁(yè)面所有狀態(tài) val viewStates: LiveData<Demo14ViewState> = _viewStates //當(dāng)前頁(yè)面所需的數(shù)據(jù)與狀態(tài) data class Demo14ViewState( val industrys: List<Industry> = emptyList(), val schools: List<SchoolBean> = emptyList(), var isChanged: Boolean = false ) : BaseViewState() //獲取學(xué)校數(shù)據(jù) private fun requestSchool() { viewModelScope.launch { //開(kāi)始Loading loadStartLoading() val result = mRepository.getSchool() result.checkSuccess { _viewStates.setState { copy(schools = it ?: emptyList()) } } loadHideProgress() } }
例如上文中的copy用法,淺拷貝,當(dāng)我們調(diào)用接口獲取到學(xué)校的是時(shí)候就賦值學(xué)校數(shù)據(jù),淺拷貝一個(gè)對(duì)象,并對(duì)它賦值,再把它設(shè)置給LiveData,當(dāng)我們獲取到行業(yè)數(shù)據(jù)的時(shí)候,一樣的操作,又并不會(huì)對(duì)學(xué)校的數(shù)據(jù)做修改,又修改了原生LiveData中的Value值,因?yàn)閮蓚€(gè)對(duì)象的引用是不同的,這樣就可以觸發(fā)LiveData的通知。也是一個(gè)比較典型的應(yīng)用。
如果是深拷貝,當(dāng)然也能實(shí)現(xiàn)同樣的邏輯,就是會(huì)麻煩一點(diǎn),性能也沒(méi)有淺拷貝好,所以這個(gè)場(chǎng)景使用的是簡(jiǎn)單的淺拷貝。
總結(jié)
原型模式本質(zhì)上就是對(duì)象的拷貝,容易出現(xiàn)的問(wèn)題也都是深拷貝、淺拷貝。使用原型模式可以解決構(gòu)建復(fù)雜對(duì)象的資源消耗問(wèn)題,能夠在某些場(chǎng)景下提升創(chuàng)建對(duì)象的效率。
優(yōu)點(diǎn):
- 原型模式是在內(nèi)存中二進(jìn)制流的拷貝,要比直接new一個(gè)對(duì)象性能好很多,特別是要在一個(gè)循環(huán)體內(nèi)產(chǎn)生大量對(duì)象時(shí),原型模式可能更好的體現(xiàn)其優(yōu)點(diǎn)。
- 還有一個(gè)重要的用途就是保護(hù)性拷貝,也就是對(duì)某個(gè)對(duì)象對(duì)外可能是只讀的,為了防止外部對(duì)這個(gè)只讀對(duì)象的修改,通??梢酝ㄟ^(guò)返回一個(gè)對(duì)象拷貝的形式實(shí)現(xiàn)只讀的限制。
缺點(diǎn):
- 這既是它的優(yōu)點(diǎn)也是缺點(diǎn),直接在內(nèi)存中拷貝,構(gòu)造函數(shù)是不會(huì)執(zhí)行的,在實(shí)際開(kāi)發(fā)中應(yīng)該注意這個(gè)潛在問(wèn)題。優(yōu)點(diǎn)是減少了約束,缺點(diǎn)也是減少了約束,需要大家在實(shí)際應(yīng)用時(shí)考慮。
- 通過(guò)實(shí)現(xiàn)Cloneable接口的原型模式在調(diào)用clone函數(shù)構(gòu)造實(shí)例時(shí)并不一定比通過(guò)new操作速度快,只有當(dāng)通過(guò)new構(gòu)造對(duì)象較為耗時(shí)或者說(shuō)成本較高時(shí),通過(guò)clone方法才能夠獲得效率上的提升。
平常的開(kāi)發(fā)中,淺拷貝和深拷貝都有各自的應(yīng)用場(chǎng)景,如果 class 中都是基礎(chǔ)數(shù)據(jù),那也不需要關(guān)心是淺拷貝還是深拷貝,直接淺拷貝即可,如果包含對(duì)象,那么就要看自己的需求來(lái)選擇是淺拷貝還是深拷貝了。
以上就是Android常用設(shè)計(jì)模式之原型模式詳解的詳細(xì)內(nèi)容,更多關(guān)于Android 設(shè)計(jì)模式原型模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
源碼解析Android Jetpack組件之ViewModel的使用
Jetpack 是一個(gè)豐富的組件庫(kù),它的組件庫(kù)按類別分為 4 類,分別是架構(gòu)(Architecture)、界面(UI)、 行為(behavior)和基礎(chǔ)(foundation)。本文將從源碼和大家講講Jetpack組件中ViewModel的使用2023-04-04Android項(xiàng)目實(shí)戰(zhàn)之百度地圖地點(diǎn)簽到功能
這篇文章主要介紹了Android項(xiàng)目實(shí)戰(zhàn)之百度地圖地點(diǎn)簽到功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04Android自定義View構(gòu)造函數(shù)詳解
這篇文章主要為大家詳細(xì)介紹了Android自定義View構(gòu)造函數(shù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10android表格效果之ListView隔行變色實(shí)現(xiàn)代碼
首先繼承SimpleAdapter再使用重載的Adapter來(lái)達(dá)到效果,其實(shí)主要是需要重載SimpleAdapter,感興趣的朋友可以研究下,希望本文可以幫助到你2013-02-02

Android 快速使用正則表達(dá)式,校驗(yàn)身份證號(hào)的實(shí)例

Android實(shí)現(xiàn)自定義ImageView的圓角矩形圖片效果

Android自定義控件實(shí)現(xiàn)底部菜單(上)