引言
一切随缘!
在许多面向对象的应用程序中,有许多的对象的创建代价过大或过于复杂。要是在已有的对象上面进行创建并做稍微的改动就能够为我们所用,那么事情就会变得容易很多。
好比将某个组合复杂的对象比作一个印章(可以添加组件的)、模具,每次使用的时候只要盖个章就能够完成所做的重复事情,如果有稍微不一样的地方适当的添加减少印章上面的组件;与创建各种跟父类差异很少的独立类相比,这种做法的可复用性极高并且更易于维护。
应用于"复制"操作的模式我们称之为"原型"(Prototype)模式。复制(cloning)指用同一模具生产的一系列的产品。模具所基于的物品称为原型。原型决定了最终产品应该是什么样子的。尽管产品是用同一个模具的复制品,但是某些属性、尺寸、颜色,可以稍微不同。尽管有小的差异,它们还是属于同一类。
什么是原型模式(定义)
原型模式是一种非常简单的设计模式。客户端知道抽象ProtoType类。在运行时,抽象Prototype子类的任何对象都可以按客户端的意愿被复制。因此,无需手工创建就可以制造同一类型的多个实例。
原型模式:使用原型实例指定创建对象的类型,并通过复制这个原型创建新的与该对像差异性很小的一个对象。
又可以这么解释:用原型实例对象指定创建对象种类,并通过拷贝这些原型创建新的对象(根据原始对象,克隆一个新的对象)
什么时候使用原型模式
使用场景:
- 不想要与层次相对应的工厂模式。
- 创建新对象的成本太大(其大小界限: 是否只是简单的状态改变、内存使用大小),对“原型实例”进行复制来获得一个新对象。
- 不同实例对象的差异仅仅是状态不同的若干组件对象。那么复制相应数量的原型比手工去实例化更加方便。
- 从0到有的创建一结构复杂的类并不是很容易,比如每个组件之中会有一些其他组件作为子节点的组合对象。复制已经有的组合对象并对其副本进行修改会更加的容易。
- 需要创建同类型的类,其行为属性略有不同,而且主要的差异就是内部属性,比如名称、状态、图片信息等。
- 以组合(树形)对象为基础去创建跟该组合差不多的组合对像时。
在日常开发中,常常使用的场景:
- 当我们编写组件的时候需要创建新的实例对象,但是又不想依赖于初始化操作(不依赖于构造方法)->采用原型模式(解耦合->耦合:两个实例互相关联,导致相互影响)
- 如果我们初始化的过程需要耗费非常大的资源(数据资源、硬件资源等等)->原型模式
怎么使用原型模式
为了更清晰的来理解原型模式,我们首先要掌握一下深浅拷贝的概念以及理解
什么是浅复制?
在原型模式中,我们一直说的"对原型实例进行复制来获取一个新的对象", 这里首先从内存、指针这两个方面来说明这个问题。
如果对象有个指针成员变量指向内存中的某个资源,指针是什么?那么如何复制这个对象?你只是复制指针的值传给副本的新对象吗?
指针只是存储内存中资源地址的占位符。在复制操作中,如果只是指针复制给新对象,那么底层的资源实际上是这两个实例对象共享的资源。
如下图所示:
<center>
![](https://img.haomeiwen.com/i7980283/2a8da5ad4c90ffee.png)
</center>
我们知道在面向对象的语言中所有的对象都是这个对象的指针,这个指针指向的就是这个对象的资源。当我们对这个对象进行复制并取得这个对象的副本的时候只是复制了这个对象的指针,指向的内存资源并没有改变,因此只复制了指针值而不是实际资源,最终两个对象共享同一资源,这称之为浅复制。
什么是深复制?
深复制并不仅仅就复制一下对象的指针,还复制了指针所指向的资源内容。
如下图所示:
<center>
![](https://img.haomeiwen.com/i7980283/3f4817736ba0b3ee.png)
</center>
复制(clone)操作并不是简单复制指针,还要生成内存中实际资源的真正副本,因此副本对象的指针指向了内存中不同位置的同一资源(内容)的副本,这称之为深复制。
案例
在深入原型模式的时候一定要搞明白哪些类型适用于浅度拷贝,哪些类型适用于深度拷贝。
原型模式的结构
说明它们之间静态关系的类图
![](https://img.haomeiwen.com/i7980283/22fa92c5bb5c5e63.png)
Prototype: 抽象原型类。声明克隆自身的接口。
Concreteprototype: 具体原型类。实现克隆的具体操作。
Client: 客户类。让一个原型克隆自身,从而获得一个新的对象。
Prototype声明了复制自身的接口。作为Prototype的子类,ConcretePrototype实现了具体复制自身的clone操作。这里的客户端通过请求原型复制其自身,创建一个新的对像。
自定义克隆接口案例一(浅拷贝->具体原型类完全数值类型参数)
基本模型:
原型模式->浅拷贝->数值类型
首先来写一个例子来验证:
接口类Prototype
import UIKit
protocol Prototype{
func clone() -> AnyObject
}
具体原型类Concreteprototype
import UIKit
//具体实现类
class ConcretePrototype: NSObject,Prototype {
var ClassName:String?
var ClassType:String?
init(ClassName:String,ClassType:String) {
self.ClassName = ClassName
self.ClassType = ClassType
}
func clone() -> AnyObject {
return ConcretePrototype.init(ClassName: self.ClassName!, ClassType: self.ClassType!)
}
}
客户类client,控制类
/*
* 原型模式->浅复制
*
* 简单的浅复制的原型模式 这种状态下只适用于一些值数值copy,
* 一旦原型类中存在被引用的参数(指针指向)那么这种状态就会出现数据共享的问题。
*
**/
let CatModel:ConcretePrototype = ConcretePrototype.init(ClassName: "cat", ClassType: "animal")
//克隆对象
let DogModel:ConcretePrototype = CatModel.clone() as! ConcretePrototype
//修改原型对象值,那么克隆对象的值会不会发生改变呢?
DogModel.ClassName = "dog"
DogModel.ClassType = "animal"
print("原型对象name = \(CatModel.ClassName!)")
print("原型对象sex = \(CatModel.ClassType!)")
print("克隆对象name = \(DogModel.ClassName!)")
print("克隆对象sex = \(DogModel.ClassType!)")
/*
原型对象name = cat
原型对象sex = animal
克隆对象name = dog
克隆对象sex = animal
结果是值类型的,内存当中存的数据是一个值,所以克隆的时候是直接拷贝
**/
结果表明,原型类中的数据是值数据,那么clone克隆的就是值数据而不是引用
自定义克隆接口案例二(浅拷贝->数组)
原型模式->浅拷贝->数组引用(值还是值类型string)
具体原型类的修改
import UIKit
class struggle_Concreteprototype_pointer: Prototype
{
var className:String?
var classType:String?
var actions:[String]? = []
init(className:String,classType:String,actions:[String]) {
self.className = className
self.classType = classType
self.actions = actions
}
func clone() -> AnyObject {
return struggle_Concreteprototype_pointer.init(className: self.className!, classType: self.classType!, actions: self.actions!)
}
}
client调用类的实现
/*
* 实例二
* 原型模式->浅复制->数组(值类型的数组)
*
**/
let Apple = struggle_Concreteprototype_pointer.init(className: "苹果", classType: "水果", actions: ["吃的","硬的","零食"])
let CopyApple:struggle_Concreteprototype_pointer = Apple.clone() as! struggle_Concreteprototype_pointer
Apple.actions![1] = "可以煮"
print("原型对象className = \(Apple.className!)")
print("原型对象classType = \(Apple.classType!)")
print("原型对象actions = \(Apple.actions!)")
print("克隆对象className = \(CopyApple.className!)")
print("克隆对象classType = \(CopyApple.classType!)")
print("克隆对象actions = \(CopyApple.actions!)")
/* 打印数据
原型对象className = 苹果
原型对象classType = 水果
原型对象actions = ["吃的", "硬的", "零食"]
克隆对象className = 苹果
克隆对象classType = 水果
克隆对象actions = ["吃的", "可以煮", "零食"]
*/
通过调试还能够发现这样的在Apple.actions还是CopyApple.actions里面的数据都是基础数据如图:
![](https://img.haomeiwen.com/i7980283/99e17dc5647c7448.png)
![](https://img.haomeiwen.com/i7980283/3cca3f30bdba7558.png)
分析结果出来,当原型类中的数组中的类型也是值类型,所以拷贝的时候也是直接拷贝
自定义克隆接口案例三(浅拷贝->数据模型引用对象)
为什么是浅拷贝,我会在下面进行解释。
上述2种案例属性参数其实都是值数据,这次我打算在原型类中再添加一个新的引用对象。
具体原型类如下:
import UIKit
class struggle_Concreteprototype_Model_pointer: Prototype {
var className:String?
var classengineerModel:Struggle_engineer?
func clone() -> AnyObject{
return struggle_Concreteprototype_Model_pointer.init(self.className!,self.classengineerModel!)
}
init(_ className:String, _ engineerModel:Struggle_engineer) {
self.className = className
self.classengineerModel = engineerModel
}
}
引用对象类:
import UIKit
class Struggle_engineer: NSObject {
var ClassName:String?
var age:Int
var ClassType:String?
init(_ ClassName:String, _ age:Int,ClassType:String) {
self.ClassName = ClassName
self.ClassType = ClassType
self.age = 10;
}
}
clinet调用代码:
let engineeritem = Struggle_engineer.init("struggle3g", 28, ClassType: "工程师")
let engineer = struggle_Concreteprototype_Model_pointer.init("struggle", engineeritem)
let Copyengineer = engineer.clone() as! struggle_Concreteprototype_Model_pointer
Copyengineer.classengineerModel?.ClassName = "xiaoming"
print("原型对象engineer.className = \(engineer.className!)")
print("原型对象engineer.classengineerModel.ClassName = \((engineer.classengineerModel?.ClassName)!)")
print("原型对象engineer.classengineerModel.age = \(engineer.classengineerModel!.age)")
print("原型对象engineer.classengineerModel.ClassType = \(engineer.classengineerModel!.ClassType!)")
print("克隆对象Copyengineer.className = \(Copyengineer.className!)")
print("克隆对象Copyengineer.classengineerModel.ClassName = \((Copyengineer.classengineerModel?.ClassName)!)")
print("克隆对象Copyengineer.classengineerModel.age = \(Copyengineer.classengineerModel!.age)")
print("克隆对象Copyengineer.classengineerModel.ClassType = \(Copyengineer.classengineerModel!.ClassType!)")
/* 打印数据
原型对象engineer.className = struggle
原型对象engineer.classengineerModel.ClassName = xiaoming
原型对象engineer.classengineerModel.age = 10
原型对象engineer.classengineerModel.ClassType = 工程师
克隆对象Copyengineer.className = struggle
克隆对象Copyengineer.classengineerModel.ClassName = xiaoming
克隆对象Copyengineer.classengineerModel.age = 10
克隆对象Copyengineer.classengineerModel.ClassType = 工程师
*/
结果就是大家所看到的原型对象和克隆对象的值一样,并且他们中的classengineerModel指针是一样的,这就证明了,如果是引用对象的时候克隆的只是指针而不是值(内存地址),所以案例三也是浅拷贝如图:
![](https://img.haomeiwen.com/i7980283/807b6075a40ef7dc.png)
![](https://img.haomeiwen.com/i7980283/3b602ff500cb0149.png)
自定义克隆接口案例四(深拷贝->数据模型引用对象)
案例三给我的启示是,在原型对象中的引用对象,无法直接赋值,直接赋值的话也就是简单的指针复制,并不是指针所指向内存中的数据的值。
那么这个案例就要实现完美克隆的方法,我们只需要修改案例三具体原型类中clone方法,如下修改:
//深拷贝
func clone() -> AnyObject{
let NewConcreteprototypeModel = struggle_Concreteprototype_Model_pointer.init(self.className!,self.classengineerModel!)
let NewengineerModel = Struggle_engineer.init((self.classengineerModel?.ClassName)!, (self.classengineerModel?.age)!, ClassType: (self.classengineerModel?.ClassType)!)
NewConcreteprototypeModel.classengineerModel = NewengineerModel
return NewConcreteprototypeModel
}
/* 打印结果
原型对象engineer.className = struggle
原型对象engineer.classengineerModel.ClassName = struggle3g
原型对象engineer.classengineerModel.age = 10
原型对象engineer.classengineerModel.ClassType = 工程师
克隆对象Copyengineer.className = struggle
克隆对象Copyengineer.classengineerModel.ClassName = xiaoming
克隆对象Copyengineer.classengineerModel.age = 10
克隆对象Copyengineer.classengineerModel.ClassType = 工程师
*/
重新创建一个新的对象Struggle_engineer,再将旧的对象的数据依次复制给新对象,然后在交给Copyengineer对象,这样就达到了我们一开始所需要互不影响的目的,也就是深度拷贝。指针验证结果如图:
![](https://img.haomeiwen.com/i7980283/b2661e9f740e4fdc.png)
![](https://img.haomeiwen.com/i7980283/51d33c1c882c09db.png)
指针不一样,这两个对象互不影响,互不干涉,当然还有很多种实现深拷贝的方式:归档等
系统协议NSCoping案例五(深拷贝)
自定义协议跟系统协议NSCoping实际上的功能差不多,废话不多说了直接上代码
具体原型类代码:
import Foundation
class StruggleSystemModel: NSCopying{
var SubSystem:StruggleSubSystemModel?
init(_ subSystem:StruggleSubSystemModel) {
self.SubSystem = subSystem
}
func copy(with zone: NSZone? = nil) -> Any {
let subSystem = SubSystem?.copy() as! StruggleSubSystemModel
return StruggleSystemModel.init(subSystem)
}
}
引用对象类:
import UIKit
class StruggleSubSystemModel: NSCopying {
var SystemName:String?
init(_ systemName:String) {
self.SystemName = systemName;
}
func copy(with zone: NSZone? = nil) -> Any {
return StruggleSubSystemModel.init(self.SystemName!);
}
}
client调用代码:
/*
* 实例五
* 原型模式->引用类型参数->深拷贝
*
**/
let system = StruggleSystemModel.init(StruggleSubSystemModel.init("Mac"))
let Copysystem = system.copy() as! StruggleSystemModel
Copysystem.SubSystem?.SystemName = "Windows"
print("原型对象的system.SubSystem.SystemName = \((system.SubSystem?.SystemName)!)")
print("克隆对象的Copysystem.SubSystem.SystemName = \((Copysystem.SubSystem?.SystemName)!)")
结果显而易见是两个互不相干涉的对象。
使用原型模式的好处
- 使代码简洁易懂
- 符合设计面向设计的六大基本原则中的开闭原则,对扩展开放,对修改关闭。
- 避免了在一个对象中重复修改
- 也符合设计中的单一原则
案例代码
import UIKit
class AppleIPhone {
var IPhoneVersionName:String?
init(_ VersionName:String) {
self.IPhoneVersionName = VersionName
}
}
class IPhoneManager: NSObject {
var Iphones:[AppleIPhone] = [AppleIPhone]()
func addIPhone(_ Iphone:AppleIPhone){
self.Iphones.append(Iphone)
}
func ManagerPrint(_ backCall:(AppleIPhone)->Void){
for Iphone in self.Iphones{
backCall(Iphone)
}
}
}
/*
* 调用代码
**/
let Iphone = AppleIPhone.init("XR")
let Manager = IPhoneManager.init()
Manager.addIPhone(Iphone)
Manager.ManagerPrint { (Iphone) in
print("Iphone的版本号 \(Iphone.IPhoneVersionName!)")
}
上述代码是逻辑相对简单的代码,如果说我们后期需要在AppleIPhone对象中添加一些新的内容,比如说是这个手机的拥有者(OwnerName),而拥有者可能转手几次,我们的需求是都要放入到列表中查看,那么我们通常会直接修改模型model
如下 修改1:
import UIKit
class AppleIPhone {
var IPhoneVersionName:String?
var OwnerName:String?
init(_ VersionName:String,_ OwnerName:String) {
self.IPhoneVersionName = VersionName
self.OwnerName = OwnerName;
}
}
class IPhoneManager: NSObject {
var Iphones:[AppleIPhone] = [AppleIPhone]()
func addIPhone(_ Iphone:AppleIPhone){
self.Iphones.append(AppleIPhone.init(Iphone.IPhoneVersionName!, Iphone.OwnerName!))
}
func ManagerPrint(_ backCall:(AppleIPhone)->Void){
for Iphone in self.Iphones{
backCall(Iphone)
}
}
}
/*
* 调用代码
**/
let Iphone = AppleIPhone.init("XR","Struggle3g")
let Manager = IPhoneManager.init()
Manager.addIPhone(Iphone)
Iphone.OwnerName = "xue"
Manager.addIPhone(Iphone)
Manager.ManagerPrint { (Iphone) in
print("Iphone的版本号 \(Iphone.IPhoneVersionName!)")
print("Iphone的版本号 \(Iphone.OwnerName!)")
}
修改1是直接修改模型数据得到的,在日常的开发中上述的代码不可能都这么简单,编写代码的时候考虑设计模式就要想到,单一原则,对外扩展,对修改关闭,那么我们用继承的形式再次修改
修改2:
import UIKit
class AppleIPhone {
var IPhoneVersionName:String?
init(_ VersionName:String) {
self.IPhoneVersionName = VersionName
}
}
class OwnerAppleIPhone: AppleIPhone {
var OwnerName: String?
init(_ VersionName: String, _ OwnerName: String) {
self.OwnerName = OwnerName;
super.init(VersionName)
}
}
class IPhoneManager: NSObject {
var Iphones:[AppleIPhone] = [AppleIPhone]()
func addIPhone(_ Iphone:AppleIPhone){
if let MyIphone = Iphone as? OwnerAppleIPhone {
self.Iphones.append(OwnerAppleIPhone.init(MyIphone.IPhoneVersionName!, MyIphone.OwnerName!))
}else{
self.Iphones.append(AppleIPhone.init(Iphone.IPhoneVersionName!))
}
}
func ManagerPrint(_ backCall:(AppleIPhone)->Void){
for Iphone in self.Iphones{
backCall(Iphone)
}
}
}
/*
* 调用代码
**/
let Iphone = AppleIPhone.init("XR")
let Manager = IPhoneManager.init()
Manager.addIPhone(Iphone)
Manager.addIPhone(OwnerAppleIPhone.init(Iphone.IPhoneVersionName!, "xue"))
Manager.ManagerPrint { (Iphone) in
print("==================")
if let owner = Iphone as? OwnerAppleIPhone{
print("OwerName: \(owner.OwnerName!)")
}
print("Iphone的版本号 \(Iphone.IPhoneVersionName!)")
}
虽然删掉了Model模型的修改,但是manager中的AddIphone方法还是需要修改,如果我们按照一些版本来更新什么样的数据,更新得版本越多那么在manager也就会越来越臃肿,有没有一种完全不需要修改的方式来完成呐?
那就用到设计模式了,我们将原型设计模式套进去,代码如下:
修改3:
class AppleIPhone :Prototype{
var IPhoneVersionName:String?
init(_ VersionName:String) {
self.IPhoneVersionName = VersionName
}
func clone() -> AnyObject {
return AppleIPhone.init(self.IPhoneVersionName!)
}
}
class OwnerAppleIPhone: AppleIPhone {
var OwnerName: String?
init(_ VersionName: String, _ OwnerName: String) {
self.OwnerName = OwnerName;
super.init(VersionName)
}
override func clone() -> AnyObject { //覆盖父类方法
return OwnerAppleIPhone.init(self.IPhoneVersionName!, self.OwnerName!)
}
}
class IPhoneManager: NSObject {
var Iphones:[AppleIPhone] = [AppleIPhone]()
func addIPhone(_ Iphone:AppleIPhone){
self.Iphones.append(Iphone.clone() as! AppleIPhone)
}
func ManagerPrint(_ backCall:(AppleIPhone)->Void){
for Iphone in self.Iphones{
backCall(Iphone)
}
}
}
/*
* 调用代码
**/
let Iphone = AppleIPhone.init("XR")
let Manager = IPhoneManager.init()
Manager.addIPhone(Iphone)
Manager.addIPhone(OwnerAppleIPhone.init(Iphone.IPhoneVersionName!, "xue"))
Manager.ManagerPrint { (Iphone) in
print("==================")
if let owner = Iphone as? OwnerAppleIPhone{
print("OwerName: \(owner.OwnerName!)")
}
print("Iphone的版本号 \(Iphone.IPhoneVersionName!)")
}
这样做的结果是,不管怎么添加信息,我只要继承一个原型类,就能解决需要多个同种对象不同表示的需求,以上!
网友评论