Skip to content

Latest commit

 

History

History
631 lines (498 loc) · 24.3 KB

File metadata and controls

631 lines (498 loc) · 24.3 KB
layout
title 3-创建型-建造者模式-原型模式-单例模式
date 2016-10-08
updated 2016-10-08
tags
设计模式
UML与设计模式
categories 设计模式
permalink
thumbnail
toc true
comment true
notag false
top false

3-创建型-建造者模式-原型模式-单例模式

3.1 建造者模式(Builder)

3.1.1 建造者模式引入-数据导出的设计

  • 导出数据有如下的要求:
    • 导出的文件,不管什么格式,都分成三个部分,分别是文件头、文件体和文件尾
    • 在文件头部分,需要描述如下信息:分公司或门市点编号、导出数据的日期,对于文本格式,中间用逗号分隔
    • 在文件体部分,需要描述如下信息:表名称、然后分条描述数据。对于文本格式,表名称单独占一行,数据描述一行算一条数据,字段间用逗号分隔。
    • 在文件尾部分,需要描述如下信息:输出人
public class ExportHeader {
  private String depId;
  private String exportDate;

  public setter()…
  public getter()…
}
public class ExportData {
  private String productId;
  private double price;
  private double amount;

  public setter()…
  public getter()…
}
public class ExportFooter {
  private String exportUser;
  
  public setter()…
  public getter()…
}

public class ExportToTxt {
public void export(ExportHeader ehm,Map<String,Collection<ExportData>> mapData,ExportFoote efm){
    StringBuffer buffer = new StringBuffer();
    //1:先来拼接文件头的内容
buffer.append(ehm.getDepId()+","+ehm.getExportDate()+"\n");
    //2:接着来拼接文件体的内容
    for(String tblName : mapData.keySet()){
        //先拼接表名称
        buffer.append(tblName+"\n");
        //然后循环拼接具体数据
        for(ExportData edm : mapData.get(tblName)){
    buffer.append(edm.getProductId()+","+edm.getPrice()+","+edm.getAmount()+"\n");
        }
    }
    //3:接着来拼接文件尾的内容
    buffer.append(efm.getExportUser());
    //把要输出的内容输出到控制台看看
    System.out.println("输出到文本文件的内容:\n"+buffer);
}
}

public class Client {
	public static void main(String[] args) {
		//准备测试数据
		……
		
		//测试输出到文本文件
		ExportToTxt toTxt = new ExportToTxt();
		toTxt.export(ehm, mapData, efm);
		//测试输出到xml文件
		ExportToXml toXml = new ExportToXml();
		toXml.export(ehm, mapData, efm);
		
	}
}

3.1.2 回顾:界面皮肤库的设计并使用建造者模式完成上例

image

interface Exporter{
	public void buildHeader(ExportHeader ehm);
	public void buildBody(mapData);
	public void buildFooter(ExportFooter efm);
}

class TxtExporter implements Exporter{  
    public void buildHeader () {  …… }  
    public void buildBody () {  …… }  
    public ComboBox createComboBox() {  …… }  
} 

class XmlExporter implements Exporter{  
    public void buildHeader () {  …… }  
    public void buildBody () {  …… }  
    public void buildFooter() {  …… }  
} 

public class Director {
	private Exporter exporter;
	public Director(Exporter builder) {
		this.builder = builder;
	}
	public void construct(ehm, mapData, efm) {
		//1:先构建Header
		exporter.buildHeader(ehm);
		//2:然后构建Body
		exporter.buildBody(mapData);
		//3:然后构建Footer
		exporter.buildFooter(efm);
	}
}

public class Client {
	public static void main(String[] args) {
		//准备测试数据
		……
		
		//测试输出到xml文件
		XmlExporter xmlBuilder = new XmlExporterr();
		
		Director director2 = new Director(xmlExporter);
		director2.construct(ehm, mapData, efm);
	}
}

  • UML图形

image

3.1.3 建造者模式定义

  • 建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
  • 建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。根据中文翻译的不同,建造者模式又可以称为生成器模式。
  • 将客户端与包含多个部件的复杂对象的创建过程分离,客户端无须知道复杂对象的内部组成部分与装配方式,只需要知道所需建造者的类型即可
  • 关注如何逐步创建一个复杂的对象,不同的建造者定义了不同的创建过程

3.1.4 建造者模式的结构

  • 建造者模式包含以下4个角色:

    1. Builder(抽象建造者)
    2. ConcreteBuilder(具体建造者)
    3. Product(产品)
    4. Director(指挥者)
  • UML: image

  • 模式分析

    1. 建造者模式的结构中引入了一个指挥者类Director,该类的作用主要有两个:一方面它隔离了客户与生产过程;另一方面它负责控制产品的生成过程。指挥者针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个完整的产品对象。
  • 结果及分析

    1. 如果需要更换具体角色建造者,只需要修改配置文件
    2. 当需要增加新的具体角色建造者时,只需将新增具体角色建造者作为抽象角色建造者的子类,然后修改配置文件即可,原有代码无须修改

3.1.5 建造者模式优缺点

  • 建造者模式的优点
    1. 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
    2. 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。
    3. 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
    4. 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便。
  • 建造者模式的缺点如下:
    1. 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
    2. 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

3.1.6 模式适用环境

  • 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
  • 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
  • 对象的创建过程独立于创建该对象的类。在建造者模式中引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类中。
  • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。

3.1.7 建造者模式与抽象工厂模式的比较

  • 与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族。
  • 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象。
  • 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。

3.2 原型模式(Prototype)

3.2.1 原型模式引入-订单处理系统的设计

  • 订单处理系统,订单类型被分成两种:一种是个人订单,一种是公司订单
  • 保存订单的业务功能,每当订单的预定产品数量超过1000的时候,就需要把订单拆成两份订单来保存,如果拆成两份订单后,还是超过1000,那就继续拆分,直到每份订单的预定产品数量不超过1000。
interface OrderApi {
int getOrderProductNum();
void setOrderProductNum(int num);
}

class PersonalOrder implements OrderApi{
  private String customerName;
  private String productId;
  private int orderProductNum = 0;
	
  public int getOrderProductNum() {
    return this.orderProductNum;
  }	
  public void setOrderProductNum(int num)   
  {
    this.orderProductNum = num;
  }
  public String toString(){
    return "本个人订单的订购人是="+this.customerName+",订购产品是="+this.productId+",订购数量为="+this.orderProductNum;
  }
}

class EnterpriseOrder implements OrderApi{
  private String enterpriseName;
  private String productId;
  private int orderProductNum = 0;
	
  public int getOrderProductNum() {
    return this.orderProductNum;
  }	
  public void setOrderProductNum(int num)   
  {
    this.orderProductNum = num;
  }
  public String toString(){
    return “本企业订单的订购企业是="+this. enterpriseName +",订购产品是="+this.productId+",订购数量为="+this.orderProductNum;
  }
}

public class OrderBusiness {
public void saveOrder(OrderApi order){
	while(order.getOrderProductNum() > 1000){
		OrderApi newOrder = null;
		if(order instanceof PersonalOrder){
			PersonalOrder p2 = new PersonalOrder();
			PersonalOrder p1 = (PersonalOrder)order;
			p2.setCustomerName(p1.getCustomerName());
			p2.setProductId(p1.getProductId());		
			p2.setOrderProductNum(1000);
			newOrder = p2;
		}else if(order instanceof EnterpriseOrder){
			EnterpriseOrder e2 = new EnterpriseOrder();
	EnterpriseOrder e1 = (EnterpriseOrder)order;
	e2.setEnterpriseName(e1.getEnterpriseName());
	e2.setProductId(e1.getProductId());
	e2.setOrderProductNum(1000);
	newOrder = e2;
		}
	order.setOrderProductNum(order.getOrderProductNum()-1000);
	System.out.println("拆分生成订单=="+newOrder);
	}
	System.out.println("订单=="+order);
}
}

public class OrderClient {
	public static void main(String[] args) {
		//创建订单对象,这里为了演示简单,直接new
		PersonalOrder op = new PersonalOrder();
		//设置订单数据
		op.setOrderProductNum(2925);
		op.setCustomerName("张三");
		op.setProductId("P0001");
		
		OrderBusiness ob = new OrderBusiness();
		//调用业务来保存订单对象
		ob.saveOrder(op);
	}
}

3.2.2 使用原型模式解决上述问题

  1. 提取类似内容至接口
  2. 把操作交给对象自己处理
interface OrderApi {
int getOrderProductNum();
void setOrderProductNum(int num);
public OrderApi cloneOrder();
}

class PersonalOrder implements OrderApi{
  private String customerName;
  private String productId;
  private int orderProductNum = 0;
  ……
  public OrderApi cloneOrder() {
    	//创建一个新的订单,然后把本实例的数据复制过去
    	PersonalOrder order = new PersonalOrder();
    	order.setCustomerName(this.customerName);
 	order.setProductId(this.productId);
	order.setOrderProductNum(this.orderProductNum);
	return order;
  }	
}

class EnterpriseOrder implements OrderApi{
  private String customerName;
  private String productId;
  private int orderProductNum = 0;
  ……
  public OrderApi cloneOrder() {
    	//创建一个新的订单,然后把本实例的数据复制过去
    	EnterpriseOrder order = new EnterpriseOrder ();
    	order.setCustomerName(this.customerName);
 	order.setProductId(this.productId);
	order.setOrderProductNum(this.orderProductNum);
	return order;
  }	
}

public class OrderBusiness {
  public void saveOrder(OrderApi order){
while(order.getOrderProductNum() > 1000){

	OrderApi newOrder = order.cloneOrder();

	newOrder.setOrderProductNum(1000);
	System.out.println("old order num=="+order.getOrderProductNum()+" , new Order num==="+newOrder.getOrderProductNum());
	order.setOrderProductNum(order.getOrderProductNum()-1000);
	System.out.println("拆分生成订单=="+newOrder);
       }		
       System.out.println("订单=="+order);
  }
}

image

3.2.3 原型模式定义

  • 原型模式(Prototype Pattern):原型模式是一种对象创建型模式,用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。原型模式允许一个对象再创建另外一个可定制的对象,无须知道任何创建的细节。
  • 原型模式的基本工作原理是通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝原型自己来实现创建过程。
  • 创建新对象(也称为克隆对象)的工厂就是原型类自身,工厂方法由负责复制原型对象的克隆方法来实现
  • 通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址,每一个克隆对象都是独立的
  • 通过不同的方式对克隆对象进行修改以后,可以得到一系列相似但不完全相同的对象

3.2.4 原型模式结构

  • 原型模式包含以下3个角色:

    1. Prototype(抽象原型类)
    2. ConcretePrototype(具体原型类)
    3. Client(客户类)
  • UML image

  • 在面向对象系统中,使用原型模式来复制一个对象自身,从而克隆出多个与原型对象一模一样的对象。

  • 在软件系统中,有些对象的创建过程较为复杂,而且有时候需要频繁创建,原型模式通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象,这就是原型模式的意图所在。

  • 通常情况下,一个类包含一些成员对象,在使用原型模式克隆对象时,根据其成员对象是否也克隆,原型模式可以分为两种形式:深克隆和浅克隆。

    1. 浅克隆(Shallow Clone):当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制
    2. 深克隆(Deep Clone):除了对象本身被复制外,对象所包含的所有成员变量也将被复制

3.2.5 原型模式分析

  • 在java的类库中已经实现了这一模式,只要你定义的类实现了Cloneable接口,用这个类所创建的对象可以做为原型对象进而克隆出更多的同类型的对象。
  • Java语言提供的clone()方法将对象复制了一份并返回给调用者。一般而言,clone()方法满足:
    1. 对任何的对象x,都有x.clone() !=x,即克隆对象与原对象不是同一个对象。
    2. 对任何的对象x,都有x.clone().getClass()==x.getClass(),即克隆对象与原对象的类型一样。
    3. 但是对于类的属性,???
public interface ProtoType extends Cloneable{public Object clone();
}
public class ConcreteProtoType implements ProtoType {   
    private String propA;   
    private String propB;     
    public ProtoType clone(){   
        try { 
	return (ProtoType)super.clone();
        }   
        catch (CloneNotSupportedException e) {
	return null; 
        }   
    }   
} 
public class Client {   
    public static void testMethod(){    
        ConcreteProtoType proto = new ConcreteProtoType();     
        proto.setPropA("propA");   
        proto.setPropB("propB");   
        ConcreteProtoType newProto = (ConcreteProtoType) proto.clone();   
        newProto.setPropA(“newPropA”);
        newProto.setPropB(“newPropB”);

        //输出 proto.GetPropA() ???
    }   
}

  • java实现shadow clone(浅克隆)与深克隆(deep clone),以上样例使用浅克隆方式实现proto type模式,浅克隆无法对对象中包含的其他非java原是类型数据进行复制。如果需要复制其他非java原是类型数据,则需要使用深克隆方式

3.2.6 原型管理器

image

using System.Collections;
class PrototypeManager
{
Hashtable ht = new Hashtable();  //使用Hashtable存储原型对象
    public PrototypeManager()
    {
        ht.Add("A", new ConcretePrototypeA());
        ht.Add("B", new ConcretePrototypeB());
    }
    public void Add(string key, Prototype prototype)
    {
        ht.Add(key,prototype);
    }
    public Prototype Get(string key)
    {
        Prototype clone = null;
        clone = ((Prototype)ht[key]).Clone();  //通过克隆方法创建新对象
        return clone;
    }
}

3.3.7 原型方法的优缺点

  • 原型模式的优点
    1. 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过一个已有实例可以提高新实例的创建效率。
    2. 原型模式提供了简化的创建结构,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
    3. 可以使用深克隆的方式保存对象的状态。
  • 原型模式的缺点
    1. 需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码。
    2. 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。

3.3 单例模式(Singleton)

3.3.1 单例模式的动机

  • 对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。
  • 如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。
  • 一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。

3.3.2 单例模式定义

  • 单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
  • 单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。

3.3.2 单例模式的结构

  • 单例模式只包含一个单例角色:

    1. Singleton(单例)
  • UML image

  • 单例模式的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式包含的角色只有一个,就是单例类——Singleton。单例类拥有一个私有构造函数,确保用户无法通过new关键字直接实例化它。除此之外,该模式中包含一个静态私有成员变量与静态公有的工厂方法,该工厂方法负责检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。

public class Singleton
{
	private static Singleton instance=null;  //静态私有成员变量
	//私有构造函数
	private Singleton()
	{	
	}
	
       //静态公有工厂方法,返回唯一实例
	public static Singleton getInstance()
	{
		if(instance==null)
		    instance=new Singleton();	
		return instance;
	}
}

public class Singleton {
	private static Singleton instance = new Singleton();
	//1:私有化构造方法,好在内部控制创建实例的数目
	private Singleton(){		
	}
	
	//2:定义一个方法来为客户端提供类实例
	//3:这个方法需要定义成类方法,也就是要加static
	public static Singleton getInstance(){
		return instance;
	}
	public static void main(String[] args) {
		for(int i=0;i<3;i++){
			System.out.println(Singleton.getInstance());
		}
	}
}

  • 在单例模式的实现过程中,需要注意如下三点:
    1. 单例类的构造函数为私有;
    2. 提供一个自身的静态私有成员变量;
    3. 提供一个公有的静态工厂方法。
  • UML image

3.3.3 懒汉式

class LazySingleton 
{ 
    private static LazySingleton instance = null; 
    
    
    private LazySingleton() { } 
    public static LazySingleton GetInstance() 
    { 
          //第一重判断,先判断实例是否存在,不存在再加锁处理
        if (instance == null) 
        {
                //加锁的程序在某一时刻只允许一个线程访问
            synchronized (Singleton.class)
            {
                //第二重判断
                if(instance==null)
                {
                    instance = new LazySingleton();  //创建单例实例
                }
            }
        }
        return instance; 
    }
}

3.3.4 饿汉式单例与懒汉式单例

  • 饿汉式单例类:无须考虑多个线程同时访问的问题;调用速度和反应时间优于懒汉式单例;资源利用效率不及懒汉式单例;系统加载时间可能会比较长
  • 懒汉式单例类:实现了延迟加载;必须处理好多个线程同时访问的问题;需通过双重检查锁定等机制进行控制,将导致系统性能受到一定影响

3.3.5 单例模式优缺点

  • 单例模式的优点
    1. 提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它,并为设计及开发团队提供了共享的概念。
    2. 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
    3. 允许可变数目的实例。我们可以基于单例模式进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例。
  • 单例模式的缺点
    1. 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
    2. 单例类的职责过重。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
    3. 滥用单例将带来一些负面问题,如现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失。
    4. 单例移植至分布式环境存在新的挑战

3.4 创建型模式小结

  • 创建型模式(Creational Pattern)关注对象的创建过程

  • 创建型模式对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离,对用户隐藏了类的实例的创建细节

  • 创建型模式描述如何将对象的创建和使用分离,让用户在使用对象时无须关心对象的创建细节,从而降低系统的耦合度,让设计方案更易于修改和扩展

  • Singleton单件模式解决的问题是:实体对象个数问题

  • Factory Methor工厂方法模式解决的问题是:某个对象的创建工作

  • AbstractFactory抽象工厂模式解决的问题是:“一系列互相依赖的对象”的创建工作

  • Builder生成器模式解决的问题是:“一些复杂对象”的创建工作,子对象变化较频繁,对算法相对稳定

  • Prototype原型模式是通过拷贝自身来创建新的对象,这一点和其他创建型模式不相同