前言

什么是原型模式?

它是指创建对象的种类,并通过拷贝这些原型创建新的对象。

它是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。

原型模式的工作原理很简单:将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程。由于在软件系统中我们经常会遇到需要创建多个相同或者相似对象的情况,因此原型模式在真实开发中的使用频率还是非常高的。

一、基本使用

固定的用法就是 实现 Cloneable 接口 ,重写 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()
            }

二、对象与集合的使用

对集合与对象的的处理需要额外的注意。

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;
    }
}

例如我们自己处理数据的赋值,那么就会出现这样的问题,List是无法clone的。

如果强制这么使用

           val userProfile = UserProfile("1", "张三", "30")
            val skills = listOf("篮球", "游泳", "长跑", "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()
            }

就会报错:

我们需要改为ArrayList,因为只有它才显示了Cloneable,也就是只有内部成员属性都实现 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;
    }
}

我们就能打印正确的拷贝值:

集合是可以了,那么我们能不能添加自定义的对象呢?又会怎么样?

我们再UserProfile类中添加一个Address的对象

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();
    }
}

测试一下赋值与拷贝

        fun prototypeTest() {
            val userProfile = UserProfile("1", "张三", "30")
            val skills = arrayListOf("篮球", "游泳", "长跑", "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 = "长沙"
                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()
            }
        }

打印的结果如下:

可以看到 userProfile 与 newUser 中的 Address 是同一个对象,如果一个对象修改了 Address 的值,那么另一个对象的值就改了。

要修改起来其实很简单,我们把 Address 对象重写 Cloneable 并实现 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方法中手动的对对象赋值

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();
        // 把地址也做一次克隆,达到深拷贝
        profile.address = address.clone();
        return profile;
    }
}

我们再次运行就可以得到我们想要的结果了:

三、浅拷贝与深拷贝

浅拷贝只会复制值,深拷贝不仅会复制值也会复制引用

浅拷贝又叫影子拷贝,上面我们在拷贝文档时并没有把原文档中的字段都重新构造了一遍,而只是拷贝了引用,也就是Address字段引用了之前的Address字段,这样的话修改新的Address中的内容就会连原Address也改掉了,这就是浅拷贝。

深拷贝就是在浅拷贝的基础上,对于引用类型的字段也要采用拷贝的形式,比如上面的Address,而像String、int这些基本数据类型则没关系

平常的开发中更推荐大家使用深拷贝,就是对象实现 Cloneable 并重写 clone 方法。

例如这样深拷贝:

    @NonNull
    @Override
    public UserProfile clone() throws CloneNotSupportedException {
        UserProfile profile = (UserProfile) super.clone();
        // 把地址也做一次克隆,达到深拷贝
        profile.address = address.clone();
        return profile;
    }

当然还有另一种用法,既不是浅拷贝,也不是深拷贝,例如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 方法实际上是通过new对象的方法来实现的,并没有调用super.clone()。

四、Kotlin的应用

那么在Kotlin中的使用又有什么不同呢?

其实在Kotlin中调用普通的类是和Java一样的,但是如果是data class 的数据类,内部有copy方法可以快速实现clone的对象。

那么它是浅拷贝还是深拷贝呢?

data class Company(
    var name: String,
    var year: String,
    var address: UserAddress,
)

使用:

    val company = Company("百度", "2001年", UserAddress("北京", "海淀区"))
    YYLogUtils.w("company:$company")
    val newCompany = company.copy()
    newCompany.name = "网易"
    newCompany.address.city = "杭州"
    YYLogUtils.w("newCompany:$newCompany")
    YYLogUtils.w("company:$company")

我们再 data class 中定义一个普通的对象 Address ,我们打印的值如下:

可以看到打印的结果,默认的 data class copy 为浅拷贝。

那么如何实现深拷贝呢?我们仿造Java写 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
    }
}

使用的时候我们不使用copy()而使用clone()

    val company = Company("百度", "2001年", UserAddress("北京", "海淀区"))
    YYLogUtils.w("company:$company addressCity:${company.address.city}")
    val newCompany = company.clone()
    newCompany.name = "网易"
    newCompany.address.city = "杭州"
    YYLogUtils.w("newCompany:$newCompany  addressCity:${newCompany.address.city}")
    YYLogUtils.w("company:$company  addressCity:${company.address.city}")

那么打印的结果确实成了深拷贝,就是我们想要的。

另外一些其他的方法,例如一些第三库 github.com/bennyhuo/Ko… 给对象 data class 加注解,让其实现深拷贝的逻辑。

还有一些方法是通过Kotlin反射库的一些方式实现,扩展方法如下

//data class 对象的深拷贝
fun <T : Any> T.deepCopy(): T {
    //如果不是数据类,直接返回
    if (!this::class.isData) {
        return this
    }
    //拿到构造函数
    return this::class.primaryConstructor!!.let { primaryConstructor ->
        primaryConstructor.parameters.map { parameter ->
            //转换类型
            //最终value=第一个参数类型的对象
            val value = (this::class as KClass<T>).memberProperties.first {
                it.name == parameter.name
            }.get(this)
            //如果当前类(这里的当前类指的是参数对应的类型,比如说这里如果非基本类型时)是数据类
            if ((parameter.type.classifier as? KClass<*>)?.isData == true) {
                parameter to value?.deepCopy()
            } else {
                parameter to value
            }
            //最终返回一个新的映射map,即返回一个属性值重新组合的map,并调用callBy返回指定的对象
        }.toMap().let(primaryConstructor::callBy)
    }
}

如果报错的话,可以看看是不是Kotlin反射库没有导入,比如我项目的Kotlin版本为1.5.31 , 那么我导入了一个反射库依赖如下:

api 'org.jetbrains.kotlin:kotlin-reflect:1.5.31'

使用起来很简单,直接使用扩展方法即可:

    val company = Company("百度", "2001年", Address("北京", "海淀区"))
    YYLogUtils.w("company:$company addressCity:${company.address.city}")
    val newCompany = company.deepCopy()
    newCompany.name = "网易"
    newCompany.address.city = "杭州"
    YYLogUtils.w("newCompany:$newCompany  addressCity:${newCompany.address.city}")
    YYLogUtils.w("company:$company  addressCity:${company.address.city}")

打印的结果符合我们的预期:

这种方法需要注意的是只支持 data class ,内部的对象也需要时data class 如果是普通类或Java类的对象都是不行的。并且大家需要注意的是这么,深拷贝的性能是比浅拷贝要差一点的。特别是使用这样扩展方式的形式,他的性能比重写 Cloneable 和 clone()这样的方式性能要差。如果没有必要还是推荐使用浅拷贝实现。

比如之前的文章 MVI框架的展示。我们就是使用浅拷贝,我们就是要改变内存中List的值

    private val _viewStates: MutableLiveData<Demo14ViewState> = MutableLiveData(Demo14ViewState())
    //只需要暴露一个LiveData,包括页面所有状态
    val viewStates: LiveData<Demo14ViewState> = _viewStates
    //当前页面所需的数据与状态
    data class Demo14ViewState(
        val industrys: List<Industry> = emptyList(),
        val schools: List<SchoolBean> = emptyList(),
        var isChanged: Boolean = false
    ) : BaseViewState()
    //获取学校数据
    private fun requestSchool() {
        viewModelScope.launch {
            //开始Loading
            loadStartLoading()
            val result = mRepository.getSchool()
            result.checkSuccess {
                _viewStates.setState {
                    copy(schools = it ?: emptyList())
                }
            }
            loadHideProgress()
        }
    }

例如上文中的copy用法,浅拷贝,当我们调用接口获取到学校的是时候就赋值学校数据,浅拷贝一个对象,并对它赋值,再把它设置给LiveData,当我们获取到行业数据的时候,一样的操作,又并不会对学校的数据做修改,又修改了原生LiveData中的Value值,因为两个对象的引用是不同的,这样就可以触发LiveData的通知。也是一个比较典型的应用。

如果是深拷贝,当然也能实现同样的逻辑,就是会麻烦一点,性能也没有浅拷贝好,所以这个场景使用的是简单的浅拷贝。

总结

原型模式本质上就是对象的拷贝,容易出现的问题也都是深拷贝、浅拷贝。使用原型模式可以解决构建复杂对象的资源消耗问题,能够在某些场景下提升创建对象的效率。

优点:

  • 原型模式是在内存中二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量对象时,原型模式可能更好的体现其优点。
  • 还有一个重要的用途就是保护性拷贝,也就是对某个对象对外可能是只读的,为了防止外部对这个只读对象的修改,通常可以通过返回一个对象拷贝的形式实现只读的限制。

缺点:

  • 这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的,在实际开发中应该注意这个潜在问题。优点是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。
  • 通过实现Cloneable接口的原型模式在调用clone函数构造实例时并不一定比通过new操作速度快,只有当通过new构造对象较为耗时或者说成本较高时,通过clone方法才能够获得效率上的提升。

平常的开发中,浅拷贝和深拷贝都有各自的应用场景,如果 class 中都是基础数据,那也不需要关心是浅拷贝还是深拷贝,直接浅拷贝即可,如果包含对象,那么就要看自己的需求来选择是浅拷贝还是深拷贝了。

以上就是Android常用设计模式之原型模式详解的详细内容,更多关于Android 设计模式原型模式的资料请关注Devmax其它相关文章!

Android常用设计模式之原型模式详解的更多相关文章

  1. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  4. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  5. Ionic – Splash Screen适用于iOS,但不适用于Android

    我有一个离子应用程序,其中使用CLI命令离子资源生成的启动画面和图标iOS版本与正在渲染的启动画面完美配合,但在Android版本中,只有在加载应用程序时才会显示白屏.我检查了config.xml文件,所有路径看起来都是正确的,生成的图像出现在相应的文件夹中.(我使用了splash.psd模板来生成它们.我错过了什么?这是config.xml文件供参考,我觉得我在这里做错了–解决方法在config.xml中添加以下键:它对我有用!

  6. ios – 无法启动iPhone模拟器

    /Library/Developer/CoreSimulator/Devices/530A44CB-5978-4926-9E91-E9DBD5BFB105/data/Containers/Bundle/Application/07612A5C-659D-4C04-ACD3-D211D2830E17/ProductName.app/ProductName然后,如果您在Xcode构建设置中选择标准体系结构并再次构建和运行,则会产生以下结果:dyld:lazysymbolbindingFailed:Symbol

  7. Xamarin iOS图像在Grid内部重叠

    heyo,所以在Xamarin我有一个使用并在其中包含一对,所有这些都包含在内.这在Xamarin.Android中看起来完全没问题,但是在Xamarin.iOS中,图像与标签重叠.我不确定它的区别是什么–为什么它在Xamarin.Android中看起来不错但在iOS中它的全部都不稳定?

  8. 在iOS上向后播放HTML5视频

    我试图在iPad上反向播放HTML5视频.HTML5元素包括一个名为playbackRate的属性,它允许以更快或更慢的速率或相反的方式播放视频.根据Apple’sdocumentation,iOS不支持此属性.通过每秒多次设置currentTime属性,可以反复播放,而无需使用playbackRate.这种方法适用于桌面Safari,但似乎在iOS设备上的搜索限制为每秒1次更新–在我的情况下太慢了.有没有办法在iOS设备上向后播放HTML5视频?解决方法iOS6Safari现在支持playbackRat

  9. 使用 Swift 语言编写 Android 应用入门

    Swift标准库可以编译安卓armv7的内核,这使得可以在安卓移动设备上执行Swift语句代码。做梦,虽然Swift编译器可以胜任在安卓设备上编译Swift代码并运行。这需要的不仅仅是用Swift标准库编写一个APP,更多的是你需要一些框架来搭建你的应用用户界面,以上这些Swift标准库不能提供。简单来说,构建在安卓设备上使用的Swiftstdlib需要libiconv和libicu。通过命令行执行以下命令:gitclonegit@github.com:SwiftAndroid/libiconv-libi

  10. Swift设计模式之原型模式

    转自Swift设计模式原文Design-Patterns-In-Swift

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部