设计模式——0_3 原型(Prototype)

所谓高贵的灵魂,即对自己怀有敬畏之心
——尼采

文章目录

  • 定义
  • 图纸
  • 一个例子:在程序里加入一个长方形
    • 组件工厂
    • 使用原型的原因
        • 可控的复制范围
    • 原型库
    • 原型和原型的状态
  • 写在最后的碎碎念
    • 清爽的类结构

定义

用原型实例指定创建对象的种类,在使用他的时候通过拷贝这些原型来创建新的对象

和其他创建型设计模式一样,原型对 client 隐藏了具体的产品类型,从而实现了 client 和具体产品之间的解耦;同时可以在不改动调用上下文的情况下切换具体产出的产品




图纸

设计模式——0_3 原型(Prototype)_第1张图片




一个例子:在程序里加入一个长方形

原型的实现相当简单,一言以蔽之,就是通过复制当前对象的方式创建新的对象(现在绝大多数面向对象的语言已经提供了clone方法以便你实现)

但是问题在于,什么时候下我们才会需要不使用new,而是去复制一个对象呢?

所以在这个例子里,我将省略大部分实现,因为关键是为什么要用原型,而不是原型怎么用

准备好了?那我们开始:


假定现在我们在开发这样一个绘图工具,功能是这样的:

  • 点击按钮A,画板上会生成一个长方形组件。你可以拖拽他,修改他的尺寸和背景色
  • 点击按钮B,画板上会生成一个正方形组件。你可以拖拽他,修改他的尺寸和背景色
  • 点击按钮C,画板上会放入一副50*50尺寸的正方形图片组件

针对这样的组件内容,我们定义了如下的 组件类簇,就像这样:

设计模式——0_3 原型(Prototype)_第2张图片



组件工厂

使用工厂方法(即使使用抽象工厂,问题也是一样的)生成出的效果,就像这样:

设计模式——0_3 原型(Prototype)_第3张图片

这种做法当然可以解决使用new时产生的硬编码问题,而且他封装了组件对象生成的细节

但是他和直接new也有相同的问题:

每次创建 ImageComponent 对象的时候,都要从磁盘上重新加载一遍图片,这是很消耗资源的

如果一个对象里所需要的信息可能要通过对磁盘中的文件进行解析 或者 通过数据库才能获取,这样一来生成这个对象的成本就会变高

如果此时这个对象又经常性需要新的实例,这时候我们就会考虑尽量不初始化他,转而采用复制的方式直接从内存里再得到一个状态相同的新实例



使用原型的原因

事实上上面讲的,这就是我们使用原型的第一个原因

正因为对象的初始化代价大,所以我们考虑直接复制他以获得新的实例

所以上面的组件如果采用原型模式的话,他是这样的:

设计模式——0_3 原型(Prototype)_第4张图片

新的结构允许 Component及其子类 通过复制自身的方式创建新的实例

这样一来就解决了在创建 ImageComponent 的时候的高损耗问题

可控的复制范围

那你会说,不对啊。这样一来样式也会被复制,到时候不都重叠到一起了吗?

说是复制,其实也未必真的要完全按照编程语言预设的clone方法来,只要你绕开了高损耗的部分,就算是完成任务了,比如这样:

public class ImageComponent extends Component{

 ……

	public ImageComponent clone(){
    	ImageComponent result = new ImageComponent();
     
     	result.image = this.image;  
     
     	return result;
	}

 ……
}

这样不就实现了既不会重叠,也避开了损耗,还没有把自己的字段泄露出去

事实上像这种新建大量大开销对象的需求并不少见,比如割草游戏里的草、俄罗斯方块里的方块还有祖玛嘴里的球。他们可能存活不到一秒钟,系统却要给他们设定各种各样的属性和贴图。

为了这些龙套浪费宝贵的系统资源是不值当的,这时候就是原型模式大显神威的时候



原型库

一般来说,我们不会让 client 直接去调用原型的 clone 方法,我们会使用一个原型的管理器,统一管理原型对象

这个原型管理器,我们称之为 原型库 ,就像这样:

设计模式——0_3 原型(Prototype)_第5张图片

//组件原型库
public class ComponentRepository{
    
    private Map<Class<? extends Component>,Component> prototypeMap = new HashMap<>(); 
    
    public ComponentRepository(){
        把组件类的Class对象和原型对象的映射关系写入prototypeMap中
    }
    
    public Component createComponent(Class<? extends Component> c){
        if(prototypeMap.containerKey(c)){
            return c.clone();//返还复制体
        }else {
            return null;//原型库中不存在
        }
    }
    
    public void setPrototype(Class<? extends Component> c,Component cpt){
        prototypeMap.put(c,cpt);
    }
}

原型库提供了类似工厂方法的 createComponent 让你可以获取想要的组件的复制类,同时还提供了 setPrototype 方法以供你随时替换原型对象

在下个需求里你就知道他有什么用了



原型和原型的状态

现在我们有了新的需求,他是这样的:

  • 当我修改了长方形的样式后,再点击按钮A所生成的长方形必须是修改样式后的长方形

如果现在我们是用其他的方式创建组件对象,那就必须要新增一个类用来记录当前应用的长方形的样式,并在每次创建新的组件对象的时候都访问那个记录样式的类,根据他的信息来生成新的长方形

但是原型模式中就不需要了,我们可以这样做:

//组件原型库
public class ComponentRepository{
……
    
  public Component createComponent(Class<? extends Component> c){  
        if(prototypeMap.containerKey(c)){
            Component result = c.clone();
            setPrototype(c,result);//把复制体设定成新的原型
            return result;//返还复制体
        }else {
            return null;//原型库中不存在
        }
    }
    
    public void setPrototype(Class<? extends Component> c,Component cpt){
        prototypeMap.put(c,cpt);
    }  
……
}

然后你就会发现再点击按钮A的时候生成的长方形,就已经和上一个的样式一样了

这事实上才是原型模式和工厂模式最大的区别,当你使用原型模式产出产品的时候,你对原型对象的修改,是可以即时的提现到后续产出的对象中的




写在最后的碎碎念

清爽的类结构

原型模式的结构是相对简洁的,这是因为原型模式是唯一一个必须直接依附产品类簇 上的创建型模式(静态工厂属于工厂方法的变例;单例则根本没有产品类簇的概念)

这样一来原型模式不会出现像工厂模式那样的平行类层次,也不需要生成器那样的 Director 来主导构筑对象的过程,整体会显得更加轻便

你可能感兴趣的:(设计模式,原型模式,设计模式,java)