iis服务器助手广告广告
返回顶部
首页 > 资讯 > 移动开发 >Android常用设计模式之原型模式详解
  • 341
分享到

Android常用设计模式之原型模式详解

Android 设计模式原型模式Android 原型模式 2022-11-13 18:11:18 341人浏览 独家记忆
摘要

目录前言一、基本使用二、对象与集合的使用三、浅拷贝与深拷贝四、Kotlin的应用总结前言 什么是原型模式? 它是指创建对象的种类,并通过拷贝这些原型创建新的对象。 它是用于创建重复的

前言

什么是原型模式?

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

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

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

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

一、基本使用

固定的用法就是 实现 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 设计模式原型模式的资料请关注编程网其它相关文章!

--结束END--

本文标题: Android常用设计模式之原型模式详解

本文链接: https://www.lsjlt.com/news/169856.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Android常用设计模式之原型模式详解
    目录前言一、基本使用二、对象与集合的使用三、浅拷贝与深拷贝四、Kotlin的应用总结前言 什么是原型模式? 它是指创建对象的种类,并通过拷贝这些原型创建新的对象。 它是用于创建重复的...
    99+
    2022-11-13
    Android 设计模式原型模式 Android 原型模式
  • Java设计模式之原型模式详解
    目录一、前言二、优点及适用场景三、原型模式的注意事项四、浅复制和深复制五、浅复制demo演示六、深复制demo演示一、前言 原型模式是一种比较简单的模式,也非常容易理解,实现一个接口...
    99+
    2022-11-12
  • Java设计模式之java原型模式详解
    目录介绍角色Java语言提供的clone()方法代码演示—克隆羊结论深浅拷贝深浅拷贝探讨实现深克隆的方式一 : 手动对引用对象进行克隆实现深克隆的方式一 :序列化原型模式对单例模式的...
    99+
    2022-11-12
  • Golang设计模式之原型模式详细讲解
    目录原型模式概念示例原型模式 原型是一种创建型设计模式, 使你能够复制对象, 甚至是复杂对象, 而又无需使代码依赖它们所属的类。 所有的原型类都必须有一个通用的接口, 使得即使在对象...
    99+
    2023-01-11
    Go原型模式 Go设计模式
  • Javascript设计模式之原型模式详细
    目录1、原型模式示例一示例二示例三2、观察者模式1、原型模式 原型模式用于在创建对象时,通过共享某个对象原型的属性和方法,从而达到提高性能、降低内存占用、代码复用的效果。 示例一 ...
    99+
    2022-11-12
  • JavaScript设计模式之原型模式详情
    目录前言案例回顾原型的拓展前言 设计模式呢最多的可能是用到类,我们去通过类来封装一些实用的方法,通过设计模式去实现各个方法之间的解耦等,由于JS中的继承是用原型链继承的,所以原型模式...
    99+
    2022-11-13
  • java设计模式--原型模式详解
    目录引例原型模式浅拷贝在原先Sheep类基础上实现Cloneable接口,重写clone方法。客户端调用Sheep类新添的Cow类客户端调用克隆深拷贝1.Cow类也实现Cloneab...
    99+
    2022-11-12
  • Java设计模式之原型模式的示例详解
    目录定义案例需求方案一方案二对比分析总结 定义 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象 即实现了一个原型接口,该接口用于创建当前对象的克隆,当直接创建对象的代...
    99+
    2022-11-13
  • Android设计模式之单例模式详解
    单例模式一个类只有一个实例,并且可以全局访问使用应用场景如账户管理类,数据库操作类等(某个对象频繁被访问使用)常用方式饿汉式懒汉式同步加锁DCL双重加锁验证静态内部类枚举单例饿汉式加载类的同时立即进行初始化操作,对资源消耗很大public ...
    99+
    2023-05-30
    android 设计模式 单例模式
  • Java 超详细讲解设计模式之原型模式讲解
    目录传统方式原型模式基本介绍原型模式在spring框架中源码分析深入讨论-浅讨论和深拷贝原型模式的注意事项和细节传统方式 克隆羊问题 现在有一只羊 tom,姓名为: tom,年龄为:...
    99+
    2022-11-13
  • Java设计模式之原型设计示例详解
    目录简单说一下(定义)稍微夸一下(优缺点)顺便提一下(适用场景)着重讲一下(深、浅克隆)多多用一下(结构、代码实现)简单说一下(定义) 什么是原型模式:原型模式是用于创建重复的对象,...
    99+
    2022-11-13
  • JavaScript设计模式之原型模式和适配器模式示例详解
    目录原型模式原型模式介绍代码实现适配器模式适配器模式介绍代码实现小结原型模式 原型模式介绍 原型模式是指原型实例指向创建对象的种类,并通过拷贝这些原型创建新的对象,是一种用来创建对象...
    99+
    2022-11-13
  • 深入理解Java设计模式之原型模式
    目录一、前言二、什么是原型模式三、原型模式的适用场景四、原型模式的实现1.浅拷贝实现2.深拷贝实现五、总结一、前言 单例模式可以避免重复创建消耗资源的对象,但是却不得不共用对象。若是...
    99+
    2022-11-12
  • Java中常用的设计模式之模板模式详解
    目录优点缺点使用场景一、实现方式1、游戏抽象类2、LOL游戏类3、CF游戏类二、测试总结优点 封装不变部分,扩展可变部分。 提取公共代码,便于维护。 行为由父类控制,子类实现。 缺点...
    99+
    2022-11-13
  • android开发设计模式之——单例模式详解
    单例模式是设计模式中最常见也最简单的一种设计模式,保证了在程序中只有一个实例存在并且能全局的访问到。比如在Android实际APP 开发中用到的 账号信息对象管理, 数据库对象...
    99+
    2022-06-06
    设计模式 android开发 单例模式 Android
  • .Net创建型设计模式之原型模式(Prototype)
    目录一、动机(Motivation)二、意图(Intent)三、结构(Structure)四、模式的组成五、 原型模式的具体实现六、原型模式的实现要点:1、原型模式的优点:2、原型模...
    99+
    2022-11-13
  • Java 深入理解创建型设计模式之原型模式
    1.思考问题 现在有一只羊 tom,姓名为: tom,年龄为:1,颜色为:白色,请编写程序创建和 tom羊属性完全相同的10只羊。 按照传统的思路来,我们可能会按照下面的方式去写。 ...
    99+
    2022-11-13
  • Java结构型设计模式之装饰模式详解
    目录介绍实现优缺点介绍 意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更灵活。 主要解决:我们扩展一个类常使用继承方式实现,由于继承为类引入静态特征,...
    99+
    2023-05-14
    Java设计模式装饰模式 Java装饰模式
  • Java结构型设计模式之组合模式详解
    目录组合模式应用场景优缺点主要角色组合模式结构分类透明组合模式创建抽象根节点创建树枝节点创建叶子节点客户端调用安全组合模式创建抽象根节点创建树枝节点创建叶子节点客户端调用组合模式 组...
    99+
    2022-11-13
  • Java中常用的设计模式之策略模式详解
    目录优点缺点使用场景一、实现方式1、订单类型枚举类2、订单处理接口3、普通订单处理器4、秒杀订单处理器5、拼团订单处理器6、下单管理器二、测试1、引入依赖2、测试用例总结优点 1.算...
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作