1. 相关概念
模版的用处:我们要制作一系列的东西,他们都有相同之处,我们只需要修改其中一个模板的某些元素,就能供给其他地方使用,这就是模板。
原型模型是先借用已有系统作为原型模型,通过“样品”不断改进,使得最后的产品就是用户所需要的。
原型模式属于对象的创建模式。通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的创建办法创建出更多同类型的对象。
不通过new来创建对象而是通过拷贝来创建对象的模式就叫做原型模式。
抽象的部分一致,细节不同。
2. 原型模式的原理
何时使用原型模式
某些对象或控件特别复杂,我们直接重写的话比较麻烦,而直接从之前的模板中拷贝出来,只需修改其中的几个元素就能使用。
代码示例:
(1)定义一个协议
#import <Foundation/Foundation.h>
@protocol ProtoypeCopyProtocol <NSObject>
@required
/**
* 复制自己
*
* @return 返回一个拷贝样本
*/
- (id)clone;
@end
(2)遵守协议,并实现拷贝方法
#import <Foundation/Foundation.h>
#import "ProtoypeCopyProtocol.h"
@interface StudentModel : NSObject <ProtoypeCopyProtocol>
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSNumber *age;
@property (nonatomic, strong) NSString *address;
@property (nonatomic, strong) NSNumber *totalScore;
- (id)clone; // 实现复制方法
@end
#import "Student.h"
@implementation Student
/**
* 完成复制的所有操作
*
* @return 学生对象
*/
- (id)clone{
Student *student = [[[self class] alloc] init];
student.name = self.name;
student.age = self.age;
student.address = self.address;
student.totalScore = self.totalScore;
return student;
}
@end
在主控制器中调用复制的方法
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Student *student = [[Student alloc] init];
student.name = @"乐乐乐";
student.age = 24;
student.address = @"北京宇宙无限公司";
student.totalScore = 10000;
Student *student2 = [student clone];
NSLog(@"%@", student2.name);
}
结论:以上只是通过一个小的模型来展示了原型模式的实现原理,如果一个对象很简单,属性很少,我们会觉得并没有什么卵用,但是如果很复杂的一个控件有很多相似之处,使用原型模式设计是不是让我们开发事半功倍?
3. NSCopying协议的使用细节
(1)创建基类,遵守NSCopying协议,并实现协议的代理方法
#import <Foundation/Foundation.h>
@interface BaseCopyObject : NSObject<NSCopying>
/**
* == 子类不要重载 ==
*
* @return 复制的对象
*/
- (id)copyWithZone:(NSZone *)zone;
/**
* == 由子类重载实现,只是为了通过这个方法让外界拿到拷贝过的对象用来进行进一步的赋值 =
*
* 复制(赋值实现)
* @param object 已经复制的对象
*/
- (void)copyOperationObject:(id)object;
@end
#import "BaseCopyObject.h"
@implementation BaseCopyObject
- (id)copyWithZone:(NSZone *)zone{
BaseCopyObject *copyObject = [[self class] allocWithZone:zone];
// 赋值操作作业
[self copyOperationObject:copyObject];
return copyObject;
}
- (void)copyOperationObject:(id)object{
// 空实现、什么操作都不做
}
@end
(2)Model类继承自实现了NSCopying协议的类
#import "BaseCopyObject.h"
@interface StudentModel : BaseCopyObject
@property(nonatomic, copy) NSString * name;
@property(nonatomic, assign) NSInteger age;
@end
实现协议中的方法
#import "StudentModel.h"
@implementation StudentModel
- (void)copyOperationObject:(StudentModel *)object{
object.name = self.name;
object.age = self.age;
}
@end
使用场景:
StudentModel *stu1 = [[StudentModel alloc] init];
stu1.name = @"小明";
StudentModel *stu2 = stu1.copy;
深拷贝与浅拷贝
(1)写一个Class对象,同样继承实现了NSCopying协议的基类
#import "BaseCopyObject.h"
@interface ClassModel : BaseCopyObject
@property(nonatomic, copy) NSString * className;
@property (nonatomic, strong) NSArray *students;
@end
#import "ClassModel.h"
@implementation ClassModel
- (void)copyOperationObject:(ClassModel *)object{
object.className = self.className;
object.students = self.students;
}
@end
使用场景:
ClassModel *class1 = [[ClassModel alloc] init];
class1.className = @"班级1";
class1.students = @[stu1, stu2];
ClassModel *class2 = class1.copy;
NSLog(@"%@ %@", class1, class2);
NSLog(@"%@", class1.students);
NSLog(@"%@", class2.students);
打印结果:
如何实现深拷贝?我们在赋值的时候使用深拷贝的方法即可:
- (void)copyOperationObject:(ClassModel *)object{
object.className = self.className;
// 深拷贝(完整的复制了集合里面的对象)
object.students = [[NSArray alloc] initWithArray:self.students copyItems:YES];
}
所以,在我们使用NSCopying在对对象进行拷贝的时候,如果对象中包含数组、集合、字典的时候一定要使用initWithArray: copyItem方法进行深拷贝。要注意的是数组、集合、字典里面的对象也要实现NSCopying协议。
4. 外观模式的原理
- 外观模式的基本原理
不关心内容实现,只用来使用
何时使用外观模式
- 为什么使用?
(1)解耦合(只提供几个简单的接口)
(2)简化了操作,将繁琐的实现过程封装,使用者不需要关心实现。
5. 如何绘制复杂的图形
绘制一个圆,绘制一个矩形,两者结合起来的方法
- Shape类
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
@interface Shape : NSObject
- (void)draw;
@end
- 绘制圆的类
#import "Shape.h"
@interface Circle : Shape
@property (nonatomic) CGFloat radius;
- (void)draw;
@end
- 绘制矩形类
#import "Shape.h"
@interface Rectangle : Shape
@property (nonatomic) CGFloat width;
@property (nonatomic) CGFloat height;
- (void)draw;
@end
- 图形创造器类
/*
图形创造器
*/
#import <Foundation/Foundation.h>
#import "Circle.h"
#import "Rectangle.h"
@interface ShapeMaker : NSObject
+ (void)drawCircleWithParas:(NSDictionary *)paras;
+ (void)drawCircleAndRectangle:(NSDictionary *)paras;
@end
- 主控制器调用
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 绘制一个圆
[ShapeMaker drawCircleWithParas:@{@"radius": @"10"}];
// 绘制圆 + 矩形
[ShapeMaker drawCircleAndRectangle:@{@"a": @"2"}];
// 绘制一个圆
Circle *circle = [Circle new];
circle.radius = 10.0f;
[circle draw];
// 绘制一个矩形
Rectangle *restangle = [Rectangle new];
restangle.width = 10.0f;
restangle.height = 20.0f;
[restangle draw];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
6. 使用Java实现原型模式
6.1 简单形式的原型模式
3个角色:
(1)客户角色:客户类提出创建对象的请求。
(2)抽象原型:这是一个抽象角色,通常使用接口或抽象类实现。此角色给出所有具体原型类所需的接口。
(3) 具体原型:被复制的对象。此角色需要实现抽象角色所提供的方法。
抽象原型代码:
package com.zsw.prototype;
public interface Prototype extends Cloneable {
public Prototype clone();
public void setName(String name);
public String getName();
}
具体原型代码:
package com.zsw.prototype;
public class ConcretePrototype implements Prototype {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Prototype clone(){
try {
return (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
客户端原型代码:
package com.zsw.prototype;
public class Client {
public static void main(String[] args) {
Prototype prototype = new ConcretePrototype();
prototype.setName("shangwu");
Prototype p = prototype.clone();
System.out.println(prototype == p);
System.out.println(prototype.getName());
System.out.println(p.getName());
}
}
6.2 登记形式的原始模型模式
它有如下角色:
(1)客户端角色:客户端向管理员提出创建对象的请求。
(2)抽象原型角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有具体原型类所需的接口。
(3)具体原型角色:被复制的对象,需要实现抽象原型角色定义的方法。
(4)原型管理器角色:创建具体原型类的对象,并记录每一个被创建的对象。
6.3 复制简历来实现浅复制
具体原型类型:
package com.zsw.prototype.resume;
public class Resume implements Cloneable {
private String name;
private String sex;
private int age;
private WorkExperience work;
public Resume(String name){
this.name = name;
work = new WorkExperience();
}
//设置个人信息
public void setPersonalInfo(String sex,int age){
this.sex = sex;
this.age = age;
}
//设置工作经历
public void setWorkExperience(String workDate,String company){
work.setWorkDate(workDate);
work.setCompany(company);
}
public void display(){
System.out.println("姓名:"+name+"性别:"+sex+"年龄:" + age);
System.out.println("工作日期:"+work.getWorkDate()+"工作公司:"+work.getCompany());
}
public Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
工作经历代码:
package com.zsw.prototype.resume;
public class WorkExperience {
private String workDate;
private String company;
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
public String getWorkDate() {
return workDate;
}
public void setWorkDate(String workDate) {
this.workDate = workDate;
}
}
客户端:
package com.zsw.prototype.resume;
public class Client {
public static void main(String[] args) {
Resume a = new Resume("大鸟");
a.setPersonalInfo("男",29);
a.setWorkExperience("1998-2000", "XXXX-公司");
Resume b = (Resume)a.clone();
b.setWorkExperience("1998 - 2006", "YY企业");
Resume c = (Resume)a.clone();
c.setWorkExperience("1998-2003", "ZZ企业");
a.display();
b.display();
c.display();
}
}
输出结果:
姓名:大鸟性别:男年龄:29
工作日期:1998-2003工作公司:ZZ企业
姓名:大鸟性别:男年龄:29
工作日期:1998-2003工作公司:ZZ企业
姓名:大鸟性别:男年龄:29
工作日期:1998-2003工作公司:ZZ企业
此处的工作日期,和工作公司都是一样,a、b、c三个对象都引用工作经历对象,但同时都引用了最后一个对象,所以三次输出的数据都相同。这就是浅复制的现象,只复制所考虑的对象,而不复制它的引用对象。使用深复制可以解决此问题。
6.4 对简历进行改造,使用深复制来实现
工作经历类:
package com.zsw.prototype.resume;
public class WorkExperience implements Cloneable {
private String workDate;
private String company;
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
public String getWorkDate() {
return workDate;
}
public void setWorkDate(String workDate) {
this.workDate = workDate;
}
public Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
简历源码类:
package com.zsw.prototype.resume;
public class Resume implements Cloneable {
private String name;
private String sex;
private int age;
private WorkExperience work;
public Resume(String name){
this.name = name;
work = new WorkExperience();
}
//设置个人信息
public void setPersonalInfo(String sex,int age){
this.sex = sex;
this.age = age;
}
//设置工作经历
public void setWorkExperience(String workDate,String company){
work.setWorkDate(workDate);
work.setCompany(company);
}
public void display(){
System.out.println("姓名:"+name+"性别:"+sex+"年龄:" + age);
System.out.println("工作日期:"+work.getWorkDate()+"工作公司:"+work.getCompany());
}
public Object clone(){
try {
Resume resume = (Resume) super.clone();
resume.work = (WorkExperience) this.work.clone();
return resume;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
运行结果如下:
姓名:大鸟性别:男年龄:29
工作日期:1998-2000工作公司:XXXX-公司
姓名:大鸟性别:男年龄:29
工作日期:1998 - 2006工作公司:YY企业
姓名:大鸟性别:男年龄:29
工作日期:1998-2003工作公司:ZZ企业
7. 优缺点
优点:
(1)原型模式的有点和缺点
(2) 原型模式允许动态的添加或减少产品类。
(3) 原始模型模式提供简单的创建结构。
(4)原始模型模式具有给一个应用软件动态加载新的功能能力。
(5) 产品类不需要非得有任何事先确定的等级结构。
缺点:原始模型模式的最主要的缺点是每一个类都必须配备一个克隆的方法。