十、原型和外观模式

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) 产品类不需要非得有任何事先确定的等级结构。

缺点:原始模型模式的最主要的缺点是每一个类都必须配备一个克隆的方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容